架构のSpring扩展点(五):如何保证在同一线程内获取的bean是同一对象-自定义Scope

Spring 的扩展点 是Spring易扩展的一个重要体现,熟悉这些扩展点的定义方式,以及其调用时机,不仅成为工作中利器,也能深度理解Spring框架的切入点。

Scope扩展点 

Scope定义

Scope定义了Bean在Spring中的作用范围到底有多大,就像用户单次的访问权限一样,它设定了Bean在应用中可用范围。

Spring中Bean的作用域

scope description
singleton (默认)将单个bean定义作用于每个Spring IoC容器的单个对象实例
prototype 将单个bean定义作用于任意数量的对象实例
request 将单个bean定义的范围扩大到单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的bean实例,该实例是在单个bean定义的后面创建的。只有在感知web的Spring ApplicationContext的上下文中才有效 
session 将单个bean定义作用于HTTP会话的生命周期。只有在感知web的Spring ApplicationContext的上下文中才有效
application 将一个bean定义作用域作用到ServletContext的生命周期。只有在感知web的Spring ApplicationContext的上下文中才有效
websocket 将一个bean定义定义到WebSocket的生命周期。只有在感知web的Spring ApplicationContext的上下文中才有效

自定义Scope

ThreadScope即同一个线程内多次getBean是同一个对象,不同线程之间getBean对象不一样

public class ThreadLocalScope implements Scope {
 
    private final ThreadLocal> local = ThreadLocal.withInitial(HashMap::new);
 
    @Override
    public Object get(String name, ObjectFactory objectFactory) {
        Map map = local.get();
        Object value = map.get(name);
        if(value == null) {
            value = objectFactory.getObject();
            map.put(name, value);
        }
        return value;
    }
 
