Bean的Scope有哪些?scope=request是什么原理?

  • 前言
  • 版本约定
  • 正文
    • Scope 接口的类图
    • RequestScope 在哪里注册的?
    • Scope 在哪里生效的?
    • scope=request 的原理
    • scope=request 用法举例
    • 自定义 scope
  • 小结
  • SpringIoC源码视频讲解:

前言

我们知道 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 和 SessionScopebeanFactory.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);}
}

上面的源码中,可以发现,ServletRequest、ServletResponse、HttpSession、WebRequest 都被注册成了 ResolvableDependency,所以,我们可以通过 @Autowired/@Resource 注入这些对象。
关于 ResolvableDependency 的讲解请戳:【Spring源码三千问】BeanDefinition注册、Bean注册、Dependency注册有什么区别?

Scope 在哪里生效的?

scope 生效的地方是在 bean 加载的时候,AbstractBeanFactory#doGetBean() 时生效的。如下图:

可以看出:

  1. scope=singleton 是单独的处理逻辑
  2. scope=prototype 是单独的处理逻辑
  3. 其他 scope 是另一套单独的处理逻辑,而且都使用了 prototype 的方式在处理

scope=singleton 产生的 bean 才会放入到 一级缓存中。scope=prototype或者其他 scope 类型的 bean 是不会放入到一级缓存中的。
也就是说,scope=prototype、request、session 等类型的 bean,每次被注入时,bean 对象都是重新产生的。
这是重点!!!要记住哦

除了 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 获取到的都是不同的对象。

requestAttributesHolder 是 ThreadLocal 类型的,能够做到线程间的隔离

