文章目录
夯实Spring系列|第十一章:Spring Bean 作用域
本章说明
本章主要讨论 Spring Bean 相关的作用域,重点关注 singleton 和 prototype 两种,其他作用域做简单的介绍。
1.项目环境
- jdk 1.8
- spring 5.2.2.RELEASE
- github 地址:https://github.com/huajiexiewenfeng/thinking-in-spring
- 本章模块:bean-scope
2.Spring Bean 作用域
来源 | 说明 |
---|---|
singleton | 默认 Spring Bean 作用域,一个 Beanfactory 有且仅有一个实例 |
prototype | 原型作用域,每次依赖查找和依赖注入生成新 Bean 对象 |
request | 将 Spring Bean 存储在 ServletRequest 上下文中 |
session | 将 Spring Bean 存储在 HttpSession 中 |
application | 将 Spring Bean 存储在 ServletContext 中 |
只需要关注和掌握 singleton 和 prototype,对应设计模式里面的单例模式和原型模式。
request、session、application 主要用于页面渲染,比如 JSP、Velocity、Freemaker 等模板引擎,在现在的 web 开发中已经慢慢的转向前后端分离,模板引擎技术已经慢慢的边缘化了。
3.“singleton” Bean作用域
Spring 官网配图:
从设计模式的角度,单例模式不论是“懒汉式”还是“饿汉式”,其实最主要的作用是保证对象是唯一的。
在 JVM 的层面来说,我们常用静态变量做单例,每个类对应一个 ClassLoader,ClassLoader 在 load Class 的时候,会加载这个类的静态信息,在同一个 ClassLoader 中,单例对象是唯一的。
在 Spring 的场景中,一个 Bean 对象对应一个应用上下文,或者说 Spring IoC 的 BeanFactory 。通常一个 Bean 默认作用域就是 singleton
,不需要配置,换言之,在同一个 BeanFactory 中,Bean 对象只有一个。
从上面的图可以看到,三个 bean 配置都不同,但是 ref="accountDao"
,都指向同一个 bean,每次进行属性的注入,都是同一个共享实例。
Bean 的作用域,并不是指的所有的 Bean,而是指的我们 BeanDefinition 。
BeanDefinition 源码:
singleton:shared instance 共享的实例
prototype:independent instance 独立的实例
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
...
/**
* Return whether this a <b>Singleton</b>, with a single, shared instance
* returned on all calls.
* @see #SCOPE_SINGLETON
*/
boolean isSingleton();
/**
* Return whether this a <b>Prototype</b>, with an independent instance
* returned for each call.
* @since 3.0
* @see #SCOPE_PROTOTYPE
*/
boolean isPrototype();
通过这个两个方法可以判断一个 BeanDefinition 是否是单例或者原型,但是并没有 request、session、application 相关的方法,侧面说明这三种不需要过多关注。
singleton 和 prototype 不能简单的说是互斥的关系,因为从接口的角度看,两个方法可以同时存在。
4.“prototype” Bean作用域
Spring官网配图:
这个图和上面的 singleton 的图比较,每次进行属性注入都会产生一个新的实例,下面我们通过示例来验证这个结论。
4.1 依赖查找示例
- 分别定义两个 Bean,一个 singleton,一个 prototype。
- getBean() 依赖查找,循环三次看每次取到的对象是否相同
- 使用
System.nanoTime()
系统时间来定义 user 的 id,以便区别 bean 的实例
/**
* Bean 作用域
*/
public class BeanScopeDemo{
@Bean
//默认是singleton
public static User singletonUser() {
return createUser(System.nanoTime());
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static User prototypeUser() {
return createUser(System.nanoTime());
}
private static User createUser(Long id) {
User user = new User();
user.setId(id);
return user;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(BeanScopeDemo.class);
applicationContext.refresh();
scopeBeanByLookup(applicationContext);
applicationContext.close();
}
private static void scopeBeanByLookup(AnnotationConfigApplicationContext applicationContext) {
for (int i = 0; i < 3; i++) {
User singletonUser = applicationContext.getBean("singletonUser", User.class);
System.out.println("singletonUser===" + singletonUser);
User prototypeUser = applicationContext.getBean("prototypeUser", User.class);
System.out.println("prototypeUser===" + prototypeUser);
}
}
}
执行结果:
singletonUser===User{beanName='singletonUser', id=49554699579710, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser===User{beanName='prototypeUser', id=49554786687935, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
singletonUser===User{beanName='singletonUser', id=49554699579710, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser===User{beanName='prototypeUser', id=49554788843663, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
singletonUser===User{beanName='singletonUser', id=49554699579710, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser===User{beanName='prototypeUser', id=49554789736982, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
每次依赖查找,singleton 的 id=49554699579710 相同,表示都是同一个对象;但是 prototype 的 id 都不相同,表示生成了三个对象。
4.2 依赖注入示例
在上面 demo 的基础上,增加 4 个属性,使用前面章节讲到的限定注入使用 @Qualifier("singletonUser")
将 singleton 对象注入到 singletonUser1 和 singletonUser2 中, @Qualifier("prototypeUser")
将 prototype 对象注入到 prototypeUser1 和 prototypeUser2 中。
/**
* Bean 作用域
*/
public class BeanScopeDemo{
@Bean
//默认是singleton
public static User singletonUser() {
return createUser(System.nanoTime());
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static User prototypeUser() {
return createUser(System.nanoTime());
}
private static User createUser(Long id) {
User user = new User();
user.setId(id);
return user;
}
@Autowired
@Qualifier("singletonUser")
private User singletonUser1;
@Autowired
@Qualifier("singletonUser")
private User singletonUser2;
@Autowired
@Qualifier("prototypeUser")
private User prototypeUser1;
@Autowired
@Qualifier("prototypeUser")
private User prototypeUser2;
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(BeanScopeDemo.class);
applicationContext.refresh();
//scopeBeanByLookup(applicationContext);
scopeBeanByInjection(applicationContext);
applicationContext.close();
}
private static void scopeBeanByLookup(AnnotationConfigApplicationContext applicationContext) {
System.out.println("======依赖查找======");
for (int i = 0; i < 3; i++) {
User singletonUser = applicationContext.getBean("singletonUser", User.class);
System.out.println("singletonUser===" + singletonUser);
User prototypeUser = applicationContext.getBean("prototypeUser", User.class);
System.out.println("prototypeUser===" + prototypeUser);
}
}
private static void scopeBeanByInjection(AnnotationConfigApplicationContext applicationContext) {
System.out.println("======依赖注入======");
BeanScopeDemo demo = applicationContext.getBean(BeanScopeDemo.class);
System.out.println(demo.singletonUser1);
System.out.println(demo.singletonUser2);
System.out.println(demo.prototypeUser1);
System.out.println(demo.prototypeUser2);
}
}
执行结果:
User{beanName='singletonUser', id=50280183089239, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
User{beanName='singletonUser', id=50280183089239, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
User{beanName='prototypeUser', id=50280199862153, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
User{beanName='prototypeUser', id=50280203148444, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
结果和依赖查找一样,singleton 共享一个对象,而 prototype 每次产生一个新的对象。
由此可以得出结论
-
Singleton Bean 无论依赖注入还是依赖查找,都是同一个对象
-
Prototype Bean 无论依赖注入还是依赖查找,都会生成一个新的对象
如果是集合的情况下,又是那种情况?
增加属性
@Autowired
private Map<String, User> users;
修改方法 scopeBeanByInjection()
private static void scopeBeanByInjection(AnnotationConfigApplicationContext applicationContext) {
System.out.println("======依赖注入======");
BeanScopeDemo demo = applicationContext.getBean(BeanScopeDemo.class);
System.out.println(demo.singletonUser1);
System.out.println(demo.singletonUser2);
System.out.println(demo.prototypeUser1);
System.out.println(demo.prototypeUser2);
System.out.println("======集合======");
demo.users.entrySet().stream().forEach(System.out::println);
}
执行结果:
======依赖查找======
singletonUser===User{beanName='singletonUser', id=51734007203786, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser===User{beanName='prototypeUser', id=51734108916171, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
singletonUser===User{beanName='singletonUser', id=51734007203786, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser===User{beanName='prototypeUser', id=51734111262168, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
singletonUser===User{beanName='singletonUser', id=51734007203786, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser===User{beanName='prototypeUser', id=51734112247463, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
======依赖注入======
User{beanName='singletonUser', id=51734007203786, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
User{beanName='singletonUser', id=51734007203786, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
User{beanName='prototypeUser', id=51734029089892, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
User{beanName='prototypeUser', id=51734032339472, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
======集合======
singletonUser=User{beanName='singletonUser', id=51734007203786, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser=User{beanName='prototypeUser', id=51734045998600, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
从结果可得到结论
从依赖查找、依赖注入、到后面的集合依赖注入,singleton bean 只生成了一个对象(id:51734007203786),而 prototype 每次都会生成一个新的对象。
但是依赖注入集合对象,情况又有点不同,Singleton Bean 和 Prototype Bean 都只会存在一个对象,而这个集合中的 Prototype Bean 有别于其他地方的依赖注入
4.3 注意事项
- Spring 容器没有办法管理 prototype Bean 的完整生命周期,也没有办法记录实例的存在。销毁回调方法将不会执行,可以利用 BeanPostProcessor 进行清扫工作。
我们先修改 User.class
- 增加两个生命周期回调方法
- 初始化方法
- 销毁方法
- 实现了 BeanNameAware 接口回调来获取 BeanName,方便打印。
/**
* 用户类
*/
public class User implements BeanNameAware {
private String beanName;
...
@PostConstruct
public void init() {
System.out.println(beanName + "用户对象初始化...");
}
@PreDestroy
public void destroy() {
System.out.println(beanName + "用户对象销毁...");
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
}
执行结果
singletonUser用户对象初始化...
prototypeUser用户对象初始化...
prototypeUser用户对象初始化...
prototypeUser用户对象初始化...
======依赖查找======
singletonUser===User{beanName='singletonUser', id=55732801895272, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser用户对象初始化...
prototypeUser===User{beanName='prototypeUser', id=55732898846975, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
singletonUser===User{beanName='singletonUser', id=55732801895272, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser用户对象初始化...
prototypeUser===User{beanName='prototypeUser', id=55732901924444, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
singletonUser===User{beanName='singletonUser', id=55732801895272, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser用户对象初始化...
prototypeUser===User{beanName='prototypeUser', id=55732902592755, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
singletonUser用户对象销毁...
从结果可得到结论
Singleton Bean 只会初始化一次,而 Prototype Bean 每次都会初始化,不过仅 Singleton Bean 会执行销毁方法回调。
4.4 增加销毁生命周期
- 第一种可以通过
addBeanPostProcessor
实现,在 bean 初始化之后进行一些销毁的处理(但是不建议这么做)。 - 第二种可以通过实现
DisposableBean
接口,重写destroy()
方法实现。
/**
* Bean 作用域
*/
public class BeanScopeDemo implements DisposableBean{
@Bean
//默认是singleton
public static User singletonUser() {
return createUser(System.nanoTime());
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static User prototypeUser() {
return createUser(System.nanoTime());
}
private static User createUser(Long id) {
User user = new User();
user.setId(id);
return user;
}
@Autowired
@Qualifier("singletonUser")
private User singletonUser1;
@Autowired
@Qualifier("singletonUser")
private User singletonUser2;
@Autowired
@Qualifier("prototypeUser")
private User prototypeUser1;
@Autowired
@Qualifier("prototypeUser")
private User prototypeUser2;
@Autowired
private Map<String, User> users;
@Autowired
private ConfigurableListableBeanFactory beanFactory;
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(BeanScopeDemo.class);
//增加生命周期管理
applicationContext.addBeanFactoryPostProcessor(beanFactory -> {
beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.printf("%s Bean 名称:%s 在初始化后回调...%n", bean.getClass().getName(), beanName);
return bean;
}
});
});
applicationContext.refresh();
//结论1:
// Singleton Bean 无论依赖注入还是依赖查找,都是同一个对象
// Prototype Bean 无论依赖注入还是依赖查找,都会生成一个新的对象
//结论2:
//如果依赖注入集合对象,Singleton Bean 和 Prototype Bean 都只会存在一个对象
//Prototype Bean 有别于其他地方的依赖注入
//结论3:
//无论是 Singleton 还是 Prototype Bean 均会执行初始化方法回调
//不过仅 Singleton Bean 会执行销毁方法回调
scopeBeanByLookup(applicationContext);
scopeBeanByInjection(applicationContext);
applicationContext.close();
}
private static void scopeBeanByLookup(AnnotationConfigApplicationContext applicationContext) {
System.out.println("======依赖查找======");
for (int i = 0; i < 3; i++) {
User singletonUser = applicationContext.getBean("singletonUser", User.class);
System.out.println("singletonUser===" + singletonUser);
User prototypeUser = applicationContext.getBean("prototypeUser", User.class);
System.out.println("prototypeUser===" + prototypeUser);
}
}
private static void scopeBeanByInjection(AnnotationConfigApplicationContext applicationContext) {
System.out.println("======依赖注入======");
BeanScopeDemo demo = applicationContext.getBean(BeanScopeDemo.class);
System.out.println(demo.singletonUser1);
System.out.println(demo.singletonUser2);
System.out.println(demo.prototypeUser1);
System.out.println(demo.prototypeUser2);
System.out.println("======集合======");
demo.users.entrySet().stream().forEach(System.out::println);
}
@Override
public void destroy() throws Exception {
System.out.println("当前 BeanScopeDemo Bean 正在销毁中...");
this.prototypeUser1.destroy();
this.prototypeUser2.destroy();
// 销毁集合中的 Prototype Bean
for (Map.Entry<String,User> entry:this.users.entrySet()){
String beanName = entry.getKey();
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
// 判断 beanDefinition 是否是原型模式,因为 Singleton Bean 会自己进行销毁
if(beanDefinition.isPrototype()){
User user = entry.getValue();
user.destroy();
}
}
}
}
执行结果:
org.springframework.context.event.EventListenerMethodProcessor Bean 名称:org.springframework.context.event.internalEventListenerProcessor 在初始化后回调...
org.springframework.context.event.DefaultEventListenerFactory Bean 名称:org.springframework.context.event.internalEventListenerFactory 在初始化后回调...
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor Bean 名称:org.springframework.context.annotation.internalAutowiredAnnotationProcessor 在初始化后回调...
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor Bean 名称:org.springframework.context.annotation.internalCommonAnnotationProcessor 在初始化后回调...
singletonUser用户对象初始化...
com.huajie.thinking.in.spring.ioc.overview.domain.User Bean 名称:singletonUser 在初始化后回调...
prototypeUser用户对象初始化...
com.huajie.thinking.in.spring.ioc.overview.domain.User Bean 名称:prototypeUser 在初始化后回调...
prototypeUser用户对象初始化...
com.huajie.thinking.in.spring.ioc.overview.domain.User Bean 名称:prototypeUser 在初始化后回调...
prototypeUser用户对象初始化...
com.huajie.thinking.in.spring.ioc.overview.domain.User Bean 名称:prototypeUser 在初始化后回调...
com.huajie.thinking.in.spring.bean.scope.BeanScopeDemo Bean 名称:beanScopeDemo 在初始化后回调...
======依赖查找======
singletonUser===User{beanName='singletonUser', id=1450625753231, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser用户对象初始化...
com.huajie.thinking.in.spring.ioc.overview.domain.User Bean 名称:prototypeUser 在初始化后回调...
prototypeUser===User{beanName='prototypeUser', id=1450722835202, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
singletonUser===User{beanName='singletonUser', id=1450625753231, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser用户对象初始化...
com.huajie.thinking.in.spring.ioc.overview.domain.User Bean 名称:prototypeUser 在初始化后回调...
prototypeUser===User{beanName='prototypeUser', id=1450724832241, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
singletonUser===User{beanName='singletonUser', id=1450625753231, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser用户对象初始化...
com.huajie.thinking.in.spring.ioc.overview.domain.User Bean 名称:prototypeUser 在初始化后回调...
prototypeUser===User{beanName='prototypeUser', id=1450725698322, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
======依赖注入======
User{beanName='singletonUser', id=1450625753231, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
User{beanName='singletonUser', id=1450625753231, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
User{beanName='prototypeUser', id=1450652613973, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
User{beanName='prototypeUser', id=1450655129318, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
======集合======
singletonUser=User{beanName='singletonUser', id=1450625753231, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
prototypeUser=User{beanName='prototypeUser', id=1450663002600, name='null', age=null, configFileReource=null, city=null, cities=null, lifeCities=null}
当前 BeanScopeDemo Bean 正在销毁中...
prototypeUser用户对象销毁...
prototypeUser用户对象销毁...
prototypeUser用户对象销毁...
singletonUser用户对象销毁...
可以看到在 BeanScopeDemo 销毁的时候,Prototype Bean 也会一起进行销毁。
5.“request” Bean作用域
配置
- XML - <bean class="…" scope=“request” …/>
- Java 注解 -@RequestScope 或者 Scope(WebApplicationContext.SCOPE_REQUEST)
实现
- API - RequestScope
源码位置:org.springframework.web.context.request.RequestScope
5.1 示例
新建 WebConfiguration 类
设置 User Bean 作用域为 request
@Configuration
@EnableWebMvc
public class WebConfiguration {
@Bean
@RequestScope
public User user(){
return User.createUser("web-xwf");
}
}
调用方式还是依赖注入
@Controller
public class IndexController {
@Autowired
private User user;// CGLIB 提升后的代理对象(始终不变)
public String index(Model model){
model.addAttribute("user",user);
return "index";
}
}
注意此示例需要在 web 环境下,我们这里就不做演示了,直接说结论。
当前端页面渲染时,我们说的对象是新生成的对象(JSP页面中的引用对象),每个请求都会生产新的对象。
但是创建代理对象或者创建新生对象,它实际上是一个被 CGLIB 提升后的一个代理对象,这个代理对象本身它始终是一个不变的,而在前端渲染的对象每次都是变化的。
6.“session” Bean作用域
配置
- XML - <bean class="…" scope=“session” …/>
- Java 注解 -@SessionScope 或者 Scope(WebApplicationContext.SCOPE_SESSION)
实现
- API - SessionScope
源码位置:org.springframework.web.context.request.SessionScope
源码中的 get() 方法操作
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex();
synchronized (mutex) {
return super.get(name, objectFactory);
}
}
使用互斥锁,为了防止以下场景
一个用户同一个浏览器,打开多个 tab 窗口,打开的页面中使用 Ajax 循环发送请求,由于不同的 tab 之前,cookie 是共用的,这时候无法保证操作的顺序性;前后直接就会产生数据不一致的情况。
使用 cookie 记录 jsessionid 用来进行用户跟踪,因为涉及到同步,性能会有一定的损耗
和 Request 实现的区别
- 每个 Request 对应一个线程,线程和线程之间它的作用域,所以间接的就隔开了。
- 前端渲染的对象每次都是同一个对象
使用方式:
@Configuration
@EnableWebMvc
public class WebConfiguration {
@Bean
@SessionScope
public User user(){
return User.createUser("web-xwf");
}
}
7.“application” Bean作用域
配置
- XML - <bean class="…" scope=“application” …/>
- Java 注解 -@ApplicationScope 或者 Scope(WebApplicationContext.SCOPE_APPLICATION)
实现
- API - ServletContextScope
- scopedTarget
使用方式:
@Configuration
@EnableWebMvc
public class WebConfiguration {
@Bean
@ApplicationScope
public User user(){
return User.createUser("web-xwf");
}
}
需要注意的是在 jsp 中使用的时候也可以 ${applicationScope['scopedTarget.user'].name}
这种 EL 的表达式,因为 ServletContext 上下文中默认新生成对象的名称是 scopedTarget.user,并非 user。
8.自定义 Bean 作用域
实现 Scope
- org.springframework.beans.factory.config.Scope
注册 Scope
- API - org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope
- 配置
实现一个 ThreadLocal 级别的 Scope
public class ThreadLocalScope implements Scope {
public static final String SCOPE_NAME = "thread-local=scope";
private NamedThreadLocal<Map<String, Object>> threadLocal = new NamedThreadLocal<Map<String, Object>>(SCOPE_NAME) {
//初始化
public Map<String, Object> initialValue() {
return new HashMap<>();
}
};
private Map<String, Object> getContext() {
return this.threadLocal.get();
}
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> context = getContext();
Object object = context.get(name);
if (null == object) {
object = objectFactory.getObject();
context.put(name, object);
}
return object;
}
@Override
public Object remove(String name) {
return getContext().remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
// TODO
}
@Override
public Object resolveContextualObject(String key) {
return getContext().get(key);
}
@Override
public String getConversationId() {
Thread thread = Thread.currentThread();
return String.valueOf(thread.getId());
}
}
调用测试
public class ThreadLocalScopeDemo {
@Bean
@Scope(SCOPE_NAME)
public User user() {
return createUser(System.nanoTime());
}
private static User createUser(Long id) {
User user = new User();
user.setId(id);
return user;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(ThreadLocalScopeDemo.class);
applicationContext.addBeanFactoryPostProcessor(beanFactory -> {
beanFactory.registerScope(SCOPE_NAME, new ThreadLocalScope());
});
applicationContext.refresh();
scopeBeanByLookup(applicationContext);
// applicationContext.close();
}
private static void scopeBeanByLookup(AnnotationConfigApplicationContext applicationContext) {
for (int i = 0; i < 3; i++) {
new Thread(new MyThread(applicationContext)).start();
}
}
static class MyThread implements Runnable {
private ApplicationContext context;
public MyThread(ApplicationContext context) {
this.context = context;
}
@Override
public void run() {
for (int i = 0; i <3 ; i++) {
User user = context.getBean(User.class);
System.out.println(Thread.currentThread().getName()+"===scope-user===" + user);
}
}
}
}
测试结果
user用户对象初始化...
user用户对象初始化...
Thread-1===scope-user===User{id=25427297277698, name='null', age=null, configFileReource=null, city=null, cities=null}
user用户对象初始化...
Thread-2===scope-user===User{id=25427297278093, name='null', age=null, configFileReource=null, city=null, cities=null}
Thread-2===scope-user===User{id=25427297278093, name='null', age=null, configFileReource=null, city=null, cities=null}
Thread-2===scope-user===User{id=25427297278093, name='null', age=null, configFileReource=null, city=null, cities=null}
Thread-1===scope-user===User{id=25427297277698, name='null', age=null, configFileReource=null, city=null, cities=null}
Thread-1===scope-user===User{id=25427297277698, name='null', age=null, configFileReource=null, city=null, cities=null}
Thread-3===scope-user===User{id=25427297357437, name='null', age=null, configFileReource=null, city=null, cities=null}
Thread-3===scope-user===User{id=25427297357437, name='null', age=null, configFileReource=null, city=null, cities=null}
Thread-3===scope-user===User{id=25427297357437, name='null', age=null, configFileReource=null, city=null, cities=null}
可以看到同一个线程,每次拿到的对象都是相同的,不同的线程之前对象是不同的。
9.自定义作用域的应用
Spring Cloud 中的 RefreshScope
注解信息:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {
/**
* @see Scope#proxyMode()
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
源码位置:org.springframework.cloud.context.scope.refresh.RefreshScope
当配置 Bean 需要刷新的时候,类中有一个 refreshAll()
方法,这个方法会发送一个刷新的事件。
@ManagedOperation(description = "Dispose of the current instance of all beans in this scope and force a refresh on next method execution.")
public void refreshAll() {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
那么必然有一个对应的监听方法来更新配置。
过滤方法,判断哪些 Bean 作用域是 refresh 。
private void eagerlyInitialize() {
for (String name : this.context.getBeanDefinitionNames()) {
BeanDefinition definition = this.registry.getBeanDefinition(name);
if (this.getName().equals(definition.getScope())
&& !definition.isLazyInit()) {
Object bean = this.context.getBean(name);
if (bean != null) {
bean.getClass();
}
}
}
}
10.面试题
10.1 Spring 内建的 Bean 作用域有几种?
设计模式 singleton、prototype
web场景 request、session、application 以及 websocket(spring 后期加入)
10.2 singleton Bean 是否在一个应用是唯一的?
否,singleton bean 仅在当前的 spring ioc 容器(BeanFactory)中是单例对象,如果有多个应用上下文,比如层次性上下文。
如果一个静态字段在 JVM 中是不是唯一的?
一个静态字段对应的 ClassLoader是唯一的,但是一个应用可以有多个 ClassLoader。ClassLoader 之间是互相隔离的。
10.3 “application” Bean 是否被其他方案代替?
可以,实际上,“application” Bean 与 “singleton” Bean 没有本质的区别,因为如果想在一个应用上下文中需要使用一个共享的唯一的 bean,直接使用 "singleton " Bean 就可以了。
11.参考
- 极客时间-小马哥《小马哥讲Spring核心编程思想》