一、Scope定义

Scope用来声明容器中管理的对象所应该处的限定场景或者说对象的存活时间,即容器在对象进入相应的Scope之前,生产并装配这些对象,在该对象不再处于这些Scope之后,容器通常会销毁这些对象1。换句说,Scope是用来管理容器中对象的生存周期的,当对象在spring容器中组装生成之后,将其存入Scope内,该对象在容器中的获取及销毁操作都由Scope负责,容器只是在恰当的时间调用这些方法。

二、Scope种类

1、singleton:一个Spring IoC容器只包含一个该对象,如下图所示,与GoF中的单例模式不同,在单例模式中,它是保证在一个类加载器中只有一个对象实例。

在使用中可以使用如下三种方式:

<bean id="accountService" class="com.foo.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default); using spring-beans-2.0.dtd -->

<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

<!-- the following is equivalent and preserved for backward compatibility in spring-beans.dtd -->

<bean id="accountService" class="com.foo.DefaultAccountService" singleton="true"/>

2、prototype:每次向容器请求对象都回返回对象的一个全新的对象,如下图所示;

配置如下:

<!-- using spring-beans-2.0.dtd -->

<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>

<!-- the following is equivalent and preserved for backward compatibility in spring-beans.dtd -->

<bean id="accountService" class="com.foo.DefaultAccountService" singleton="false"/>

3、request:每一个HTTP请求都返回一个全新的对象 ;

<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>

4、session:每一个Http Session返回一个全新的对象;

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

5、global session:全局session对象,在一个上下文环境中只有一个对象。

<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>

其中Scope request,session和global session只适用于Web应用程序中,通常是与XmlWebApplicationContext一起使用,在实现上,singleton和prototype两种类型的容器的标准类型,而Scope request,session和global session是继承于自定义的Scope接口,如果用户有特殊的需要,可以继承这个接口并注册到容器中,即可使用用户自定义的Scope类型。用户自定义的Scope必须自己维护所管辖对象的初始化及销毁,即容器将这些对象的生存周期委托给Scope管理,而容器只管理Scope对象本身就可以。

三、request scope分析

Request socpe是怎么实现的呢?我们可以设想是这样:1、IoC(BeanFactory,ApplicationContext等)容器负责Bean对象的装配;2、通过Scope将Bean对象的引用存入HttpServletRequest中,使该对象在整个http request中都有效;3、在request请求结束后,request对象被销毁,存储在request对象中的bean对象引用也随着销毁。没有了对象的引用,Bean对象的内存空间随即被JVM回收。在Spring中是否是这样处理的?可以从Spring的源码中看出端倪。

在Spring中,request Scope的继承关系如下图所示:

在上图中,Scope接口定义了对象生存周期的方法,如获取和移除对象的方法,另外还可定义一些回调方法,在图中没有展现出来,如果要定义一个自定义的Scope,这两个方法是必须要实现的。AbstractRequestAttributesScope抽象类实现大部分的逻辑,RequestCope和SessionScope只是简单继承这个抽象类。现在来分析抽象类中的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());
}
return scopedObject;
}

第2行代码主要是返回绑定到当前线程的RequestAttributes对象,该对象封装了对HttpServletRequest对象的访问;第3行代码主要是从request对象中获取Bean对象,如果存在,直接返回,如果不存在,则由IoC容器生成并存入到Request对象中,主要由5,6行代码实现。

按照之前假设的步骤来分析代码:

1、Bean对象的组装生成,关键是第5行代码objectFactory.getObject()。objectFactory是对BeanFactory(IoC容器)对象的简单封装。接下来再看在容器AbstractBeanFactory对Scope的调用:

protected <T> T doGetBean( final String name, final Class<T> requiredType,
final Object[] args, boolean typeCheckOnly) throws BeansException
{ final String beanName = transformedBeanName(name);
Object bean;
……. String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null)
{
throw new IllegalStateException("");
}
try {
Object scopedInstance = scope.get(beanName, new
ObjectFactory<Object>() {
public Object getObject() throws BeansException {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
} finally {
afterPrototypeCreation(beanName);
}
}
}); bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
return (T) bean; }

