Spring 源码解读:实现@Scope与自定义作用域

ops/2024/11/14 15:27:23/

引言

在 Spring 框架中,@Scope 注解用于定义 Spring 容器中 Bean 的作用域(Scope),即 Bean 的生命周期与其使用范围。通过不同的作用域,开发者可以控制 Bean 的创建频率及其共享方式。Spring 提供了几种常见的作用域,如 singleton(单例)和 prototype(原型),它们决定了 Bean 是在整个应用程序中共享还是每次请求时创建新的实例。在一些特定的场景下,我们可能需要自定义作用域来处理不同的 Bean 生命周期。本篇文章将通过手动实现 Bean 的作用域管理机制,展示如何实现自定义作用域,并对比 Spring 中的 @Scope 注解及其实现。

摘要

@Scope 注解是 Spring 框架中用于控制 Bean 生命周期的机制。本文将通过手动实现 Bean 的作用域管理机制,展示如何支持自定义作用域,并与 Spring 的 @Scope 注解进行对比,帮助读者深入理解不同作用域的管理方式及其应用场景。

什么是 Spring 中的作用域

Spring 提供了多种内置的 Bean 作用域,用于控制 Bean 在应用程序中的创建和管理方式。常见的作用域有:

  • singleton:默认的作用域,整个 Spring 容器中只会创建一个 Bean 实例。
  • prototype:每次请求都会创建一个新的 Bean 实例。
  • request:在 Web 应用中,每个 HTTP 请求对应一个 Bean 实例。
  • session:在 Web 应用中,每个 HTTP 会话对应一个 Bean 实例。
  • application:在 Web 应用中,每个 ServletContext 对应一个 Bean 实例。

Spring 中的 @Scope 注解用于定义 Bean 的作用域:

@Scope("singleton")
@Component
public class MySingletonBean {// Singleton作用域的Bean
}@Scope("prototype")
@Component
public class MyPrototypeBean {// Prototype作用域的Bean
}

Spring 中的 @Scope 注解

Spring 中的 @Scope 注解定义如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {String value() default "singleton"; // 定义作用域的名称
}

Spring 默认提供了上述几种常见的作用域,但在某些情况下,开发者可能需要自定义作用域来满足特定的业务需求。接下来我们将手动实现 Bean 的作用域管理机制,并支持自定义作用域。

手动实现 Bean 的作用域管理机制

需求场景

我们将实现一个简单的 Bean 工厂,支持 singleton(单例)和 prototype(原型)两种作用域,同时实现一个自定义作用域,用于模拟 Web 应用中的 request 作用域。

步骤概述

  1. 定义 Scope 接口:定义一个用于管理 Bean 作用域的接口。
  2. 实现 singletonprototype 作用域:实现单例和原型作用域。
  3. 实现自定义 request 作用域:模拟 Web 请求作用域的管理。
  4. 实现 Bean 工厂类:支持通过作用域创建 Bean 实例。
  5. 测试自定义作用域:验证作用域管理机制的工作流程。

定义 Scope 接口

首先,我们定义一个 Scope 接口,用于管理不同的 Bean 作用域。

/*** 定义 Scope 接口,用于管理 Bean 的作用域*/
public interface Scope {/*** 根据作用域获取 Bean 实例* @param beanName Bean 的名称* @param objectFactory 创建 Bean 实例的工厂* @return 作用域中的 Bean 实例*/Object get(String beanName, ObjectFactory<?> objectFactory);/*** 移除作用域中的 Bean 实例* @param beanName Bean 的名称* @return 被移除的 Bean 实例*/Object remove(String beanName);
}
  • get():根据作用域获取 Bean 实例。如果 Bean 不存在,则通过 ObjectFactory 创建新实例。
  • remove():从作用域中移除 Bean 实例。

实现 singletonprototype 作用域

接下来,我们实现 singletonprototype 作用域的管理。

