【Spring】Spring IOC原理及源码解析之scope=request、session
一、容器
1. 容器
抛出一个议点:BeanFactory是IOC容器,而ApplicationContex则是Spring容器。
什么是容器?Collection和Container这两个单词都有存放什么东西的意思,但是放在程序猿的世界,却注定是千差万别。Collection,集合,存放obj instanceof Class为true的一类对象,重点在于存放;Container,容器,可以存放各种各样的obj,但不仅仅是存放,他被称为容器,更重要的是他能管理存放对象的生命周期和依赖。
容器:用于存放对象,并能对存放对象进行生命周期管理和依赖管理。
2. Spring IOC容器是BeanFactory
Spring IOC容器是BeanFactory,也正是基于【容器】的论点。
在逻辑和源码分析之前,先做一些铺垫。对于使用Spring的程序猿来说,常用是ApplicationContext接口及其实现子类,ClasPathXmlApplicationContext、FilesystemXmlApplicationContext、XmlWebApplicationContext和AnnotationConfigWebApplicationContext,对于这四个类来说,他们都有一个共同的抽象父类AbstractRefreshableApplicationContext,而正是在该抽象父类中完成对BeanFactory的装饰。
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext { private Boolean allowBeanDefinitionOverriding; private Boolean allowCircularReferences; /** Bean factory for this context */ private DefaultListableBeanFactory beanFactory;
所有通过ApplicationContext接入使用Spring服务的,都是使用该bean工厂,而且最重要的是这个bean工厂实现了所有BeanFactory的接口、抽象类,拥有完整的Spring IOC逻辑。

二、Spring IOC逻辑
- BeanFactory在Spring容器初始化的时候创建,默认创建的是DefaultListableBeanFactory对象;
- Bean的配置信息在Spring容器初始化的时候被加载,并被解析成BeanDefinition存放在DefaultListableBeanFactory的ConcurrentHashMap中,此时无论是单例、非单例Bean都没有被创建;
- BeanFactory:直接通过BeanFactory来接入使用Spring,无论是单例、非单例的Bean都不会在Spring初始化的时候被创建,而是在第一次getBean的是时候才被创建,此时单例bean会被缓存,而非单例的bean不会被缓存;
- ApplicationContext:直接通过ApplicationContext来介入使用Spring,单例并且非lazy-init的Bean在Spring初始化的时候被创建并缓存,非单例、lazy-init的Bean在第一次getBean的时候被创建(Spring容器初始化的refresh方法中,finishBeanFactoryInitialization(beanFactory)方法完成);
- 所有Bean的依赖注入在getBean逻辑中完成,当然是在getBean实例化对象之后;
- 对于web环境下scope为request、session、globalsession的Bean来说,通过RequestContextListener侦听器侦听Request的建立和销毁,从而进行Bean生命周期的管理,具体是RequestContextHolder中通过ThreadLocal将ServletRequestAttributes与当前线程绑定,ServletRequestAttributes构造方法传入HttpServletRequest进行绑定,所有Bean第一次获取的时候会被缓存到ServletRequestAttributes中(因为传入了request,实际上最终存入了request的map容器中),之后直接从ServletRequestAttributes中获取不再进行创建;
三、源码分析
1. BeanFactory的创建
第一节已经说过ApplicationContext常用的的四个子类都有一个公共的抽象父类AbstractRefreshableApplicationContext,在该类中对BeanFactory进行装饰,一个更重要的点是AbstractRefreshableApplicationContext的父类是AbstractApplicationContext,其refresh方法定义了整个Spring容器启动的过程。
也就是说,无论你采用哪一种ApplicationContext接入Spring容器,最终都会进入AbstractApplicationContext的refresh方法,完成Spring的启动。
源码分析是基于Spring IOC中提出的五点逻辑,发现一篇写的很全面的文章,因此决定不再写了,贴出来共享。
《Spring:源码解读Spring IOC原理》
作为连接文章中未提及的web部分,本文予以补充,进行原理和源码的分析。
2. web Scope(request、session、globalsession)
如上文继承关系所述内容,无论是哪一种ApplicationContext,最终都是通过AbstractBeanFactory.getBean(String)来获取Bean,节选部分代码。
@Override
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());
}
return scopedObject;
}
// 从BeanDefinition中获取scope配置内容 String scopeName = mbd.getScope();
// this.scopes是一个Map<String, Scope>,用于存放Scope对象实例
// 这里如果是request则获取到RequestScope
// 如果是Session则获取到SessionScope final Scope scope = this.scopes.get(scopeName);
if (scope == null){throw new IllegalStateException("No Scope registered for scope '" + scopeName + "'");}try {// 这里是重点// 通过Scope.get(String, ObjectFactory)接入到Spring IOC容器中 Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { beforePrototypeCreation(beanName); try { 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的类结构如下图所示。

AbstractRequestAttributeScope定义了get(beanName, ObjectFactory)方法,通过该方法接入Spring IOC容器,源码如下所示。
public Object get(String name, ObjectFactory<?> objectFactory) { // 这里很重要 // RequestContextHolder中通过ThreadLocal将RequestAttributes实例和当前线程绑定 // RequestAttributes在构造的时候需要传入HttpServletRequest,稍后会有源码分析和总结 RequestAttributes attributes = RequestContextHolder.currentRequestAttributes(); // 从当前线程绑定的RequestAttributes中获取对象(实际上是从HttpServletRequest的Map) // 如果已经实例化过了,则不再实例化 // 否则进入Spring IOC过程获取对象 Object scopedObject = attributes.getAttribute(name, getScope()); if (scopedObject == null) { scopedObject = objectFactory.getObject() attributes.setAttribute(name, scopedObject, getScope()); } return scopedObject; }
现在存在的疑问是RequestAttributes、RequestContextHolder是个什么鬼?从哪里来?
要想使用Spring web scope,在web.xml中要配置一个侦听器RequestContextListener,所有谜题的答案都在这里。
public class RequestContextListener implements ServletRequestListener {
private static final String REQUEST_ATTRIBUTES_ATTRIBUTE =
RequestContextListener.class.getName() + ".REQUEST_ATTRIBUTES";
@Override
public void requestInitialized(ServletRequestEvent requestEvent) {
if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
throw new IllegalArgumentException(
"Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
}
HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
LocaleContextHolder.setLocale(request.getLocale());
RequestContextHolder.setRequestAttributes(attributes);
}
@Override
public void requestDestroyed(ServletRequestEvent requestEvent) {
ServletRequestAttributes attributes = null;
Object reqAttr = requestEvent.getServletRequest().getAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE);
if (reqAttr instanceof ServletRequestAttributes) {
attributes = (ServletRequestAttributes) reqAttr;
}
RequestAttributes threadAttributes = RequestContextHolder.getRequestAttributes();
if (threadAttributes != null) {
// We're assumably within the original request thread...
LocaleContextHolder.resetLocaleContext();
RequestContextHolder.resetRequestAttributes();
if (attributes == null && threadAttributes instanceof ServletRequestAttributes) {
attributes = (ServletRequestAttributes) threadAttributes;
}
}
if (attributes != null) {
attributes.requestCompleted();
}
}
}
这句代码RequestContextHolder.setRequestAttributes(attributes)是将Spring web scope与Spring IOC结合的关键。
public abstract class RequestContextHolder {
// 将ServletRequestAttributes与当前线程绑定
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<RequestAttributes>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<RequestAttributes>("Request context");
// 1 第一步 public static void setRequestAttributes(RequestAttributes attributes) { setRequestAttributes(attributes, false); }
// 2 第二步,inheritable=false public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable)
{ if (attributes == null) { resetRequestAttributes(); }
else {
if (inheritable) { inheritableRequestAttributesHolder.set(attributes); requestAttributesHolder.remove(); }
else {
// 3 第三步,将ServletRequestAttributes与当前线程绑定 requestAttributesHolder.set(attributes); inheritableRequestAttributesHolder.remove(); } } } //其他源码,略 }
至此,整个过程结束。session的过程几乎和request一致,只是生命周期不一样,多了一把锁。总结一下上述源码分析的内容。
总结:Spring Bean所有scope定义为request、session、globalsession的Bean都不会主动初始化,当第一次通过Spring IOC来getBean的时候才会实例化并进行依赖注入,但是在实例化的时候需要借助于RequestContextHolder(ThreadLocal模式)来将Bean与当前线程中的HttpServletRequest来进行绑定,并在scope的生命周期中缓存在相应的域对象中(request或session)。
最后补充一点,ServletRequestAttributes的getAttribute和setAttribute。在getBean的时候,会先从ServletRequestAttributes.getAttribute中获取Bean,如果获取到则返回,否则进行IOC实例化,并调用ServletRequestAttributes.setArrtibute进行缓存,实际上在setAttribute方法中是调用request.setAttribute来完成。
public class ServletRequestAttributes extends AbstractRequestAttributes {
public static final String DESTRUCTION_CALLBACK_NAME_PREFIX = ServletRequestAttributes.class.getName() + ".DESTRUCTION_CALLBACK.";
private final HttpServletRequest request; private HttpServletResponse response;
private volatile HttpSession session;
private final Map<String, Object> sessionAttributesToupdate = new ConcurrentHashMap<String, Object>(1);
// 1 ServletRequestAttributes对象的创建,必须要一个HttpServletRequest实例
public ServletRequestAttributes(HttpServletRequest request) {
Assert.notNull(request, "Request must not be null"); this.request = request; }
public ServletRequestAttributes(HttpServletRequest request, HttpServletResponse response) { this(request); this.response = response; }
// 转调request.getAttribute或session.getAttribute
public Object getAttribute(String name, int scope) { if (scope == SCOPE_REQUEST) { if (!isRequestActive()) { throw new IllegalStateException( "Cannot ask for request attribute - request is not active anymore!"); } return this.request.getAttribute(name); } else { HttpSession session = getSession(false); if (session != null) { try { Object value =session.getAttribute(name); if (value != null) { this.sessionAttributesToUpdate.put(name, value); } return value; } catch (IllegalStateException ex) { // Session invalidated - shouldn't usually happen. } } return null; } }
// 转调reqeust.setAttribute或session。setAttribute 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 = getSession(true); this.sessionAttributesToUpdate.remove(name); session.setAttribute(name, value); } }
【Spring】Spring IOC原理及源码解析之scope=request、session的更多相关文章
- Spring MVC工作原理及源码解析(三) HandlerMapping和HandlerAdapter实现原理及源码解析
1.HandlerMapping实现原理及源码解析 在前面讲解Spring MVC工作流程的时候我们说过,前端控制器收到请求后会调⽤处理器映射器(HandlerMapping),处理器映射器根据请求U ...
- 【SSH进阶之路】Spring的IOC逐层深入——源码解析之IoC的根本BeanFactory(五)
我们前面的三篇博文,简单易懂的介绍了为什么要使用IOC[实例讲解](二).和Spring的IOC原理[通俗解释](三)以及依赖注入的两种常用实现类型(四),这些都是刚开始学习Spring IoC容器时 ...
- Spring MVC工作原理及源码解析(一) MVC原理介绍、与IOC容器整合原理
MVC原理介绍 Spring MVC原理图 上图是Spring MVC工作原理图(图片来自网上搜索),根据上图,我们可以得知Spring MVC的工作流程如下: 1.用户(客户端,即浏览器)发送请求至 ...
- Spring MVC工作原理及源码解析(二)DispatcherServlet实现原理及源码解析
1.DispatcherServlet 处理流程 从上一篇文章中Spring MVC原理图中我们可以看出:DispatcherServlet 在 Spring MVC框架 中处于核心位置,它负责协调和 ...
- Spring MVC工作原理及源码解析(四) ViewResolver实现原理及源码解析
0.ViewResolver原理介绍 根据视图的名称将其解析为 View 类型的视图,如通过 ModelAndView 中的视图名称将其解析成 View,View 是用来渲染页面的,也就是将 Mode ...
- Spring-Session实现Session共享实现原理以及源码解析
知其然,还要知其所以然 ! 本篇介绍Spring-Session的整个实现的原理.以及对核心的源码进行简单的介绍! 实现原理介绍 实现原理这里简单说明描述: 就是当Web服务器接收到http请求后,当 ...
- 机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理、源码解析及测试
机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理.源码解析及测试 关键字:决策树.python.源码解析.测试作者:米仓山下时间:2018-10-2 ...
- Spring AOP的实现及源码解析
在介绍AOP之前,想必很多人都听说AOP是基于动态代理和反射来实现的,那么在看AOP之前,你需要弄懂什么是动态代理和反射及它们又是如何实现的. 想了解JDK的动态代理及反射的实现和源码分析,请参见下面 ...
- 【spring源码学习】spring的事务管理的源码解析
[一]spring事务管理(1)spring的事务管理,是基于aop动态代理实现的.对目标对象生成代理对象,加入事务管理的核心拦截器==>org.springframework.transact ...
随机推荐
- 骑士飞行棋 C#代码详解
最近看见一个骑士飞行棋的小游戏代码,感觉这个代码中将大多数C#的基础知识都运用到了,是一个新手检验学习成果的有效方法,特此将这个代码整理一遍.这是一个控制台程序.这是代码下载地址,代码中的注释非常详细 ...
- 20160329javaweb之JSP -session入门
3.Session Session 是一个域 !!作用范围:当前会话范围 !!生命周期: 当程序第一次调用到request.getSession()方法时说明客户端明确的需要用到session此时创建 ...
- java经验总结二:ORA-08103: 对象不再存在
问题发生的环境: 在springMvc+mybatis框架中,调用oracle的存储过程时,碰到的一个这样的异常: org.springframework.jdbc.UncategorizedSQLE ...
- ASP.NET MVC5总结(一)@HTML和对应的HTML
HtmlHelper用来在视图中呈现 HTML 控件,主要分为以下几类: 1.ActionLink - 链接到操作方法 @Html.ActionLink("这是一个连接", &qu ...
- [linux] 系统管理常用命令
1.查看某个软件是否安装: rpm -qa|grep software_name 2.top命令,显示系统的动态视图,q退出 3.ps aux|grep process_name 显示正在运行的进程 ...
- editActionsForRowAtIndexPath(iOS8) tableview编辑(删除、插入、移动)
ios8 出来的左滑小菜单 可以自定义想要的按钮 (要求ios8以上) - (nullable NSArray<UITableViewRowAction *> *)tableView:(U ...
- weewwe
http://blog.csdn.net/u013073524/article/details/25912891
- 九度OJ 1019 简单计算器 -- 2006年浙江大学计算机及软件工程研究生机试真题
题目地址:http://ac.jobdu.com/problem.php?pid=1019 题目描述: 读入一个只包含 +, -, *, / 的非负整数计算表达式,计算该表达式的值. 输入: ...
- 用 Python写 daemon
转自 http://chowroc.blogspot.com/2007/05/python-how-to-write-daemon.html 最近用 Python 可能要写 daemon,找资料先看看 ...
- Javascript数组的indexOf()、lastIndexOf()方法
在javascript数组中提供了两个方法来对数组进行查找,这两个方法分别为indexOf(),lastIndexOf(). 这两个方法都有两个参数,第一个参数为需要查找的项,第二个参数则是查找的起始 ...