代码改变世界

Spring对象生存周期(Scope)的分析

2014-03-24 17:02 by noahark-zhang, ... 阅读, ... 评论, 收藏, 编辑

一、Scope定义

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

二、Scope种类

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

singleton

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

<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:每次向容器请求对象都回返回对象的一个全新的对象,如下图所示;

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的继承关系如下图所示:

class

在上图中,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方法之后即可返回代理的对象,其调用序列如下:

sequence

获取代理对象之后,对目标对象的调用都是先通过代理对象来进行,然后再根据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/ ,请勿用于任何商业用途)