import java.util.HashMap;
import java.util.Map;/*** Singleton 作用域实现*/
public class SingletonScope implements Scope {private final Map<String, Object> singletonObjects = new HashMap<>();@Overridepublic Object get(String beanName, ObjectFactory<?> objectFactory) {return singletonObjects.computeIfAbsent(beanName, k -> objectFactory.getObject());}@Overridepublic Object remove(String beanName) {return singletonObjects.remove(beanName);}
}/*** Prototype 作用域实现*/
public class PrototypeScope implements Scope {@Overridepublic Object get(String beanName, ObjectFactory<?> objectFactory) {// 每次返回一个新的实例return objectFactory.getObject();}@Overridepublic Object remove(String beanName) {// Prototype 作用域不维护 Bean 实例,因此不需要移除操作return null;}
}

说明

  • SingletonScope 使用 HashMap 来缓存 Bean 实例,每次获取时,如果 Bean 存在则返回已缓存的实例;如果不存在则通过 ObjectFactory 创建新实例。
  • PrototypeScope 每次都会返回一个新的 Bean 实例。

实现自定义 request 作用域

接下来我们实现一个自定义的 request 作用域,用于模拟每个请求对应一个新的 Bean 实例。

import java.util.HashMap;
import java.util.Map;/*** Request 作用域实现,用于模拟 Web 请求作用域*/
public class RequestScope implements Scope {private final ThreadLocal<Map<String, Object>> requestScopedBeans = ThreadLocal.withInitial(HashMap::new);@Overridepublic Object get(String beanName, ObjectFactory<?> objectFactory) {Map<String, Object> beans = requestScopedBeans.get();return beans.computeIfAbsent(beanName, k -> objectFactory.getObject());}@Overridepublic Object remove(String beanName) {Map<String, Object> beans = requestScopedBeans.get();return beans.remove(beanName);}
}

说明

  • RequestScope 使用 ThreadLocal 来模拟每个请求的作用域。每个线程都有独立的 Map 存储该请求中的 Bean 实例。
  • 这种设计类似于 Spring 中 request 作用域的工作方式。

实现 Bean 工厂类

我们将实现一个简单的 Bean 工厂类,支持通过不同作用域创建和管理 Bean 实例。

import java.util.HashMap;
import java.util.Map;/*** 简单的 Bean 工厂类,支持作用域管理*/
public class SimpleBeanFactory {private final Map<String, Scope> scopes = new HashMap<>();public SimpleBeanFactory() {// 注册默认的作用域scopes.put("singleton", new SingletonScope());scopes.put("prototype", new PrototypeScope());}/*** 注册自定义作用域* @param scopeName 作用域名称* @param scope 作用域实现*/public void registerScope(String scopeName, Scope scope) {scopes.put(scopeName, scope);}/*** 创建 Bean 实例* @param beanName Bean 的名称* @param objectFactory 创建 Bean 实例的工厂* @param scopeName 作用域名称* @return 创建的 Bean 实例*/public Object createBean(String beanName, ObjectFactory<?> objectFactory, String scopeName) {Scope scope = scopes.get(scopeName);if (scope != null) {return scope.get(beanName, objectFactory);}throw new IllegalArgumentException("Unknown scope: " + scopeName);}
}

说明

