一、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. PDA开发数据由DB下载至PDA本地

    public string DownFile = "\\下载.txt";//下载路径 public string LoadFile = "\\上传.txt";/ ...

  2. Android模拟器启动不了解决办法

    问题描述:Windows2008中的MyEclipse项目在Windows2003中运行时无法启动模拟器. 解决要点:启动模拟器管理工具,在启动中设置属性中不勾选默认尺寸显示. 系统错误如下: [20 ...

  3. Android Data Binding实战(一)

    在今年Google I/O大会上,Google推出Design Library库的同时也推出了Android Data Binding,那么什么是Data Binding?其名曰数据绑定,使用它我们可 ...

  4. ThreadPoolExecutor运行机制

    最近发现几起对ThreadPoolExecutor的误用,其中包括自己,发现都是因为没有仔细看注释和内部运转机制,想当然的揣测参数导致,先看一下新建一个ThreadPoolExecutor的构建参数: ...

  5. The 2nd tip of DB Query Analyzer

    The 2nd tip of DB Query Analyzer                               Ma Genfeng   (Guangdong Unitoll Servi ...

  6. redis存入中文,取出来显示不正常

    问题: 127.0.0.1:6379> set name 张泰松OK127.0.0.1:6379> get name"\xe5\xbc\xa0\xe6\xb3\xb0\xe6\x ...

  7. IndexedDB,FileSystem- 前端数据库,文件管理系统

    "我们不再需要下载并且安装软件.一个简单的web浏览器和一个可供使用的互联网就足以让我们在任何时间, 任何地点, 还有任何平台上使用任何web应用程序." web应用很酷, 但是相 ...

  8. Python的lambda

    if else 可以用简单的三元运算符表示 if 1 == 1: name = 'wupeiqi' else: name = 'alex' --> name = 'wupeiqi' if 1 = ...

  9. minimun depth of binary tree

    Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shor ...

  10. java小知识点汇总

    1.ConcurrentHashMap使用segment来分段和管理锁,segment继承自ReentrantLock,因此ConcurrentHashMap使用ReentrantLock来保证线程安 ...