private static final ThreadLocal<RequestAttributes> requestAttributesHolder =new NamedThreadLocal<>("Request attributes");

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 {@Autowiredprivate RequestId requestId;@Autowiredprivate 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 的处理分为了三类:

  1. scope=singleton 是单独的处理逻辑,产生的 bean 会放入一级缓存
  2. scope=prototype 是单独的处理逻辑,产生的 bean 不会放入缓存
  3. 其他 scope 是另一套单独的处理逻辑,而且它使用了 prototype 的方式在处理,产生的 bean 不会放入缓存

所以,只有 scope=singleton 类型的 bean 在任何地方注入的目标对象都是相同的。其他 scope 类型的 bean,每注入一次都会重新产生一个对象。

为什么说"注入的目标对象都是相同的",而不是说"注入的对象都是相同的"?
答:因为 scope=singleton 类型的 bean 有可能被 @Lazy 进行标记,这时注入的就是一个代理 bean。没有使用 @Lazy 的地方就可能不是代理 bean。
AOP 也会使 singleton 类型的 bean 产生代理。
这些情况下,注入的对象可能是不同的,但是注入的最底层的目标对象是相同的。


SpringIoC源码视频讲解:

课程 地址
SpringIoC源码解读由浅入深 https://edu.51cto.com/sd/68e86

如果本文对你有所帮助,欢迎点赞收藏!

源码测试工程下载:
老王读Spring IoC源码分析&测试代码下载
老王读Spring AOP源码分析&测试代码下载

公众号后台回复:下载IoC 或者 下载AOP 可以免费下载源码测试工程…

文章,请关注公众号: 老王学源码


系列博文:
【老王读Spring IoC-0】Spring IoC 引入
【老王读Spring IoC-1】IoC 之控制反转引入
【老王读Spring IoC-2】IoC 之 BeanDefinition 扫描注册
【老王读Spring IoC-3】Spring bean 的创建过程
【老王读Spring IoC-4】IoC 之依赖注入原理
【老王读Spring IoC-5】Spring IoC 小结——控制反转、依赖注入

相关阅读:
【Spring源码三千问】@Resource 与 @Autowired 的区别
【Spring源码三千问】bean name 的生成规则
【Spring源码三千问】BeanDefinition详细分析
【Spring源码三千问】Spring 是怎样解决循环依赖问题的?
【Spring源码三千问】哪些循环依赖问题Spring解决不了?
【Spring源码三千问】@Lazy为什么可以解决特殊的循环依赖问题?
【Spring源码三千问】BeanDefinition注册、Bean注册、Dependency注册有什么区别?
【Spring源码三千问】Bean的Scope有哪些?scope=request是什么原理?
【Spring源码三千问】为什么要用三级缓存来解决循环依赖问题?二级缓存行不行?一级缓存行不行?

【Spring源码三千问】Bean的Scope有哪些?scope=request是什么原理?相关推荐

  1. 【Spring源码三千问】Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?

    Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy? 前言 版本约定 正文 例子测试 结论分析 proxyTargetClass 标识的校正 哪些接口不是 Reas ...

  2. Spring源码分析系列——bean创建过程分析(三)——工厂方法创建bean

    前言 spring创建bean的方式 测试代码准备 createBeanInstance()方法分析 instantiateUsingFactoryMethod()方法分析 总结 spring创建be ...

  3. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  4. Spring源码阅读之bean对象的创建过程

    Spring源码阅读之bean对象的创建过程 ​ Spring是通过IOC容器来管理对象的,该容器不仅仅只是帮我们创建了对象那么简单,它负责了对象的整个生命周期-创建.装配.销毁.这种方式成为控制反转 ...

  5. Spring源码分析:Bean加载流程概览及配置文件读取

    很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已 ...

  6. 【Spring源码】讲讲Bean的生命周期

    1.前言 面试官:"看过Spring源码吧,简单说说Spring中Bean的生命周期" 大神仙:"基本生命周期会经历实例化 -> 属性赋值 -> 初始化 -& ...

  7. 我该如何学习spring源码以及解析bean定义的注册

    如何学习spring源码 前言 本文属于spring源码解析的系列文章之一,文章主要是介绍如何学习spring的源码,希望能够最大限度的帮助到有需要的人.文章总体难度不大,但比较繁重,学习时一定要耐住 ...

  8. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  9. beaninfo详解源码解析 java_【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

最新文章

  1. 怎么用计算机算p a,老师,(P/A,12%,10)这个值用计算器怎么算出来?
  2. BZOJ1841 : 蚂蚁搬家
  3. java工程师面试宝典_【Java工程师面试宝典】学习说明_互联网校招面试真题面经汇总_牛客网...
  4. 对话阿里云李飞飞:数据库迎来开源新时代 | 《新程序员》
  5. this,super关键字的使用
  6. mysql执行计划id相同_MySQL|MySQL执行计划
  7. python列表题目_python4_list应用的练习题
  8. 系列学习 Gateway 之第 1 篇 —— SpringCloud Gateway 简介,Gateway 入门实例
  9. 关于Jabber客户端
  10. mongodb 数据库迁移
  11. 塔尔寺景点门票销售管理系统
  12. Swift游戏实战-跑酷熊猫 14 熊猫打滚
  13. 账户服务器暂时出现问题,Microsoft帐户,验证你的帐户,在提交安全代碼后出现提示信息:此服务暂时出现了问题,请重试 - Microsoft Community...
  14. 插件化框架集成-360插件框架DroidPlug
  15. 计算图片的相似度(深度学习)
  16. 关于violate变量的使用
  17. wps高亮怎么取消_一组WPS表格小技巧,简单实用
  18. STATA学习笔记:egen函数
  19. Java mail接收邮件 回复邮件 转发邮件
  20. 如何选购一款性能较好的固态硬盘

热门文章

  1. 关于数据、数据流、数据管道的一些看法(二)
  2. win10 计算机 搜索文件,在 Windows 10 上高效搜文件,自带搜索功能其实就够了
  3. Hive操作——删除表(drop、truncate)
  4. 进程和守护进程的区别
  5. 有哪些高性价比的LoRa模块?
  6. 解决hive 中 beeline无法连接问题
  7. Quartus-II的安装教程
  8. Excel从身份证号提取生日
  9. 基于python的国内外研究现状怎么写_毕业论文指之国内外研究现状的写法与范文...
  10. tkinter:Toplevel