  • SimpleBeanFactory 类支持通过作用域管理不同生命周期的 Bean。
  • 默认注册了 singletonprototype 作用域,开发者也可以注册自定义作用域(例如 request 作用域)。

测试自定义作用域

我们通过一个测试类验证自定义作用域的工作流程。

public class ScopeTest {public static void main(String[] args) {// 创建 Bean 工厂SimpleBeanFactory beanFactory = new SimpleBeanFactory();// 注册自定义的 Request 作用域beanFactory.registerScope("request", new RequestScope());// 定义一个 ObjectFactory 创建 Bean 实例ObjectFactory<MyBean> objectFactory = MyBean::new;// 测试 Singleton 作用域Object singletonBean1 = beanFactory.createBean("myBean", objectFactory, "singleton");Object singletonBean2 = beanFactory.createBean("myBean", objectFactory, "singleton");System.out.println("Singleton Beans are same: " + (singletonBean1 == singletonBean2)); // true// 测试 Prototype 作用域Object prototypeBean1 = beanFactory.createBean("myBean", objectFactory, "prototype");Object prototypeBean2 = beanFactory.createBean("myBean", objectFactory, "prototype");System.out.println("Prototype Beans are same: " + (prototypeBean1 == prototypeBean2)); // false// 测试 Request 作用域Object requestBean1 = beanFactory.createBean("myBean", objectFactory, "request");Object requestBean2 = beanFactory.createBean("myBean", objectFactory, "request");System.out.println("Request Beans are same: " + (requestBean1 == requestBean2)); // true in same thread}
}class MyBean {public MyBean() {System.out.println("MyBean created");}
}

测试结果

  • singleton 作用域下,两个 Bean 实例是相同的。
  • prototype 作用域下,两个 Bean 实例是不同的。
  • request 作用域下,同一线程中创建的 Bean 是相同的。

类图与流程图

为了更好地理解作用域管理机制,我们提供了类图和流程图。

类图
Scope
+get(String beanName, ObjectFactory<?> objectFactory)
+remove(String beanName)
SingletonScopeimplementsScope
+get(String beanName, ObjectFactory<?> objectFactory)
+remove(String beanName)
PrototypeScopeimplementsScope
+get(String beanName, ObjectFactory<?> objectFactory)
+remove(String beanName)
RequestScopeimplementsScope
+get(String beanName, ObjectFactory<?> objectFactory)
+remove(String beanName)
SimpleBeanFactory
+createBean(String beanName, ObjectFactory<?> objectFactory, String scopeName)
+registerScope(String scopeName, Scope scope)
SingletonScope
PrototypeScope
RequestScope
流程图
Bean 工厂创建 Bean
根据作用域获取 Bean
Singleton作用域: 缓存Bean实例
Prototype作用域: 每次创建新实例
Request作用域: 基于请求线程缓存实例

Spring 中的 @Scope 注解解析

在 Spring 中,@Scope 注解用于控制 Bean 的作用域,Spring 通过 ScopeMetadataResolverScope 接口实现对不同作用域的支持。开发者可以通过 @Scope 注解轻松指定 Bean 的作用域,如 singletonprototype 或自定义作用域。

Spring 的自定义作用域实现

Spring 支持通过 CustomScopeConfigurer 来注册自定义的作用域。开发者可以通过扩展 Scope 接口来实现自定义作用域,并将其注册到 Spring 容器中。

@Configuration
public class AppConfig {@Beanpublic CustomScopeConfigurer customScopeConfigurer() {CustomScopeConfigurer configurer = new CustomScopeConfigurer();configurer.addScope("customScope", new CustomScope());return configurer;}
}

对比分析:手动实现与 Spring 的区别

  1. 功能复杂度

    • Spring:Spring 提供了多种内置作用域,并且支持通过注解的方式轻松定义和使用作用域。
    • 简化实现:我们的手动实现展示了 singletonprototype 和自定义 request 作用域的管理,适用于小型应用。
  2. 扩展性

    • Spring:Spring 的作用域管理机制具有高度扩展性,可以通过 @Scope 注解和 CustomScopeConfigurer 注册自定义作用域。
    • 简化实现:我们通过手动注册作用域和 ObjectFactory 来实现不同的作用域管理,虽然简单但灵活。
  3. 集成能力

    • Spring:Spring 的作用域管理机制与其生命周期管理、依赖注入等其他功能无缝集成,适用于复杂的企业级应用。
    • 简化实现:我们的实现适用于理解作用域的基本原理,但缺少与其他框架组件的集成能力。

总结

通过手动实现 Bean 的作用域管理机制,我们展示了如何通过自定义 Scope 接口管理不同生命周期的 Bean 实例。这种设计模式帮助开发者更好地控制 Bean 的创建和共享方式。在 Spring 中,@Scope 注解和自定义作用域机制为开发者提供了极大的灵活性,能够满足多种复杂的应用场景。


互动与思考

你是否在项目中遇到过需要使用自定义作用域的场景?你认为 @Scope 注解在哪些场景下最为有用?欢迎在评论区分享你的经验与见解!


如果你觉得这篇文章对你有帮助,请别忘了:

