【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的变化,还有涉及到一点儿代理的创建和执行的基础知识,有理解不对的地方欢迎指正哈。

posted @ 2024-05-26 14:48  酷酷-  阅读(680)  评论(0编辑  收藏  举报