一、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. ubuntu中taglist和ctags安装使用

    1.使用命令安装ctags: 2.安装taglist 下载: http://vim.sourceforge.net/scripts/download_script.php?src_id=6416 拷贝 ...

  2. Linux下进程通信之管道

    每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把 ...

  3. ITU-T Technical Paper: QoS的构建模块与机制

    本文翻译自ITU-T的Technical Paper:<How to increase QoS/QoE of IP-based platform(s) to regionally agreed ...

  4. Developing User Interfaces

      Developing a User Interface with ADF Faces Purpose This tutorial covers developing the user interf ...

  5. Vim编译器的常用使用方法与技巧

    vim操作 插入模式         命令行模式         末行模式 命令行模式  ->   插入模式 i --->   在当前光标的前一个插入 I --->   在行首插入 ...

  6. winform编程设定listview选中行

    在做项目中,需要用到listview显示数据.同时,项目要求,通过检索用户输入的数据,程序通过搜索,确定数据所在的行并通过程序设定为选中状态并高亮显示.同时,正常响应鼠标单击响应的效果,单击时,程序设 ...

  7. Oracle EBS ERP中月结年结的流程总结

    月结与年结处理,是企业财务比较特殊而重要的业务操作.在实施与推广OracleERP系统过程中,如何结合现行的会计制度与惯例,充分利用软件功能,做好相应的关账.开账工作,是困扰许多企业财务人员乃至实施顾 ...

  8. ZeroC Ice Ice Registry实现负载均衡

    Registry介绍         对于多个IceBox集群该怎么负载均衡?以服务注册表Registry为依托的Service Locator组件,以及依赖其而诞生的强大的分分布式框架-IceGri ...

  9. css选择器语法速查

    通用选择器 *{} 类似于通配符,匹配文档中所有元素类型: 类型选择器 h1,h2,p{} 匹配以逗号隔开元素列表中的所有元素 类选择器 .glass{} or p.glass{} id选择器 #id ...

  10. fastDFS与Java整合上传下载

    由于项目需要整合个文件管理,选择使用的是fastDFS. 整合使用还是很方便的. 准备 下载fastdfs-client-java源码 源码地址 密码:s3sw 修改pom.xml 第一个plugin ...