省略了大部分的代码,在这里会判断定义对象BeanDefinition中关于Scope的类型,会依次处理singleton,prototype类型,如果都不是,则获取在类定义中的scope名称,从容器中获取到对应的Scope对象,从Scope中取到对象并返回。在这个过程中,如果没有找到对应的Scope对象,则抛出异常。

2、Bean对象的存储,在上一个步骤中,容器直接通过Scope对象获取Bean对象,貌似Bean是存储在Scope中,但实际上是不是这样?看AbstractRequestAttributesScope中的代码会发现,实际上Bean对象是存储在RequestAttributes(ServletRequestAttributes实现类)中,而该对象持有HttpServletRequest的引用,最终是存储在HttpServletRequest对象上。

3、RequestAttributes对象分析。在第二步中,已经得到RequestAttributes是Bean最终存放的容器。但是对于一个Http请求,RequestAttributes是怎么跟每一个请求对应起来的?从源头RequestContextListener分析起:

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);
}

从以上代码可以看出,对于每一个Http请求,监听器都会生成ServletRequestAttributes对象,并将该对象通过RequestContextHolder依附在当前线程(ThreadLocal)上,然后在Scope中通过RequestContextHolder获取当前线程上的ServletRequestAttributes对象,从而完成数据的传递。

从以上分析可以看出,RequestScope主要是通过将Bean对象存储在HttpServletRequest对象中来完成对其生存周期的控制,另外,通过Scope接口,Spring提供了用户自行控制Bean对象的生存周期的渠道,提高了其扩展性。SessionScope的实现与RequestScope类似,就不再描述。

四、scoped-proxy

将request,session,globalsession和自定义作用域的对象注入到singleton对象(被注入对象(singleton域)的作用域大于注入对象(request作用域))时,会产生一个问题,被注入的对象一直是第一次注入的对象,从而不能满足需求。这时往往需要配置scoped-proxy,通过代理对象来调用真正的处理才能达到目的,如下配置:

<!-- a HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.foo.UserPreferences" scope="request">
<!-- this next element effects the proxying of the surrounding bean -->
<aop:scoped-proxy/>
</bean> <!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.foo.SimpleUserService">
<!-- a reference to the proxied 'userPreferences' bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean

引入Scoped-proxy配置之后,spring内部是怎么处理的?可以简单猜测一下,UserPreferences对象的BeanDefinition会记录该配置,从而在生成Bean时候返回代理对象。

对scoped-proxy进行解析的工作是由ScopedProxyBeanDefinitionDecorator负责,在这里要完成两个任务:1、为代理类生成一个新的BeanDefinination对象,其类型设置为ScopedProxyFactoryBean.class,这个类主要完成代理类的生成,并用userPreferences名称注册这个新生成的BeanDefinination对象,这样的目的是让代理对象取代目标对象,起到偷梁换柱的作用;2、使用另外的名称(加scopedTarget.前缀)来注册目标对象,从而使代理对象可以找到目标对象。

完成上面的步骤之后,调用AbstractBeanFactory对象的getBean方法之后即可返回代理的对象,其调用序列如下:

获取代理对象之后,对目标对象的调用都是先通过代理对象来进行,然后再根据Scope作用域动态地获取目标对象,从而保证对于每一个request或session返回的都是不一样的对象。

四、Scope的销毁

Scope对象本身的销毁由Spring容器来进行,而Scope管理的对象的销毁操作在remove方法中定义,由Spring容器“适时”的调用。

五、总结

在Bean对象与Spring容器之间加入Scope层,可以灵活动态地管理对象的生命周期,Spring容器只要维护Scope对象本身的生命周期,而依附在Scope对象中的Bean对象则委托给Scope对象处理。另外一方面,通过引入AOP,可以在运行时动态获取目标对象,极大的方便了功能的扩展。

附录:

1、《spring揭穿》。

