#yyds干货盘点# Spring源码三千问Bean的Scope有哪些?scope=request是什么原理?
Posted 老王学源码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#yyds干货盘点# Spring源码三千问Bean的Scope有哪些?scope=request是什么原理?相关的知识,希望对你有一定的参考价值。
@TOC
前言
我们知道 Spring Bean 的 Scope 有多种类型:singleton、prototype、request、session。
Scope | 说明 |
---|---|
singleton | 单例。每次注入都是同一个对象。 |
prototype | 多例。每次注入都是不同的对象。 |
request | 每个 request 请求,注入的都是不同的对象。(针对Web) |
session | 每个 session,注入的都是不同的对象。(针对Web) |
application | 同一个 application下,注入的都是相同的对象。(针对Web) |
singleton 和 prototype 不难理解:
scope=singleton 类型的 bean 是放入了 Spring 的一级缓存的,每次注入的都是缓存中的对象,是同一个对象。
scope=prototype 类型的 bean,每次注入时,都去重新创建出一个 bean,每次注入的都是不同的对象。
那 scope=request 和 scope=session 是如何实现的呢?
要实现每个 request 都不同的话,我们猜想 @Autowired 注入的肯定是一个代理对象, 每次使用时,代理都会先去 request 中获取,获取不到时再去创建一个新的对象?
接下来,我们就分析一下源码,来一探究竟!
版本约定
Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)
正文
Scope 接口的类图
RequestScope 在哪里注册的?
WebApplicationContextUtils#registerWebApplicationScopes()
public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory,
@Nullable ServletContext sc)
// 注册 RequestScope 和 SessionScope
beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope());
beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope());
if (sc != null)
ServletContextScope appScope = new ServletContextScope(sc);
beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope);
// Register as ServletContext attribute, for ContextCleanupListener to detect it.
sc.setAttribute(ServletContextScope.class.getName(), appScope);
beanFactory.registerResolvableDependency(ServletRequest.class, new RequestObjectFactory());
beanFactory.registerResolvableDependency(ServletResponse.class, new ResponseObjectFactory());
beanFactory.registerResolvableDependency(HttpSession.class, new SessionObjectFactory());
beanFactory.registerResolvableDependency(WebRequest.class, new WebRequestObjectFactory());
if (jsfPresent)
FacesDependencyRegistrar.registerFacesDependencies(beanFactory);
Scope 在哪里生效的?
scope 生效的地方是在 bean 加载的时候,AbstractBeanFactory#doGetBean()
时生效的。如下图:
可以看出:
- scope=singleton 是单独的处理逻辑
- scope=prototype 是单独的处理逻辑
- 其他 scope 是另一套单独的处理逻辑,而且都使用了 prototype 的方式在处理
除了 scope=singleton、prototype 是单独的处理逻辑之外,其他类型的 scope 都会通过 Scope#get()
来获取 bean
scope=request 的原理
scope=request 类型的 bean 会通过 RequestScope 来获取 bean 对象。
// AbstractRequestAttributesScope#get()
public Object get(String name, ObjectFactory<?> objectFactory)
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
Object scopedObject = attributes.getAttribute(name, getScope());
if (scopedObject == null)
scopedObject = objectFactory.getObject();
attributes.setAttribute(name, scopedObject, getScope());
// Retrieve object again, registering it for implicit session attribute updates.
// As a bonus, we also allow for potential decoration at the getAttribute level.
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;
可以看出,会先从 RequestAttributes 中获取 bean,如果获取不到,则会通过 ObjectFactory 去获取(其实就是走 createBean 的流程)
每次浏览器重新发起 request 之后,RequestAttributes 都会被清空后重新设置值,这样每次 request 获取到的都是不同的对象。
scope=request 用法举例
@Component
@Scope(value = SCOPE_REQUEST)
//@Scope(value = SCOPE_REQUEST, proxyMode= ScopedProxyMode.TARGET_CLASS)
public class RequestId
private String reqId;
......
测试程序:
@RestController
@SpringBootApplication
public class Application
@Autowired
private RequestId requestId;
@Autowired
private RequestId requestId2;
public static void main(String[] args)
SpringApplication app = new SpringApplication(Application.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
@GetMapping("/status")
public String status()
String ri = ObjectUtils.identityToString(requestId) + "@requestId:" + requestId.getReqId();
String ri2 = ObjectUtils.identityToString(requestId2) + "@requestId:" + requestId2.getReqId();
return ri + "<br/>" + ri2;
启动时会报如下错误:
Caused by: org.springframework.beans.factory.support.ScopeNotActiveException: Error creating bean with name requestId: Scope request 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;
nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread?
If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-5.3.9.jar:5.3.9]
分析错误原因:
注入 RequestId 依赖时会直接调用 Scope#get()
来获取依赖对象,而启动时并没有 RequestAttributes 对象,所以会报错。
解决:
添加 @Scope 添加属性 proxyMode= ScopedProxyMode.TARGET_CLASS
。
访问 http://localhost:8080/status 的返回如下:
com.kvn.scope.RequestId$$EnhancerBySpringCGLIB$$15d18b96@67c69b03@requestId:1d6768c0-0ebb-4bbe-b094-06e2232b8585
com.kvn.scope.RequestId$$EnhancerBySpringCGLIB$$15d18b96@67c69b03@requestId:1d6768c0-0ebb-4bbe-b094-06e2232b8585
分析:
添加了 proxyMode= ScopedProxyMode.TARGET_CLASS 之后,扫描出的 BeanDefinition 中的 class=ScopedProxyFactoryBean,而且是 isSingleton=true,会走单例的创建流程。
通过 ScopedProxyFactoryBean 产生的是一个代理 bean,不会触发 RequestId 的加载。只有第一次使用时,才会触发 bean 的加载。
所以,加上 scope 加上 proxyMode= ScopedProxyMode.TARGET_CLASS 之后,启动就会注入一个代理 bean,就没有问题了。
相当于延迟初始化了。
自定义 scope
Spring 还可以自定义 scope,可以通过 org.springframework.beans.factory.config.CustomScopeConfigurer
来处理。
很少用到这种场景,这里不做展开。
scope 需要先注册,再使用。还可以通过 ConfigurableBeanFactory#registerScope()
来进行注册。
小结
scope 生效的地方是在 bean 加载的时候,调用 AbstractBeanFactory#doGetBean()
时生效的。
scope 的处理分为了三类:
- scope=singleton 是单独的处理逻辑,产生的 bean 会放入一级缓存
- scope=prototype 是单独的处理逻辑,产生的 bean 不会放入缓存
- 其他 scope 是另一套单独的处理逻辑,而且它使用了 prototype 的方式在处理,产生的 bean 不会放入缓存
所以,只有 scope=singleton 类型的 bean 在任何地方注入的目标对象都是相同的。其他 scope 类型的 bean,每注入一次都会重新产生一个对象。
如果本文对你有所帮助,欢迎点赞收藏!
有关 Spring 源码方面的问题欢迎一起交流,备注:51cto (vx: Kevin-Wang001)
博主好课推荐:
课程 | 地址 |
---|---|
Dubbo源码解读——通向高手之路 | https://edu.51cto.com/course/23382.html |
正则表达式基础与提升 | https://edu.51cto.com/course/16391.html |
以上是关于#yyds干货盘点# Spring源码三千问Bean的Scope有哪些?scope=request是什么原理?的主要内容,如果未能解决你的问题,请参考以下文章
#yyds干货盘点#Spring源码三千问@Lazy延迟加载与延迟注入有什么区别?
#yyds干货盘点# Spring源码三千问Bean的Scope有哪些?scope=request是什么原理?
#yyds干货盘点# Spring源码三千问BeanDefinition详解——什么是 RootBeanDefinition?merged bean definition 又是什么鬼?
#yyds干货盘点# Spring源码三千问Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?
#yyds干货盘点# Spring 源码三千问同样是AOP代理bean,为什么@Async标记的bean循环依赖时会报错?