【Spring】Scope注解的使用以及原理
1 前言
Spring 帮助我们管理着 Bean,那么带来的一个思考就是 Bean 该维护一个实例呢?还是每次都获取新的呢?单例的依赖多例的作用范围的变化怎么处理呢?也就是 Bean 的一个作用范围的管理是怎么控制的呢?这就是我们本节要看的 Scope。
2 Scope 介绍
2.1 @Scope 注解
在 spring中,如果你的项目没有什么特殊的用法,其实一般你见不到 Scope 相关的东西,我们先来看看注解 @Scope 的定义:
// 注解作用在类或者注解上 方法上 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Scope { /** * Alias for {@link #scopeName}. * @see #scopeName scope的名称 */ @AliasFor("scopeName") String value() default ""; /** * Specifies the name of the scope to use for the annotated component/bean. * <p>Defaults to an empty string ({@code ""}) which implies * {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}. * @since 4.2 spring 默认提供的scope 默认的都是单例 singleton 的 * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE * @see ConfigurableBeanFactory#SCOPE_SINGLETON * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION * @see #value */ @AliasFor("value") String scopeName() default ""; /** * 作用域代理 * @see ScopedProxyMode DEFAULT(默认是NO)、NO、INTERFACES(JDK代理)、TARGET_CLASS(CGlib代理) */ ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT; }
主要有两个:scopeName 作用范围的名称、proxyMode 作用范围的代理。
spring容器中scope常见的有5种:
(1)singleton:当scope的值设置为singleton的时候,整个spring容器中只会存在一个bean实例,通过容器多次查找bean的时候(调用BeanFactory的getBean方法或者bean之间注入依赖的bean对象的时候),返回的都是同一个bean对象,singleton是scope的默认值,所以spring容器中默认创建的bean对象是单例的,通常spring容器在启动的时候,会将scope为singleton的bean创建好放在容器中(有个特殊的情况,当bean的lazy被设置为true的时候,表示懒加载,那么使用的时候才会创建),用的时候直接返回。
(2)prototype:如果scope被设置为prototype类型的了,表示这个bean是多例的,通过容器每次获取的bean都是不同的实例,每次获取都会重新创建一个bean实例对象。
(3)request:当一个bean的作用域为request,表示在一次http请求中,一个bean对应一个实例;对每个http请求都会创建一个bean实例,request结束的时候,这个bean也就结束了,request作用域用在spring容器的web环境中。
(4)session:这个和request类似,也是用在web环境中,session级别共享的bean,每个会话会对应一个bean实例,不同的session对应不同的bean实例。
(5)request:全局web应用级别的作用于,也是在web环境中使用的,一个web应用程序对应一个bean实例,通常情况下和singleton效果类似的,不过也有不一样的地方,singleton是每个spring容器中只有一个bean实例,一般我们的程序只有一个spring容器,但是,一个应用程序中可以创建多个spring容器,不同的容器中可以存在同名的bean,但是sope=aplication的时候,不管应用中有多少个spring容器,这个应用中同名的bean只有一个。
2.2 Scope 接口(自定义 Scope)
spring内置的几种sope都无法满足我们的需求的时候,我们可以自定义bean的作用域。需要实现 spring 中的 Scope 接口,我们先看下 Scope 接口:
public interface Scope { /** * 返回当前作用域中name对应的bean对象 * name:需要检索的bean的名称 * objectFactory:如果name对应的bean在当前作用域中没有找到,那么可以调用这个ObjectFactory来创建这个对象 **/ Object get(String name, ObjectFactory<?> objectFactory); /** * 将name对应的bean从当前作用域中移除 **/ @Nullable Object remove(String name); /** * 用于注册销毁回调,如果想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象 */ void registerDestructionCallback(String name, Runnable callback); /** * 用于解析相应的上下文数据,比如request作用域将返回request中的属性。 */ @Nullable Object resolveContextualObject(String key); /** * 作用域的会话标识,比如session作用域将是sessionId */ @Nullable String getConversationId(); }
将自定义的 scope 注册到容器需要调用org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope的方法,看一下这个方法的声明:
/** * 向容器中注册自定义的Scope *scopeName:作用域名称 * scope:作用域对象 **/ void registerScope(String scopeName, Scope scope);
然后我们就可以使用了,比如我们这里的自定义线程的 Scope:
/** * @author: kuku * @description */ public class ThreadScope implements Scope { public static final String SCOPE_NAME = "thread"; private ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal() { @Override protected Object initialValue() { return new HashMap<>(); } }; @Override public Object get(String s, ObjectFactory<?> objectFactory) { // 获取到当前线程的map Map<String, Object> map = threadLocal.get(); // 先从 map 中获取 bean Object bean = map.get(s); // 不存在的话,通过 objectFactory 进行获取 if (Objects.isNull(bean)) { bean = objectFactory.getObject(); // 放入到当前线程的 map 中 map.put(s, bean); } return bean; } @Override public Object remove(String s) { return threadLocal.get().remove(s); } @Override public void registerDestructionCallback(String s, Runnable runnable) { } @Override public Object resolveContextualObject(String s) { return null; } @Override public String getConversationId() { return SCOPE_NAME; } }
然后我们把它进行注册然后使用:
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args); DefaultListableBeanFactory beanFactory = ((DefaultListableBeanFactory) applicationContext.getBeanFactory()); // 注册自定义 Scope beanFactory.registerScope(ThreadScope.SCOPE_NAME, new ThreadScope()); // 测试 for (int i = 0; i < 2; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "==" + beanFactory.getBean(Simple.class)); System.out.println(Thread.currentThread().getName() + "==" + beanFactory.getBean(Simple.class)); }).start(); } } }
看效果,相同线程下获取到的是一样的:
至于 Scope 的原理,我们接下来看看。我们这里主要看自定义 Scope 下,是在获取 Bean 的过程中入场的,以及 Scope 里的作用范围代理又是做什么的,原理是什么,来展开说说。
3 Scope 原理
3.1 自定义 Scope 的原理
对于自定义的 Scope 所带来的影响我们从静态和动态来划分,静态的话比如在解析我们 Bean 生成 BeanDefinition 的时候,我们的自定义的 Scope 会设置到当前 BeanDefinition 的 scope 属性,以及注册我们的自定义 Scope:
动态的影响就是在 Bean 容器获取该 Bean 的时候:
自定义 Scope 的入场就看到这里,接下来我们看看作用域代理是个啥。
3.2 作用域代理原理
首先我们先体验一下,这个东西到底有啥用:
那我们可以做的改变:
(1)每次获取 service 的时候,都重新从容器中获取
(2)就是开启我们的作用域代理
那我们看看开启了作用范围代理的原理。
还是要从 BeanDefiniton 入手,看看解析这个 Bean 的时候,都做了哪些变化,从哪看起呢?涉及到 spring 扫描获取 BeanDefinition 的过程了,我们这里从 org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan 看起:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { // 获取到某个包下的 BeanDefinition Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { // 解析 Scope 注解 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); // 设置上它的 scope candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); // 如果是AbstractBeanDefinition if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } // 如果是一个注解的类,做公共处理 比如 @Lazy @Primary @DependsOn 等注解解析 if (candidate instanceof AnnotatedBeanDefinition) { AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } // 检查是否存在冲突 比如相同的 BeanDefinition重复检查 if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); // 在里面会判断是否需要基于原始的bd去创建一个新的scope代理 BeanDefinition // 如果需要,那么这里返回的definitionHolder就是新的scope代理BeanDefinition,反之就是原始的BeanDefinition definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); // 注册 // 这里需要注意的是,如果上面返回的definitionHolder是一个新的scope代理BeanDefinition,那么原始的bd不就没有被注册吗? // 答案是如果返回的definitionHolder是一个新的scope代理 BeanDefinition,那么原始的bd就会在applyScopedProxyMode方法中被注册 registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; } // protected Class<? extends Annotation> scopeAnnotationType = Scope.class; // 解析 Scope 注解 public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) { ScopeMetadata metadata = new ScopeMetadata(); if (definition instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; // 获取到 @Scope 注解信息 AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor( annDef.getMetadata(), this.scopeAnnotationType); // 获取其中的 scopeName 和作用域代理 if (attributes != null) { metadata.setScopeName(attributes.getString("value")); ScopedProxyMode proxyMode = attributes.getEnum("proxyMode"); if (proxyMode == ScopedProxyMode.DEFAULT) { proxyMode = this.defaultProxyMode; } metadata.setScopedProxyMode(proxyMode); } } return metadata; }
可以看到这里会解析 @Scope 注解信息,并且进入到 AnnotationConfigUtils.applyScopedProxyMode,会根据当前 BeanDefinition 的@Scope 信息,进行 BeanDefinition 的改变,那我们进去看看:
static BeanDefinitionHolder applyScopedProxyMode( ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) { ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode(); // 默认是 ScopedProxyMode.NO 如果没设置作用于代理,那直接返回当前的 definition if (scopedProxyMode.equals(ScopedProxyMode.NO)) { return definition; } // ScopedProxyMode.TARGET_CLASS 是 cglib 代理,proxyTargetClass true=表示用cglib代理,false=则用jdk代理 boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS); return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass); } public static BeanDefinitionHolder createScopedProxy( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry, boolean proxyTargetClass) { return ScopedProxyUtils.createScopedProxy(definitionHolder, registry, proxyTargetClass); } // 这个方法主要就是根据原始的 BeanDefinition 创建一个新的 BeanDefinition public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition, BeanDefinitionRegistry registry, boolean proxyTargetClass) { // 获取原始bd的beanName String originalBeanName = definition.getBeanName(); BeanDefinition targetDefinition = definition.getBeanDefinition(); String targetBeanName = getTargetBeanName(originalBeanName); // Create a scoped proxy definition for the original bean name, // "hiding" the target bean in an internal target definition. // 创建一个新的scope代理的 BeanDefinition 对象 设置的 class 是 ScopedProxyFactoryBean RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName)); proxyDefinition.setOriginatingBeanDefinition(targetDefinition); proxyDefinition.setSource(definition.getSource()); proxyDefinition.setRole(targetDefinition.getRole()); // 设置属性注入时ScopedProxyFactoryBean类中的targetBeanName属性值为scopedTarget + 原始beanName proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName); // true 使用cglib对原始bd进行代理 if (proxyTargetClass) { targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE); // ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here. } else { proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE); } // Copy autowire settings from original bean definition. proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate()); proxyDefinition.setPrimary(targetDefinition.isPrimary()); if (targetDefinition instanceof AbstractBeanDefinition) { proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition); } // The target bean should be ignored in favor of the scoped proxy. targetDefinition.setAutowireCandidate(false); targetDefinition.setPrimary(false); // Register the target bean as separate bean in the factory. // 注册原始的 BeanDefinition registry.registerBeanDefinition(targetBeanName, targetDefinition); // Return the scoped proxy definition as primary bean definition // (potentially an inner bean). return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases()); }
createScopedProxy 方法中,创建了一个新的作用域代理 BeanDefinition 对象,并且主要的是这个 BeanDefinition 中的class类型是ScopedProxyFactoryBean,这个类很明显就是一个FactoryBean,也是我们接下来要看的。
public class ScopedProxyFactoryBean extends ProxyConfig implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean { /** * 目标源对象 */ private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource(); /** * 被代理的bean的名称 */ @Nullable private String targetBeanName; /** * 当前FactoryBean创建的代理对象 */ @Nullable private Object proxy; /** * Create a new ScopedProxyFactoryBean instance. */ public ScopedProxyFactoryBean() { setProxyTargetClass(true); } /** * Set the name of the bean that is to be scoped. */ public void setTargetBeanName(String targetBeanName) { this.targetBeanName = targetBeanName; this.scopedTargetSource.setTargetBeanName(targetBeanName); } @Override public void setBeanFactory(BeanFactory beanFactory) { if (!(beanFactory instanceof ConfigurableBeanFactory)) { throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory); } ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory; // 给目标源设置一个BeanFactory,因为SimpleBeanTargetSource这个目标源需要从BeanFactory中获取被代理对象 this.scopedTargetSource.setBeanFactory(beanFactory); // 创建一个代理工厂 ProxyFactory pf = new ProxyFactory(); // 设置代理配置 pf.copyFrom(this); // 设置目标源 pf.setTargetSource(this.scopedTargetSource); Assert.notNull(this.targetBeanName, "Property 'targetBeanName' is required"); // 从BeanFactory中获取到被代理bean的类型 Class<?> beanType = beanFactory.getType(this.targetBeanName); if (beanType == null) { throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName + "': Target type could not be determined at the time of proxy creation."); } // 使用jdk动态代理 if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) { pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader())); } // 创建ScopedObject实例 ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName()); // 给代理对象添加一个introduction类型的拦截器,并且把scopedObject作为introduction实现委托对象 pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject)); // Add the AopInfrastructureBean marker to indicate that the scoped proxy itself is not subject to auto-proxying! Only its target bean is. pf.addInterface(AopInfrastructureBean.class); // 创建代理对象 this.proxy = pf.getProxy(cbf.getBeanClassLoader()); } @Override public Object getObject() { if (this.proxy == null) { throw new FactoryBeanNotInitializedException(); } return this.proxy; } @Override public Class<?> getObjectType() { if (this.proxy != null) { return this.proxy.getClass(); } return this.scopedTargetSource.getTargetClass(); } @Override public boolean isSingleton() { return true; } }
ScopedProxyFactoryBean 实现了FactoryBean接口,所以我们主要看它的 getObject 方法,在getObject方法中就直接返回了一个proxy对象,而这个proxy对象是在setBeanFactory方法中初始化的,setBeanFactoryBean方法的调用时机在bean的生命周期中是在属性注入这个阶段之后执行的也就是初始化Bean里,我之前 spring 文章中有哈:
那么在 setBeanFactory 方法里,主要是创建了 一个ProxyFactory对象,我们知道这是一个代理工厂对象,通过这个对象的一些API就可以创建出一个代理对象。
ProxyFactory中比较重要的就是设置一个目标源,我们知道一个代理对象首先它需要有一个目标代理类,也就是它代理的对象是谁,而从目标源中就可以获取到目标代理对象。这里设置的具体目标源是SimpleBeanTargetSource,我们可以看下这个目标源:
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource { public SimpleBeanTargetSource() { } public Object getTarget() throws Exception { return this.getBeanFactory().getBean(this.getTargetBeanName()); } }
targetBeanName 就是在构建 BeanDefinition 时设置的。
而ProxyFactory中比较重要的就是设置一个目标源,我们知道一个代理对象首先它需要有一个目标代理类,也就是它代理的对象是谁,而从目标源中就可以获取到目标代理对象。这里设置的具体目标源是SimpleBeanTargetSource,
还有一个重要的就是给 ProxyFactory 添加一个 advice增强器了:
// 创建ScopedObject实例 ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName()); // 给代理对象添加一个introduction类型的拦截器,并且把scopedObject作为introduction实现委托对象 pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
到这里不知道大家晕了没,我们来捋捋,中间的几个重要过程:
有点绕的话,大家可以多调试看看,主要的就是:
(1)开启 Scope 作用范围代理的情况下,会注入两个 BeanDefinition,一个是原始的(但是特别要注意的就是它的 beanName是scopedTarget.+原来的beanName),另外一个新的是 ScopedProxyFactoryBean 类型的(但它的 beanName就是用的我们原来的最初的)也就是说我们依赖注入的其实都是第二个 BeanDefinition 产生的 Bean 这个一定要区别开来
(2)在 Bean 生命周期的 initializeBean 阶段,会进行 ScopedProxyFactoryBean 的 setBeanFactory,来进行代理对象的创建,这里要记住的是 ProxyFactory 的 targetSource 是设置的 ScopedProxyFactoryBean 里的 SimpleBeanTargetSource
(3)当执行对象方法的时候,会进入增强逻辑,拿出当前代理对象的源对象,也就是 SimpleBeanTargetSource,它里的 getTarget() 就会从 Bean 容器中获取 Bean。
4 小结
这个还是有点绕的,多调试几次,细看它 beanName的变化,还有涉及到一点儿代理的创建和执行的基础知识,有理解不对的地方欢迎指正哈。