spring-解析Scope注解

109 阅读6分钟

java自定义Scope

1、实现Scope接口

public interface Scope {
//从当前的作用域中获取name的对象。如果没有找到对象,那么将使用提供的ObjectFactory创建一个新的对象。
 Object get(String name, ObjectFactory<?> objectFactory);
//从当前的作用域中删除一个命名的对象,并返回这个对象。如果没有找到对象,那么返回null。
 Object remove(String name);
//于注册一个销毁回调,当命名的对象从作用域中移除时,这个回调将被执行。
 void registerDestructionCallback(String name, Runnable callback);
//解析当前作用域上下文中的对象。这通常用于解析一些特殊的、基于当前上下文的对象,例如HttpRequest、Session等。
 Object resolveContextualObject(String key);
//返回当前作用域的唯一标识符。这个ID通常用于日志记录或者用于跟踪在特定作用域中发生的事件。比如sessionId
 String getConversationId();
}

我们自定义实现这个接口,同一个线程里边拿到的值是一样的。

public class ThreadScope implements Scope {
    
   private final ThreadLocal<Map<StringObject>> threadScope = new ThreadLocal<Map<StringObject>>() {
      @Override
      protected Map<StringObjectinitialValue() {
         return new HashMap<>();
      }
   };

   @Override
   public Object get(String name, ObjectFactory<?> objectFactory) {
      Map<StringObject> scope = threadScope.get();
      return scope.computeIfAbsent(name, k -> objectFactory.getObject());
   }
   @Override
   public Object resolveContextualObject(String key) {
      Map<StringObject> scope = threadScope.get();
      return scope.get(key);
   }

   @Override
   public Object remove(String name) {
      Map<StringObject> scope = threadScope.get();
      return scope.remove(name);
   }

   @Override
   public void registerDestructionCallback(String name, Runnable callback) {
      // For this example, we do not execute any action
   }
   
   @Override
   public String getConversationId() {
      return String.valueOf(Thread.currentThread().getId());
   }
}

2、注入到spring

@Configuration
public class MainConfig implements BeanFactoryPostProcessor {
   public static Integer num = 1;
    // 注入容器中
   @Override
   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
      beanFactory.registerScope("threadScope"new ThreadScope());
   }
   @Bean
   @Scope("threadScope")
   public UserService userService() {
      return new UserService("小明" + num++ + "号");
   }
}

3、调用

public static void main(String[] args) {
   AnnotationConfigApplicationContext context =
         new AnnotationConfigApplicationContext(MainConfig.class);
   new Thread(() -> System.out.println(context.getBean("userService").toString() + "-" + context.getBean("userService").toString())).start();
   new Thread(() -> System.out.println(context.getBean("userService").toString())).start();
   new Thread(() -> System.out.println(context.getBean("userService").toString())).start();
}

输出结果如下:

UserService(name=小明1号)
UserService(name=小明2号)-UserService(name=小明2号)
UserService(name=小明3号)

同一个线程拿到的值是一样的,不同的线程拿到的值是不一样。

原理

1、注册

beanFactory.registerScope("threadScope"new ThreadScope());

最终会调用AbstractBeanFactory#registerScope并把我们传入值作为key-value存储到scopes Map集合中。

public void registerScope(String scopeName, Scope scope) {
  //SCOPE_SINGLETON是singleton,SCOPE_PROTOTYPE是prototype
  //禁止我们重写这两个key
   if (SCOPE_SINGLETON.equals(scopeName) || SCOPE_PROTOTYPE.equals(scopeName)) {
      throw new IllegalArgumentException("Cannot replace existing scopes 'singleton' and 'prototype'");
   }
   Scope previous = this.scopes.put(scopeName, scope);
  }

现在我们scopes中有{"threadScope":threadScope}。

2、调用

context.getBean("userService");

去工厂拿bean的时候,是去单例工厂拿的。

/** 单例对象的缓存:从bean名称到bean实例。 */
// 单例池
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

我们这个不是单例single,而是threadScope,从单例工厂工厂拿不到,会走一下逻辑:

