【Spring】Spring IOC原理及源码解析之scope=request、session

来源:互联网 发布:辐射4 男性捏脸数据 编辑:程序博客网 时间:2024/05/03 04:55

    不要想得太复杂,scope=request/session,说白了就一句话:“IOC容器创建一个bean,然后把这个bean放在request域或session域”,就这么多,没了。

    不管这个bean是放进去也好还是取出来也好,核心问题就出来了:

    1. “什么时候拿到request?”

    2. “如何拿到正确的request?”

    从server的角度而言,ServletRequestListener侦听request的创建和销毁,同时request是和线程绑定的,所以通过线程可以拿到request,所以,解决方案就出来了:

    1. 什么时候拿?创建request的时候拿,通过ServletRequestListener侦听request的创建和销毁,然后放入Thread Local中;

    2. 怎么拿到正确的request?通过ThreadLocal保证,将request对象放入Thread.ThreadLocal.ThreadLocalMap中,每个线程带着自己的request走;

    Spring给出的解决方案:RequestContextListener实现了ServletRequestListener,RequestContextHolder封装了ThreadLocal,AbstractBeanFactory的getBean方法中通过Scope转到RequestContextHolder,再转到ThreadLocal拿到request,创建出来的bean要存要取予取予求。

一、容器

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,节选部分代码。

// 从BeanDefinition中获取scope配置内容String scopeName = mbd.getScope();// this.scopes是一个Map<String, Scope>,用于存放Scope对象实例// 这里如果是request则获取到RequestScope// 如果是Session则获取到SessionScopefinal 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>() {@Overridepublic 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,所有谜题的答案都在这里。

package org.springframework.web.context.request;import javax.servlet.ServletRequestEvent;import javax.servlet.ServletRequestListener;import javax.servlet.http.HttpServletRequest;import org.springframework.context.i18n.LocaleContextHolder;public class RequestContextListener implements ServletRequestListener {private static final String REQUEST_ATTRIBUTES_ATTRIBUTE =RequestContextListener.class.getName() + ".REQUEST_ATTRIBUTES";// request创建的时候会触发该方法被调用执行public void requestInitialized(ServletRequestEvent requestEvent) {// 只支持HTTP方式if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {throw new IllegalArgumentException("Request is not an HttpServletRequest: " + requestEvent.getServletRequest());}// 获取当前requestHttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();// 创建ServletRequestAttributes对象// 该对象只是简单的封装了request,实际上scope为request或session的Bean都存储在request或session中ServletRequestAttributes attributes = new ServletRequestAttributes(request);// 和request水乳交融request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);LocaleContextHolder.setLocale(request.getLocale());// RequestContextHolder中封装了一个ThreadLocal// 因此这句代码的含义是将ServletRequestAttributes与当前线程绑定// 因为是一个静态方法,因此这意味着无论在哪里// 只要调用RequestContextHolder.getRequestAttributes().getAttribute().setAttribute()都能将Bean和request进行绑定// 而且在一次request的生命周期中不会重复创建对象RequestContextHolder.setRequestAttributes(attributes);}// request被销毁的时候会触发该方法调用执行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=falsepublic 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.getAttributepublic 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。setAttributepublic 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);}}

    至此,分析结束。

3. scope代理

    当一个singleton的Bean A需要注入一个scope=request/session的bean B的时候,因为A是单例,因此在第一次被注入B之后,在整个A的生命周期内其属性A并不会改变,这违背了scope=request/session初衷。实际上,应该是通过A获取到的B是当前线程拥有的B才对,而不应该始终都是第一次注入的B。

    解决的方法是通过代理,如下代码所示,在scope=request/session的Bean中添加如下配置,则每次获取到的都是一个代理,这个代理会通过RequestContextHolder获取当前线程的Bean,原理如下图所示。

<bean id="" class="" scope="request">    <aop:scoped-proxy/></bean>


附注:

    一开始想把Spring整个IOC逻辑理出来,但是发现已经有写的比较好的文章,因此就决定不写了。

    但是阅读之后发现web scope中的IOC过程并没有分析出来,因此文章后半部分针对scope=reqeust、scope=session进行了实现源码与原理的分析过程。

    最后,本文如有错漏,烦请不吝指正,谢谢!


1 0
原创粉丝点击