  • 点赞
  • 收藏 📁
  • 关注 👀

让我们一起深入学习 Spring 框架,成为更优秀的开发者!



http://www.ppmy.cn/ops/113794.html

相关文章

【C++】探秘二叉搜索树

&#x1f680;个人主页&#xff1a;奋斗的小羊 &#x1f680;所属专栏&#xff1a;C 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 前言&#x1f4a5;一、二叉搜索树&#x1f4a5;1.1 特点&#x1f4a5;1.2 基本操作&#x1f4a5;1.2.1 插入…

Django学习实战篇五(适合略有基础的新手小白学习)(从0开发项目)

前言&#xff1a; 本章中&#xff0c;我们开始引入前端框架Bootstrap 来美化界面。在前面的章节中&#xff0c;我们通过编写后端代码来处理数据。数据之于网站&#xff0c;就相当于灵魂之于人类。而网站的前端就相当于人的形体外貌。其中HTML是骨架&#xff0c;而CSS是皮肤&…

前端进阶,使用Node.js做中间层,实现接口转发和服务器渲染

在Web开发中&#xff0c;Node.js经常被用作中间层&#xff08;也称为后端或服务器端&#xff09;&#xff0c;用于处理各种任务&#xff0c;包括接口转发&#xff08;API Gateway&#xff09;、服务器渲染&#xff08;Server-Side Rendering, SSR&#xff09;等。下面我将分别解…

一周热门|重磅!AI无限学习、进化,研究登上Nature;Meta提出多模态模型训练方法Transfusion

大模型周报将从【企业动态】【技术前瞻】【政策法规】【专家观点】四部分&#xff0c;带你快速跟进大模型行业热门动态。 01 企业动态 Ideogram 推出文生图模型 Ideogram 2.0 日前&#xff0c;Ideogram 推出了新版本文本到图像模型 Ideogram 2.0。据介绍&#xff0c;Ideogra…

二层、三层网络基本原理

文章目录 二层网络整体拓扑相关配置配置namespace创建switch创建veth设备配置veth的IP启动veth 测试 三层网络配置vm1配置vm2配置 测试 二层网络 我们用Linux bridge模拟现实中的switch&#xff0c;用namespace模拟连接在交换机上的pc 整体拓扑 ------------------ ----…

Flask-SQLAlchemy一对多 一对一 多对多关联

一. 组织一个 Flask 项目通常需要遵循一定的结构&#xff0c;以便代码清晰、可维护。下面是一个典型的 Flask 项目结构&#xff1a; my_flask_app/ │ ├── app/ │ ├── __init__.py │ ├── models.py │ ├── views.py │ ├── forms.py │ ├── tem…

vue无法通过页面路径访问提示404,通过nginx配置处理

部署vue项目时&#xff0c;可以通过IP的方式访问主页&#xff0c;当进入特定页面在刷新时&#xff0c;因为浏览器通过URL地址进行请求&#xff0c;就提示404错误。 每次都需要重新从主页进入&#xff0c;这里是因为nginx配置的问题&#xff0c;在nginx里增加一行重定向的设置 …

蓝桥杯-STM32G431RBT6(UART解析字符串sscanf和解决串口BUG)

一、C语言常识 printf和sprintf的主要区别在于它们的功能和用途&#xff1a; printf&#xff1a;主要用于将格式化的数据输出到标准输出&#xff08;如屏幕&#xff09;。sprintf&#xff1a;则是将格式化的数据存储到一个指定的字符串缓冲区中&#xff0c;而不是直接输出。 pr…