(转载本站文章请注明作者和出处 http://www.cnblogs.com/noahsark/ ,请勿用于任何商业用途)

Spring对象生存周期(Scope)的分析的更多相关文章

  1. Spring IOC 容器源码分析 - 填充属性到 bean 原始对象

    1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的.我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反 ...

  2. Spring IOC 容器源码分析 - 创建原始 bean 对象

    1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...

  3. Spring IOC 容器源码分析 - 获取单例 bean

    1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...

  4. Spring IOC 容器源码分析系列文章导读

    1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本.经过十几年的迭代,现在的 Spring 框架已经非常成熟了.Spring ...

  5. Spring IOC 源码简单分析 02 - Bean Reference

    ### 准备 ## 目标 了解 bean reference 装配的流程 ##测试代码 gordon.study.spring.ioc.IOC02_BeanReference.java   ioc02 ...

  6. 浅谈Spring框架注解的用法分析

    原文出处: locality 1.@Component是Spring定义的一个通用注解,可以注解任何bean. 2.@Scope定义bean的作用域,其默认作用域是”singleton”,除此之外还有 ...

  7. 九、Spring之BeanFactory源码分析(一)

    Spring之BeanFactory源码分析(一) ​ 注意:该随笔内容完全引自https://blog.csdn.net/u014634338/article/details/82865644,写的 ...

  8. Spring启动过程源码分析基本概念

    Spring启动过程源码分析基本概念 本文是通过AnnotationConfigApplicationContext读取配置类来一步一步去了解Spring的启动过程. 在看源码之前,我们要知道某些类的 ...

  9. Spring IOC 容器源码分析 - 余下的初始化工作

    1. 简介 本篇文章是"Spring IOC 容器源码分析"系列文章的最后一篇文章,本篇文章所分析的对象是 initializeBean 方法,该方法用于对已完成属性填充的 bea ...

随机推荐

  1. Android Studio JNI javah遇到的问题

    好久没写博客了.持之以恒的勋章也被收回了.以后要好好坚持.. 最近在学习jni,但是遇到了一点麻烦的问题.好在终于解决了,便记下来解决一下. 其他入门的jni文章有很多,这里便不在累赘,直接上我遇到的 ...

  2. Linux下实现秒级定时任务的两种方案(crontab 每秒运行)

    第一种方案,当然是写一个后台运行的脚本一直循环,然后每次循环sleep一段时间. while true ;do command sleep XX //间隔秒数 done 第二种方案,使用crontab ...

  3. 03_Nginx添加新模块

     1 进入nginx安装目录,查看nginx版本及其编译参数: [root@localhost nginx]# ./nginx -V nginx version: nginx/1.8.0 buil ...

  4. 【Java编程】Java基本数据类型

    在较前面的一篇博文<C/C++基本数据类型>中,我主要介绍了c/c++的基本数据类型.我们知道C语言没有具体规定各类数据类型所占内存的字节数,只要求long型数据长度不小于int型,sho ...

  5. Android性能优化典例(一)

    在Android开发过程中,很多时候往往因为代码的不规范.api使用不恰当.控件的使用场景考虑不全面和用户不恰当的操作等都能引发一系列性能问题的,下面就是我目前整理的一些Android开发过程中需要注 ...

  6. 不错的网络协议栈测试工具 — Packetdrill

    Packetdrill - A network stack testing tool developed by Google. 项目:https://code.google.com/p/packetd ...

  7. Android开发技巧——自定义控件之组合控件

    Android开发技巧--自定义控件之组合控件 我准备在接下来一段时间,写一系列有关Android自定义控件的博客,包括如何进行各种自定义,并分享一下我所知道的其中的技巧,注意点等. 还是那句老话,尽 ...

  8. android自定义view实现公章效果

    上次去一个公司面试,面试官问了一个题,怎么用android的自定义view实现一个公章的效果,据说这是华为之前的面试题,我想了下,要是公章的效果,最外层是一个圆,里面是一个五角星,但是这文字怎么画呢, ...

  9. Aandroid 图片加载库Glide 实战(一),初始,加载进阶到实践

    原文: http://blog.csdn.net/sk719887916/article/details/39989293 skay 初识Glide 为何使用 Glide? 有经验的 Android ...

  10. masm中list文件和宏的一些常用编译调试查看方法

    我们知道使用用 ml /Fl a.asm 可以生成lst文件,但是如果不加调整,masm默认生成的lst文件是非常大的,因为它包含了很大的windows必须用到的头文件内容,为了减小lst文件大小,便 ...