    @Override
    public Object remove(String name) {
        Map map = local.get();
        return map.remove(name);
    }
 
    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
        // nothing to do
    }
 
    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }
 
    @Override
    public String getConversationId() {
        return Thread.currentThread().getName();
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

定义一个线程Scope对象

@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Scope("thread")public @interface ThreadLocalScopeAnn {

自定义注解

@Configurationpublic class ScopeConfig {     @Bean    public CustomScopeConfigurer registerThreadLocalScope() {        CustomScopeConfigurer configurer = new CustomScopeConfigurer();        configurer.addScope("thread", new ThreadLocalScope());        return configurer;    }     @Bean    @ThreadLocalScopeAnn    public User user() {        return new User();    }

注册Scope

public class Test {
 
 
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScopeConfig.class);
        new Thread(() -> {
            System.out.println(applicationContext.getBean(User.class));
            System.out.println(applicationContext.getBean(User.class));
        }).start();
 
        new Thread(() -> {
            System.out.println(applicationContext.getBean(User.class));
            System.out.println(applicationContext.getBean(User.class));
        }).start();
    }

测试

架构のSpring扩展点(五):如何保证在同一线程内获取的bean是同一对象-自定义Scope_第1张图片

测试结果

工作原理

当@Scope注解作用在类或方法时,Spring容器启动后会为每个bean生成beandifinition,其中getSope()方法就会返回注解中value的值。其底层是在AbstractBeanFactory#doGetBean中进行判断的。

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {

 
    ...
 
  private final Map scopes = new LinkedHashMap<>(8);
    
    ... 
 
    protected  T doGetBean(
                ...
                //单例逻辑
            if (mbd.isSingleton()) {
          sharedInstance = getSingleton(beanName, new ObjectFactory() {
            public Object getObject() throws BeansException {
              try {
                return createBean(beanName, mbd, args);
              }
              catch (BeansException ex) {
                // Explicitly remove instance from singleton cache: It might have been put there
                // eagerly by the creation process, to allow for circular reference resolution.
                // Also remove any beans that received a temporary reference to the bean.
                destroySingleton(beanName);
                throw ex;
              }
            }
          });
          bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }
                
                // 多例逻辑
        else if (mbd.isPrototype()) {
          // It's a prototype -> create a new instance.
          Object prototypeInstance = null;
          try {
            beforePrototypeCreation(beanName);
            prototypeInstance = createBean(beanName, mbd, args);
          }
          finally {
            afterPrototypeCreation(beanName);
          }
          bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
        }
                
                // 其它
        else {
                    // 从beanDefination中获取作用域名称
          String scopeName = mbd.getScope();
                    // 根据作用域名称找到注册的Scope
          final Scope scope = this.scopes.get(scopeName);
          if (scope == null) {
            throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");
          }
          try {
                        // 从作用域中获取实例
            Object scopedInstance = scope.get(beanName, new ObjectFactory() {
              public Object getObject() throws BeansException {
                beforePrototypeCreation(beanName);
                try {
                                    // 创建bean,执行bean的生命周期,因此每次执行返回实例不一样
                  return createBean(beanName, mbd, args);
                }
                finally {
                  afterPrototypeCreation(beanName);
                }
              }
            });
            bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
          }
 
              ...
  }
 
}

AbstractBeanFactory

CustomScopeConfigurer是Spring提供给我们注册作用域的类,它实现了BeanFactoryPostProcessor接口,当Spring容器启动时,会回调postProcessBeanFactory方法,通过调用beanFactory.registerScope方法完成自定义Scope的注册。当然我们也可以自定义一个类实现BeanFactoryPostProcessor类并注册成一个Bean,实现思路是一样的。

public class CustomScopeConfigurer implements BeanFactoryPostProcessor, BeanClassLoaderAware, Ordered {
 
  @Nullable
  private Map scopes;
 
  private int order = Ordered.LOWEST_PRECEDENCE;
 
  @Nullable
  private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
 
 
  /**
     * 设置自定义Scope
   * Specify the custom scopes that are to be registered.
   * 

The keys indicate the scope names (of type String); each value * is expected to be the corresponding custom {@link Scope} instance * or class name. */ public void setScopes(Map scopes) { this.scopes = scopes; } /** * 添加自定义Scope * Add the given scope to this configurer's map of scopes. * @param scopeName the name of the scope * @param scope the scope implementation * @since 4.1.1 */ public void addScope(String scopeName, Scope scope) { if (this.scopes == null) { this.scopes = new LinkedHashMap<>(1); } this.scopes.put(scopeName, scope); } public void setOrder(int order) { this.order = order; } @Override public int getOrder() { return this.order; } @Override public void setBeanClassLoader(@Nullable ClassLoader beanClassLoader) { this.beanClassLoader = beanClassLoader; } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // 通过beanFactory.registerScope方法注册自定义Scope if (this.scopes != null) { this.scopes.forEach((scopeKey, value) -> { if (value instanceof Scope) { beanFactory.registerScope(scopeKey, (Scope) value); } else if (value instanceof Class) { Class scopeClass = (Class) value; Assert.isAssignable(Scope.class, scopeClass, "Invalid scope class"); beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass)); } else if (value instanceof String) { Class scopeClass = ClassUtils.resolveClassName((String) value, this.beanClassLoader); Assert.isAssignable(Scope.class, scopeClass, "Invalid scope class"); beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass)); } else { throw new IllegalArgumentException("Mapped value [" + value + "] for scope key [" + scopeKey + "] is not an instance of required type [" + Scope.class.getName() + "] or a corresponding Class or String value indicating a Scope implementation"); } }); } } }

架构のSpring扩展点(五):如何保证在同一线程内获取的bean是同一对象-自定义Scope_第2张图片

关注公众号:齐疾行者Code

一头爱分享技术的程序猿 ,一个技术与职场的宝藏博主 | 专注Java生态与职场经验分享 ,在这里分享技术、感悟、生活 | 每天进步一点点,和我一起奇迹行者

 近期文章精选

架构のSpring扩展点(二):Bean定义操作-BeanDefinitionRegistryPostProcessor

架构のSpring扩展点(一):上下文创建前的动态处理-ApplicationContextInitializer

架构のSpring扩展点(三):Bean生命周期操作-InstantiationAwareBeanPostProcessor

架构のSpring扩展点(四):Bean初始化时对象自动注入-Aware全解析

认知结构的提升,需要合适的思考方法-架构提升

你可能感兴趣的:(Spring扩展点,架构,spring,java)