spring-解析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<String, Object>> threadScope = new ThreadLocal<Map<String, Object>>() {
@Override
protected Map<String, Object> initialValue() {
return new HashMap<>();
}
};
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> scope = threadScope.get();
return scope.computeIfAbsent(name, k -> objectFactory.getObject());
}
@Override
public Object resolveContextualObject(String key) {
Map<String, Object> scope = threadScope.get();
return scope.get(key);
}
@Override
public Object remove(String name) {
Map<String, Object> 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<String, Object> 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.class, new RequestObjectFactory());
beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
beanFactory.registerResolvableDependency(WebRequest.class, new 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的作用域。常用的值包括singleton、prototype、request、session等。默认值为空字符串,此时通常会被视为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
4、返回新创建RootBeanDefinition,并注册到beanDefinitionMap容器,name=‘xxx’,新的beanDefinition就是单例了也是个工厂。
上边最主要逻辑是ScopedProxyFactoryBean,看名字就知道他是个FactoryBean。
public class ScopedProxyFactoryBean extends ProxyConfig
implements FactoryBean, BeanFactoryAware, AopInfrastructureBean {
/** 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代理。