//是否是 single
if (mbd.isSingleton()) {
 ...
}
//是否是 prototype
else if (mbd.isPrototype()) {
  ...
}
else {
    //这里才是我们自定义的
   String scopeName = mbd.getScope();
   Scope scope = this.scopes.get(scopeName);
   try {
      Object scopedInstance = scope.get(beanName, () -> {
         beforePrototypeCreation(beanName);
         try {
            return createBean(beanName, mbd, args);
         }
         finally {
            afterPrototypeCreation(beanName);
         }
      });
      beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
   }
}

上边代码解释,我们@Scope("threadScope"),scope的值是"threadScope"

1、判断是否是"single",不是。

2、判断是否是"pototype",也不是。

3、以上都不是,从scopes中取值,取得是上边注册进去的值。

4、调用Scope.get(String name, ObjectFactory<?> objectFactory)并返回。

我们的实现逻辑是:

@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
   Map<StringObject> scope = threadScope.get();
   return scope.computeIfAbsent(name, k -> objectFactory.getObject());
}

从ThreadLoop中获取,如果有直接返回,没有就用ObjectFactory.getObject创建一个,存放到ThreadLoop中并返回。在spring中我们传的是lambda表达式去调用createBean创建一个bean。

整体已理清,总结一下:

实现流程:

1、实现Scope接口。

2、注入spring。实现BeanFactoryPostProcessor重写postProcessBeanFactory方法,在里边调用registerScope(key,Scope)进行注入

源码流程:

1、注入Scope到AbstractBeanFactory.scopes的map中,其中key就是@Scope("xxx")里边的xxx,value是我们重写Scope的实体类。

2、spring中获取写有@Scope("xxx")的bean的时候,因为xxx不是single所以不会存放到单例池中。

3、用xxx从scopes中获取scope的实现类。

4、调用get(xxx,ObjectFactory)。ObjectFactory是个lambda,传入的createBean方法,也就是spring创建bean的方法。这里具体实现逻辑自己定义,上边例子的实现是,先用xxx去ThreadPool获取这个线程里边的值,判断是否存在,如果不存在调用ObjectFactory的方法,然后存放到ThreadPool中并返回,也就是调用创建bean,存放到ThreadPool中,并返回这个bean。

Signle、prototype和Scope接口的关系

Signle和prototype是spring写死了的值。

他们两个不用实现Scope接口,是在spring写死了的逻辑。spring为了扩展,提供了Scope接口,然后注入到scopes Map中,获取bean的时候spring会先去写死了的逻辑中判断singleton和prototype,如果不是的话才会走我们扩展的逻辑。

像我们常见的有:

public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
       ServletContext sc) {
    //request
   beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
    //session
   beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope());
   beanFactory.registerResolvableDependency(ServletRequest.classnew RequestObjectFactory());
   beanFactory.registerResolvableDependency(ServletResponse.classnew ResponseObjectFactory());
   beanFactory.registerResolvableDependency(HttpSession.classnew SessionObjectFactory());
   beanFactory.registerResolvableDependency(WebRequest.classnew WebRequestObjectFactory());
}

一会说明request的流程

@Scope注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
 @AliasFor("scopeName")
 String value() default "";
 
    @AliasFor("value")
 String scopeName() default "";
 
 ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}

value()scopeName():这两个属性是互为别名的,可以互换使用。用于指定bean的作用域。常用的值包括singletonprototyperequestsession等。默认值为空字符串,此时通常会被视为singleton

ScopedProxyMode我们先看个例子。

定义两个bean,一个是单例,一个是原型,我们把原型的bean注入到单例bean中。

@Component
@ToString
@Data
//单例
public class OrderService {
   @Autowired
   private UserService userService;
}
@Component
@Scope(value = "prototype")
public class UserService {
}

目的是想每次获取OrderService bean的时候userService都是不同的。

输出:

public static void main(String[] args) {
   AnnotationConfigApplicationContext context =
         new AnnotationConfigApplicationContext(MainConfig.class);
   System.out.println(context.getBean("userService"));
   System.out.println(context.getBean("userService"));
   System.out.println(context.getBean("orderService"));
   System.out.println(context.getBean("orderService"));
}
com.soundCode.scan.service.UserService@3d24753a
com.soundCode.scan.service.UserService@59a6e353
OrderService(userService=com.soundCode.scan.service.UserService@7a0ac6e3)
OrderService(userService=com.soundCode.scan.service.UserService@7a0ac6e3)

