当前位置: 首页 > article >正文

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代理。


http://www.kler.cn/a/377444.html

相关文章:

  • 产品经理的重要性
  • leetcode | 88. 合并两个有序数组
  • 基于uniapp和java的电动车智能充电系统软件平台的设计
  • 在线PDF转图片网站
  • windows XP,ReactOS系统3.4 共享映射区(Section)---1
  • 数字信号处理Python示例(5)使用实指数函数仿真PN结二极管的正向特性
  • golang switch v := data.(type)
  • Flarum:简洁而强大的开源论坛软件
  • 活动回顾丨艾体宝《开源软件供应链安全的最佳实践》线下研讨会圆满落幕!
  • 五、SpringBoot3实战(1)
  • docker对nginx.conf进行修改后页面无变化或页面报错
  • 【运动的&足球】足球场地区域图像分割系统源码&数据集全套:改进yolo11-RFAConv
  • 提高交换网络可靠性之端口安全配置
  • 项目自动化构建工具——make与Makefile详解
  • 高效实现SCRM用户管理的最佳实践与策略
  • DB-GPT系列(三):底层大模型设置(开源模型、在线模型)
  • 景联文科技医疗数据处理平台:强化医疗数据标注与管理,推动医疗数字化新篇章
  • Waymo的EMMA给多模态端到端自驾指引了方向
  • 软件(2)
  • Rust 力扣 - 73. 矩阵置零
  • gazebo仿真初学者可以试试这个ros小车
  • 我开源了一个短视频应用(Go+React)|DouTok2.0 项目介绍
  • Golang | Leetcode Golang题解之第528题按权重随机选择
  • springcloud整合sentinel,限流策略持久化到nacos,详细配置案例
  • RabbitMQ幂等性
  • vscode ssh连接autodl失败