写在前面,这里使用的spring-context版本 5.1.3.RELEASE;SpringBoot版本2.2.4.RELEASE
来源 | 说明 |
---|---|
singleton | 默认Spring Bean的作用域,一个BeanFactory有且仅有一个实例 |
prototype | 原型作用域,每次依赖查找和依赖注入生成新Bean对象 |
request | Web环境下,将Spring Bean存储在ServletRequest 上下文中 |
session | Web环境下 将Spring Bean存储在HttpSession中 |
application | Web环境下 将Spring Bean存储在ServletContext中 |
先来看一下singleton、prototype这两种作用域
public class BeanScopeDemo {
@Autowired
@Qualifier("singletonDTO")
private BeanScopeDTO singletonDTO;
@Autowired
@Qualifier("singletonDTO")
private BeanScopeDTO singletonDTO2;
@Autowired
@Qualifier("prototypeDTO")
private BeanScopeDTO prototypeDTO;
@Autowired
@Qualifier("prototypeDTO")
private BeanScopeDTO prototypeDTO2;
@Autowired
private List<BeanScopeDTO> dtoList;
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(BeanScopeDemo.class);
context.refresh();
//依赖查找
scopedBeansByLookup(context);
//依赖注入
scopedBeansByInjection(context);
BeanScopeDemo beanScopeDemo = context.getBean(BeanScopeDemo.class);
System.out.println("beanScopeDemo.dtoList = " + beanScopeDemo.dtoList);
context.close();
}
private static void scopedBeansByLookup(AnnotationConfigApplicationContext applicationContext) {
System.out.println("===========依赖查找=============");
for (int i = 0; i < 3; i++) {
// singletonDTO 是共享 Bean 对象
BeanScopeDTO singletonDTO = applicationContext.getBean("singletonDTO", BeanScopeDTO.class);
System.out.println("singletonDTO = " + singletonDTO);
// prototypeDTO 是每次依赖查找均生成了新的 Bean 对象
BeanScopeDTO prototypeDTO = applicationContext.getBean("prototypeDTO", BeanScopeDTO.class);
System.out.println("prototypeDTO = " + prototypeDTO);
}
}
private static void scopedBeansByInjection(AnnotationConfigApplicationContext applicationContext) {
System.out.println("===========依赖注入=============");
BeanScopeDemo beanScopeDemo = applicationContext.getBean(BeanScopeDemo.class);
System.out.println("beanScopeDemo.singletonDTO = " + beanScopeDemo.singletonDTO);
System.out.println("beanScopeDemo.singletonDTO2 = " + beanScopeDemo.singletonDTO2);
System.out.println("beanScopeDemo.prototypeDTO = " + beanScopeDemo.prototypeDTO);
System.out.println("beanScopeDemo.prototypeDTO2 = " + beanScopeDemo.prototypeDTO2);
System.out.println("===========依赖注入=============");
}
@Bean
public BeanScopeDTO singletonDTO() {
return new BeanScopeDTO();
}
@Bean
@Scope("prototype")
public BeanScopeDTO prototypeDTO() {
return new BeanScopeDTO();
}
public static class BeanScopeDTO implements BeanNameAware, InitializingBean, DisposableBean {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@Override
public void destroy() throws Exception {
System.out.println(beanName + "正在执行销毁操作");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println(beanName + "正在执行初始化操作");
}
}
}
上面的例子中声明了一个对象,并将其分别以不同的作用域注入到IoC容器中,来观察下在依赖注入与依赖查找下对象的特性
输出结果:
singletonDTO正在执行初始化操作
prototypeDTO正在执行初始化操作
prototypeDTO正在执行初始化操作
prototypeDTO正在执行初始化操作
===========依赖查找=============
singletonDTO = com.wojiushiwo.scope.BeanScopeDTO@4a87761d
prototypeDTO正在执行初始化操作
prototypeDTO = com.wojiushiwo.scope.BeanScopeDTO@4c762604
singletonDTO = com.wojiushiwo.scope.BeanScopeDTO@4a87761d
prototypeDTO正在执行初始化操作
prototypeDTO = com.wojiushiwo.scope.BeanScopeDTO@2641e737
singletonDTO = com.wojiushiwo.scope.BeanScopeDTO@4a87761d
prototypeDTO正在执行初始化操作
prototypeDTO = com.wojiushiwo.scope.BeanScopeDTO@727803de
===========依赖注入=============
beanScopeDemo.singletonDTO = com.wojiushiwo.scope.BeanScopeDTO@4a87761d
beanScopeDemo.singletonDTO2 = com.wojiushiwo.scope.BeanScopeDTO@4a87761d
beanScopeDemo.prototypeDTO = com.wojiushiwo.scope.BeanScopeDTO@2f0a87b3
beanScopeDemo.prototypeDTO2 = com.wojiushiwo.scope.BeanScopeDTO@319b92f3
===========依赖注入=============
beanScopeDemo.dtoList = [com.wojiushiwo.scope.BeanScopeDTO@4a87761d, com.wojiushiwo.scope.BeanScopeDTO@66d1af89]
singletonDTO正在执行销毁操作
结论:
从Bean生命周期角度看,单例Bean拥有完整的生命周期,而prototype的Bean则没有完整的生命周期,只进行了初始化操作,而未进行destroy操作。
Singleton Bean 无论依赖查找还是依赖注入,均为同一个对象
Prototype Bean 无论依赖查找还是依赖注入,均为新生成的对象
如果依赖注入集合类型的对象,Singleton Bean 和 Prototype Bean 均会存在一个
因此我们说,Spring 不会维护原型Bean的整个生命周期的,在使用原型的bean时,要注意原型bean的销毁工作,使用不当甚至会导致OOM
那么原型Bean要怎么进行销毁呢?可以参考下面这种方式
在当前配置类中实现DisposableBean,手动执行注入Bean的销毁操作
public class BeanScopeDemo implements DisposableBean {
@Autowired
@Qualifier("singletonDTO")
private BeanScopeDTO singletonDTO;
@Autowired
@Qualifier("singletonDTO")
private BeanScopeDTO singletonDTO2;
@Autowired
@Qualifier("prototypeDTO")
private BeanScopeDTO prototypeDTO;
@Autowired
@Qualifier("prototypeDTO")
private BeanScopeDTO prototypeDTO2;
@Autowired
private List<BeanScopeDTO> dtoList;
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(BeanScopeDemo.class);
context.refresh();
scopedBeansByLookup(context);
scopedBeansByInjection(context);
BeanScopeDemo beanScopeDemo = context.getBean(BeanScopeDemo.class);
System.out.println("beanScopeDemo.dtoList = " + beanScopeDemo.dtoList);
context.close();
}
private static void scopedBeansByLookup(AnnotationConfigApplicationContext applicationContext) {
System.out.println("===========依赖查找=============");
for (int i = 0; i < 3; i++) {
// singletonDTO 是共享 Bean 对象
BeanScopeDTO singletonDTO = applicationContext.getBean("singletonDTO", BeanScopeDTO.class);
System.out.println("singletonDTO = " + singletonDTO);
// prototypeDTO 是每次依赖查找均生成了新的 Bean 对象
BeanScopeDTO prototypeDTO = applicationContext.getBean("prototypeDTO", BeanScopeDTO.class);
System.out.println("prototypeDTO = " + prototypeDTO);
}
}
private static void scopedBeansByInjection(AnnotationConfigApplicationContext applicationContext) {
System.out.println("===========依赖注入=============");
BeanScopeDemo beanScopeDemo = applicationContext.getBean(BeanScopeDemo.class);
System.out.println("beanScopeDemo.singletonDTO = " + beanScopeDemo.singletonDTO);
System.out.println("beanScopeDemo.singletonDTO2 = " + beanScopeDemo.singletonDTO2);
System.out.println("beanScopeDemo.prototypeDTO = " + beanScopeDemo.prototypeDTO);
System.out.println("beanScopeDemo.prototypeDTO2 = " + beanScopeDemo.prototypeDTO2);
System.out.println("===========依赖注入=============");
}
@Bean
public BeanScopeDTO singletonDTO() {
return new BeanScopeDTO();
}
@Bean
@Scope("prototype")
public BeanScopeDTO prototypeDTO() {
return new BeanScopeDTO();
}
//BeanScopeDTO与之前一样 这里省略了
@Override
public void destroy() throws Exception {
System.out.println("当前 BeanScopeDemo Bean 正在销毁中...");
this.singletonDTO.destroy();
this.singletonDTO2.destroy();
this.prototypeDTO.destroy();
this.prototypeDTO2.destroy();
// 获取 BeanDefinition
for (BeanScopeDTO beanScopeDTO : this.dtoList) {
beanScopeDTO.destroy();
}
System.out.println("当前 BeanScopeDemo Bean 销毁完成");
}
}
由于request、session、application作用域与Web有关,这里以SpringBoot Web举例演示
@SpringBootApplication
public class BeanScopeApplication {
public static void main(String[] args) {
SpringApplication.run(BeanScopeApplication.class, args);
}
@Bean
@RequestScope
public User requestUser() {
return new User(3, "requestUser");
}
@Bean
@SessionScope
public User sessionUser() {
return new User(4, "sessionUser");
}
@Bean
@ApplicationScope
public User applicationUser() {
return new User(4, "applicationUser");
}
}
@RestController
@RequestMapping("/api/bean/scope")
public class BeanScopeController {
@Autowired
private User requestUser;
@Autowired
private User sessionUser;
@Autowired
private User applicationUser;
@GetMapping
public void beanScopePrint(){
System.out.println("requestUser==>" + requestUser);
System.out.println("sessionUser==>" + sessionUser);
System.out.println("applicationUser==>" + applicationUser);
System.out.println("====================");
}
}
User类是普通的对象,只是实现了初始化、销毁以及BeanNameAware,这里不再贴出来了
输出结果:
scopedTarget.requestUser正在 初始化
requestUser==>com.wojiushwo.scope.domain.User@69920699
scopedTarget.sessionUser正在 初始化
sessionUser==>com.wojiushwo.scope.domain.User@4d50ec2
scopedTarget.applicationUser正在 初始化
applicationUser==>com.wojiushwo.scope.domain.User@678f3c22
====================
scopedTarget.requestUser正在 销毁
scopedTarget.requestUser正在 初始化
requestUser==>com.wojiushwo.scope.domain.User@4168adbd
scopedTarget.sessionUser正在 初始化
sessionUser==>com.wojiushwo.scope.domain.User@4faf4be0
applicationUser==>com.wojiushwo.scope.domain.User@678f3c22
====================
scopedTarget.requestUser正在 销毁
2022-01-26 14:35:26.065 INFO 39763 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
singletonUser正在 销毁
scopedTarget.sessionUser正在 销毁
scopedTarget.sessionUser正在 销毁
通过多次请求,发现一些特点:
透过源码AbstractBeanFactory#doGetBean
我们可以看出,在getBean时会根据不同scope进行不同处理,截取部分代码看下
//如果是单例
if (mbd.isSingleton()) {
//这时会运用单例对象的缓存,先去缓存中找,找不到再创建 最后再放缓存一份
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
//如果是prototype
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
//从这里看出 每次getBean 均是新创建的一个对象
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
//这个else分支 就是处理request、session、application这些作用域啦
else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
}
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
//这里Scope是个接口,每种作用域对应一个类,如@RequestScope对应RequestScope
//@SessionScope对应SessionScope等等
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
//当从scope域中拿不到缓存Bean时,调用这个lambda方法去创建Bean
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
我们着重看一下Scope接口及其实现,弄清楚request、session、application三个作用域在实现上的不同
public interface Scope {
//这是Scope接口的核心操作,根据name在指定Scope下获取Bean
Object get(String name, ObjectFactory<?> objectFactory);
//在指定Scope下 移除名称为name的Bean 在destroyScopeBean时会被用到,如AbstractBeanFactory#在destroyScopeBean时会被用到
@Nullable
Object remove(String name);
//为指定name的Bean注册销毁时的回调
void registerDestructionCallback(String name, Runnable callback);
//使用key解析上下文对象
@Nullable
Object resolveContextualObject(String key);
//创建会话id
@Nullable
String getConversationId();
}
我们以@RequestScope、@SessionScope来分析下源码
public abstract class AbstractRequestAttributesScope implements Scope {
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
// 这里获取到的是绑定到当前线程上的RequestAttributes
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
//从请求域中尝试获取指定beanName,指定scope的Bean
Object scopedObject = attributes.getAttribute(name, getScope());
//若获取不到指定Bean
if (scopedObject == null) {
//则调用objectFactory.getObject() 去createBean 并将创建的Bean 放到请求域中缓存起来
scopedObject = objectFactory.getObject();
attributes.setAttribute(name, scopedObject, getScope());
Object retrievedObject = attributes.getAttribute(name, getScope());
if (retrievedObject != null) {
// Only proceed with retrieved object if still present (the expected case).
// If it disappeared concurrently, we return our locally created instance.
scopedObject = retrievedObject;
}
}
return scopedObject;
}
//...
//留给子类去实现
protected abstract int getScope();
}
attributes.setAttribute(name, scopedObject, getScope());
这句话是关键,它会根据你的scope,而选择将bean保存到request还是session中
//ServletRequestAttributes
@Override
public void setAttribute(String name, Object value, int scope) {
if (scope == SCOPE_REQUEST) {
if (!isRequestActive()) {
throw new IllegalStateException(
"Cannot set request attribute - request is not active anymore!");
}
this.request.setAttribute(name, value);
}
else {
HttpSession session = obtainSession();
this.sessionAttributesToUpdate.remove(name);
session.setAttribute(name, value);
}
}
public class RequestScope extends AbstractRequestAttributesScope {
@Override
protected int getScope() {
return RequestAttributes.SCOPE_REQUEST;
}
@Override
@Nullable
public String getConversationId() {
return null;
}
}
而RequestScope
类的实现就很简单了,只是配合重写了getScope
方法。
public class SessionScope extends AbstractRequestAttributesScope {
@Override
protected int getScope() {
return RequestAttributes.SCOPE_SESSION;
}
//获取会话Id
@Override
public String getConversationId() {
return RequestContextHolder.currentRequestAttributes().getSessionId();
}
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex();
//这里获取互斥信号量,同一个session,只创建一次指定Bean,随后就从session缓存中取
synchronized (mutex) {
return super.get(name, objectFactory);
}
}
@Override
@Nullable
public Object remove(String name) {
//获取互斥信号量时,会生成或复用一个session,该session会与ServletRequestAttribues关联,在后续获取Bean时会用到
//同一个session下,mutex也会复用,详见WebUtils#getSessionMutex
Object mutex = RequestContextHolder.currentRequestAttributes().getSessionMutex();
synchronized (mutex) {
return super.remove(name);
}
}
}
上面简要地了解了RequestScope
、SessionScope
的实现,现在我们已经清楚了,其实request域下的bean 是存放在HttpServletRequest
中的,而session域下的bean是存放在HttpSession
中的。
由于每一个请求其HttpServletRequest
对象均不同,所以@RequestScope 每次都是新对象。但是同一个会话下Session是共享的(浏览器下每一个tab页代表一个会话,JSESSIONID是不同的),因此@SessionScope
同一个会话下多次请求得到的依然是同一个对象。
其实Scope作用域也有很多用途的,也有不少框架选择扩展它,比如RefreshScope
,它可以实现动态刷新配置属性。
我们也来尝试扩展一个线程级别的Scope,用途是同一个线程在IoC容器中获得的Bean是相同的。
public class ThreadLocalScope implements Scope {
public static final String SCOPE_NAME = "thread-local";
private final NamedThreadLocal<Map<String, Object>> threadLocal = new NamedThreadLocal("thread-local-scope") {
@Override
protected Object initialValue() {
return new HashMap<>();
}
};
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> context = getContext();
Object object = context.get(name);
if (object == null) {
object = objectFactory.getObject();
context.put(name, object);
}
return object;
}
private Map<String, Object> getContext() {
return threadLocal.get();
}
@Override
public Object remove(String name) {
return getContext().remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return getContext().get(key);
}
@Override
public String getConversationId() {
return String.valueOf(Thread.currentThread().getId());
}
}
public class ThreadLocalScopeDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(ThreadLocalScopeDemo.class);
context.addBeanFactoryPostProcessor((beanFactory -> {
beanFactory.registerScope(ThreadLocalScope.SCOPE_NAME, new ThreadLocalScope());
}));
context.refresh();
scopedBeansByLookup(context);
context.close();
}
private static void scopedBeansByLookup(AnnotationConfigApplicationContext applicationContext) {
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(() -> {
// user 是共享 Bean 对象
User user = applicationContext.getBean("user", User.class);
System.out.printf("[Thread id :%d] user = %s%n", Thread.currentThread().getId(), user);
user = applicationContext.getBean("user", User.class);
System.out.printf("[Thread id :%d] user = %s%n", Thread.currentThread().getId(), user);
});
// 启动线程
thread.start();
// 强制线程执行完成
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//声明一个自定义Scope的Bean
@Bean
@Scope(ThreadLocalScope.SCOPE_NAME)
public User user() {
return User.createUser();
}
}
输出结果:
user初始化
user初始化
[Thread id :12] user = com.wojiushiwo.ioc.overview.domain.User@707095f1
[Thread id :12] user = com.wojiushiwo.ioc.overview.domain.User@707095f1
user初始化
[Thread id :13] user = com.wojiushiwo.ioc.overview.domain.User@5ee96074
[Thread id :13] user = com.wojiushiwo.ioc.overview.domain.User@5ee96074
user初始化
[Thread id :14] user = com.wojiushiwo.ioc.overview.domain.User@3f226d64
[Thread id :14] user = com.wojiushiwo.ioc.overview.domain.User@3f226d64
可以看出 同一个线程 得到的Bean是相同的。