我们发现单例orderService里边的原型userService每次都是一样的。这就会有问题。

我们可以用@LookUp注解来实现,

@Component
@Data
public abstract class OrderService {
   //这个就不要了
   //@Autowired
   //private UserService userService;
   @Lookup
   public abstract UserService getUserService();

   public String toString(){
      return this.getUserService().toString();
   }
}

其中spring会为们创建个动态代理,去实现OderService#getUserService()的方法,这样每次调用getUserService方法都会返回不一样的UserService。

在Scope中,我们也可以用ScopedProxyMode.TARGET_CLASS,来告诉spring这个bean需要创建动态代理。

@Component
@Scope(value = "prototype",proxyMode=ScopedProxyMode.TARGET_CLASS)
public class UserService {
}
//输出
com.soundCode.scan.service.UserService@735f7ae5
com.soundCode.scan.service.UserService@180bc464
OrderService(userService=com.soundCode.scan.service.UserService@1324409e)
OrderService(userService=com.soundCode.scan.service.UserService@2c6a3f77)

这样每次获取单例orderService的时候,UserService都重新生成。

补充上一篇扫描时候判断@Scope是否要生成代理。

下边分析applyScopedProxyMode都做了什么。

definitionHolder =
      AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);

1、判断@Scope scopedProxyMode是否是ScopedProxyMode.TARGET_CLASS,如果是往下走

2、创建一个RootBeanDefinition proxyDefinition,class是ScopedProxyFactoryBean。targetBeanName是scopedTarget.+xxx

proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);

这里属性赋值的时候会给ScopedProxyFactoryBean里边的targetBeanName属性赋值。

3、把原来的beanDefinition用名字scopedTarget.+xxx注入到beanDefinitionMap容器,它依然是原型。

scopedTarget.+xxx

scopedTarget.+xxx

4、返回新创建RootBeanDefinition,并注册到beanDefinitionMap容器,name='xxx',新的beanDefinition就是单例了也是个工厂。

上边最主要逻辑是ScopedProxyFactoryBean,看名字就知道他是个FactoryBean。

public class ScopedProxyFactoryBean extends ProxyConfig
      implements FactoryBean<Object>, BeanFactoryAwareAopInfrastructureBean {
 /** The TargetSource that manages scoping. */
 private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();
 @Nullable
 private String targetBeanName;
 @Nullable
 private Object proxy;
    @Override
 public void setBeanFactory(BeanFactory beanFactory) {
     ProxyFactory pf = new ProxyFactory();
        pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
        pf.setTargetSource(this.scopedTargetSource);
        this.proxy = pf.getProxy(cbf.getBeanClassLoader());
    }
   @Override
 public Class<?> getObjectType() {
  if (this.proxy != null) {
   return this.proxy.getClass();
  }
  Object beanInstance = this.beanFactory.getBean(this.targetBeanName);
  return beanInstance.getClass();
 }
  @Override
 public Object getObject() {
  return this.proxy;
 }
  
}
public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {
    //这个方法就是返回工厂去目标名字的bean。
 public Object getTarget() throws Exception {
  return getBeanFactory().getBean(getTargetBeanName());
 }
}

在spring容器中会注册ScopedProxyFactoryBean单例,执行Aware时候会执行BeanFactoryAware#setBeanFactory方法并创建代理对象。

代理对象getTarget被SimpleBeanTargetSource类重写,当调用getgetTarget方法的时候就会去工厂取名字为scopedTarget.+xxx的bean。

这里其实有个问题,就是每次调用目标方法的时候都会被代理类拦截并调用getTarget方法,如果是原型的的话每次都会生成一个新的对象。

我们注入到OrderService的userService是个代理是ScopedProxyFactoryBean#getObject返回的Proxy。在真正调用里边的方法时候才会调用getTarget方法创建bean。

public enum ScopedProxyMode {
   DEFAULT,
   NO,
   INTERFACES,
  TARGET_CLASS
}

其中NO是默认的,如果是DEFAULT默认会转换成NO。TARGET_CLASS是用cglib代理,INTERFACES是基于jdk代理。