Spring Bean的范围

当你配置一个bean的时候,你会通过bean的配置为实际的类实例创建一个配方(recipe )。bean的配置是一个配方的想法是很重要,因为它意味着,就像类一样,你可以通过一个配方来创建很多个实例。


你不仅可以控制被注入到由特定bean配置创建的对象的依赖和配置值,而且也可以控制对象的范围。这个方式是强大和灵活的,通过这种方式你可以通过配置文件来选择对象的范围,而不用放到Java类的级别。可以用多个范围内的一个来定义bean加载的范围:开箱即用,Spring支持5类范围,其中3个是只有你用到web-aware的ApplicationContext时才会有用。


下列的范围都是可以直接使用的。你也可以创建一个自定义范围(a custom scope)。

Table 6.3. Bean scopes

范围 描述

singleton

默认,一个bean定义在每个Spring容器中只会产生一个对象实例。

prototype

一个bean定义会产生任意数量的对象实例

request

一个bean定义的范围是在HTTP请求的生命周期内;这也就是,每一个HTTP请求都有自己的一个bean定义创建的bean定义。只有在基于web的SpringApplicationContext的上下文中才有效。

session

一个bean定义的范围是在HTTP Session的生命周期内,只有在基于web的SpringApplicationContext的上下文中才有效。

global session

一个bean的定义的范围是在全局HTTPSession生命周期内。通常在Portlet容器中是不可用的。只有在基于web的SpringApplicationContext的上下文中才有效。

application

一个bean的范围是在ServletContext的生命周期内。只有在基于web的SpringApplicationContext的上下文中才有效。


[Note]

在spring 3.0中,线程范围的bean也是可用的,但不是默认注册的。想了解更多信息,请参考SimpleThreadScope文档。为了说明如何注册或定义任何其他的范围,参考“使用自定义范围”(“Using a custom scope”.)章节。


单例范围(The singleton scope)


只维护一个共享的bean实例,任何通过id或ids匹配的bean的请求,Spring都会返一个特定的bean的实例。


换种方式说,当你定义一个bean的范围为singleton时,Spring容器会根据bean的配置恰好创建一个对象实例。这个单例bean被存在这些单例bean的缓存中,任何后续对于同一个bean的请求或者引用都会返回缓存对象。

singleton

Spring概念的单例bean和设计模式中的单例是不同的。设计模式的单例是通过硬编码的方式使一个对象在每一个ClassLoder中只会创建仅仅一个特定类的实例。Spring Bean的单例范围是对一个容器一个bean的最好描述。这意味着如果你在一个Spring容器中只为一个类定义了一个bean,那么Spring只会根据配置文件创建一个指定类的实例。singleton 范围是Spring默认的范围。用XML定义单例bean,你应该如例子中的写法:

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

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

原型范围

和单例范围不同,原型(prototype )范围bean的加载是在每一次请求一个指定的bean时,都会创建一个新的bean的实例。也就是说,你通过getBean()方式请求容器把一个bean注入到另一个里或者在请求它时。作为规则,对所有有状态的bean使用原型范围,对所有没有状态的bean使用单例范围。

下面的图展示Spring的原型范围。一个数据访问对象(DAO)通常不被配置为prototype,因为一个典型的DAO不会保持所有对话的状态;这仅仅是为了方便本文作者重用singleton 范围的核心图。
prototype
下面的例子用XML定义bean为 prototype范围
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
和其他范围不同,Spring不会管理 prototype的完整的生命周期:容器初始化,配置,和另外的装配prototype 对象,分配给客户端,就不会再有这个原型实例更多的记录了。因此,尽管所有不分范围的对象都会调用初始化生命周期回调方法,对于原型bean,注册的生命周期的销毁阶段是不会被调用的。客户端代码必须清除原型范围(prototype-scoped)的对象并且释放那些被prototype Bean保持的昂贵的资源。希望Spring容器来释放被原型bean保持的对象,试一下使用自定义bean后处理(custom bean post-processor),这个后处理保持了需要被清理bean的引用。

在某些方面,Spring容器的原型范围bean的角色是和JAVAnew 操作符可以互换的。所有过了生命周期管理以后,都需要由客户端来管理。(更多关于Spring Bean的声明周期细节,请参考6.6.1节“生命周期回调”( Section 6.6.1, “Lifecycle callbacks”))

拥有原型bean依赖的单例bean

当你的单例范围的bean拥有原型范围bean的依赖时,要意识到依赖是在框架初始化的时候注入的。所以,如果你给单例范围(singleton-scoped)的bean注入了原型范围(prototype-scoped )的依赖,每次容器初始化时都会产生一个新的prototype  Bean注入到singleton Bean中。原来提供给单例范围( singleton-scoped)Bean的实例是原型(prototype )实例唯一的实例。

但是,假如你希望单例范围(singleton-scoped )的Bean在运行期间每次都获取一个新的原型范围(prototype-scoped)的实例。你不能给你的单例(singleton)实例依赖注入一个原型范围(prototype-scoped)的Bean,因为那个注入只会在Spring容器初始化单例(singleton )Bean时注入一次。如果你在运行期希望多次获得一个新的原型(prototype )Bean的实例,参考6.4.6“方法注入”(“Method injection”

Request范围,Session范围和global session范围

 request sessionglobal session 范围只有在你用到基于web的Spring ApplicationContext实现的时候才会有用(例如XmlWebApplicationContext)。如果你在普通的Spring Ioc容器中使用这些范围,例如 XmlWebApplicationContext,你会得到一个报出一个未命名(unknown bean scope)的Bean范围的IllegalStateException 错误。

初始化web配置


为了支持 request sessionglobal session 级别的Bean范围(web范围的bean),在你定义Bean之前需要做一些小的初始化配置。(这个配置对标准范围是不需要的,例如 singleton 和prototype)。

你如何完成这个配置取决你的特定的Servlet 环境

如果你在Spring web MVC里使用Bean的范围,使用 DispatcherServlet 或 DispatcherPortlet来处理请求,那么什么都不用做:DispatcherServlet 和 DispatcherPortlet已经了解了所有有关的状态。

如果你使用Servlet 2.5 Web容器,在Spring的DispatcherServlet 之外处理request(例如,当使用JSF或Struts),你需要注册org.springframework.web.context.request.RequestContextListenerServletRequestListener。对于Servlet 3.0+,这个可以通过WebApplicationInitializer 自动完成。另外,对于更老的容器,给你应用 web.xml 文件增加如下声明:
<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>
另外,如果你的监听启动有问题,可以考虑使用Spring的 RequestContextFilter。过滤器的映射取决于包围的Web容器的配置,所以你需要作出适当的改变。
<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>
DispatcherServlet RequestContextListener , 和  RequestContextFilter都做的是同样的事情,绑定HTTP请求对象到请求服务的线程,这可以进一步下降请求传递链使 request- and session-范围的Bean可用。

Request范围

考虑下面的Bean定义:

<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
Spring容器通过使用 loginActionBean的定义来为每一个HTTP请求创建一个新的 loginActionBean的实例。也就是, loginActionBean的范围是在HTTP请求(request)级别的。你可以如你所愿的更改创建的实例的内部状态,因为其他的由相同 loginActionBean配置创建的实例不会看到这个状态的改变;他们对于单独的请求是唯一的。当请求完成处理,请求范围的Bean实例就会被丢弃。

Session范围

考虑下面的Bean配置:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
Spring容器使用UserPreferences Bean定义为每一个HTTP Session生命周期内创建一个新的UserPreferences Bean的实例。换句话说,UserPreferences Bean在HTTP Session级别才有效。正如userPreferences Bean,你可以如你所愿的改变这些创建的实例的状态,其他的HTPP Session实例也会使用相同Bean定义创建的实例,这些实例是互相看不到对方状态的,因为他们对于每一个HTTP Session都是唯一的。当HTTP Session被丢弃的时候,相应范围的Bean也会被丢弃。

Global session范围

考虑下面的Bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
  global session范围和标准的HTTP   Session范围相似,只在基于Portlet(  portlet-based)的Web应用中在可用。 Portlet说明书定义global Session的概念是,由所有组成portlet的单一的portlet web应用所共享。被定义为global session 范围的Bean在全局portlet Session生命周期中都是有效的。

如果你写了一个基于标准Servlet的Web应用,定义了一个或多个Bean拥有global session 范围,容器会使用标准的HTTPSession 范围,不会报错。

Application范围

考虑下面的定义:
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
Spring容器为整个web应用只会创建一个   AppPreferences实例。也就是,   AppPreferences范围是 ServletContext  级别的,存入为一个普通的 ServletContext  属性。这有些和Spring的 singleton相似,但是两点主要的不同:它在每个ServletContext中是唯一的,而不是每个‘ApplicationContext’(或者在给定的应用中可能有多个实例),而且它实际上是作为ServletContext 的参数暴漏出来的。

 作为依赖时的Bean范围

Spring容器不仅管理你的对象(Bean)的初始化,而且连接他们之间的协作关系(或依赖)。如果你 想把一个HTTP request范围的Bean注入到另一个拥有更长的范围的Bean,你应该选择注入AOP代理而不是范围Bean。也就是,你需要注入一个和范围Bean暴露相同公共接口的代理对象,这个代理可以给实际的目标对象映射一个相同范围(例如HTTP request范围),代理实际对象的方法调用。

[Note]

你还可以在singletonBean之间使用<aop:scoped-proxy/> ,通过那个引用就可以获得一个立即序列化的代理,因此也可以通过反序列化重新获取目标的singleton Bean.当声明为<aop:scoped-proxy/>而不是 prototype,每一个共享的代理的方法调用都会导致一个新的目标实例的创建,然后这个调用才会被转发。

并且,范围代理不仅仅是唯一用一种生命周期安全的方式(lifecycle-safe fashion)在短生命周期的bean中访问长声明周期bean。你也可以把你的注入点(像构造方法,setter方法或者是自动装配字段)声明为ObjectFactory<MyTargetBean>,允许在每一次需要的时候调用getObject() 方法来获取当前需要的实例,而不需要一直保持实例或者单独存储实例。这个JSR-330 的变种叫做 Provider,用一个Provider<MyTargetBean> 来声明,对于每一次获取都会调用相应的get() 方法。从这里( here)获取更多关于JSR-330的全部细节。


下面例子的配置仅仅只有一行,但是它对理解“为什么”和“怎么做”很重要:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- an HTTP Session-scoped bean exposed as a proxy -->
    <bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
        <!-- instructs the container to proxy 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>
</beans>

通过创建一个代理,你在范围Bean声明内插入了一个子<aop:scoped-proxy/> 元素。参考“选择创建代理的类型”章节和第40章基于XML模式的配置。为什么requestsession,globalSession 和自定义范围的bean需要<aop:scoped-proxy/> 元素?让我们解释下面的singleton Bean定义,与你上面提到的范围做对比。(下面的userPreferences Bean定义按目前来说还没有完成)。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
在上面的例子中, singleton Bean userManager 被注入了一个HTTPSession范围的userPreferencesBean引用。这里需要注意的点是userManager Bean是singleton的。它只会在每一个容器里被实例化一次,它的依赖(在这里仅仅有一个userPreferences Bean)仅仅被注入了一次。这意味着 userManagerBean仅仅对相同的userPreferences 对象进行操作,也就是,最开始注入的哪一个。

当把一个短存活范围的Bean注入到一个长存活范围的Bean中时,这不是你想要的行为,例如把一个HTTPSession范围的Bean注入到一个singleton Bean中。而对于在特定的HTTP Session生命周期期间你需要一个userPreferences 对象。因此容器创建了一个暴露相同公共接口的对象作为 UserPreferences(理想情况下是一个 UserPreferences 实例对象),这个类可以从范围界定机制(the scoping mechanism)下获取真正的UserPreferences 对象。容器给userManager bean注入了一个代理的对象,但你无法意识到这是一个代理。在这个例子中,当UserManager 实例调用了依赖注入的UserPreferences 实例的方法,它实际上调用了代理对象的方法。然后代理再从HTTPSession中获取实际的UserPreferences 对象,并且通过代理方法调用实际对象。

因此你需要下面的正确完整的配置,当你需要注入request-session-Bean给协作的对象时。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

选择创建的代理类型

默认的,当Spring容器创建一个被标记了<aop:scoped-proxy/> 元素的Bean的代理的时候,一个基于CGLIIB类的(CGLIB-based class)代理就会被创建。

[Note]

CGLIB代理仅仅拦截公共方法调用!不会调用代理的非public方法;他们也不会代理到实际的范围目标对象上。


另外,你可以配置Spring容器来为那个范围Bean创建一个标准的基于JDK接口的代理,通过指定<aop:scoped-proxy/>proxy-target-class属性为false 。用基于JDK的代理意味着你不需要在你的classpath中增加额外的类库来产生代理。但是,它也意味着你的范围Bean类必须实现至少一个接口,你所有要注入的Bean都需要通过它的接口。
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session">
    <aop:scoped-proxy proxy-target-class="false"/>
</bean>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>
了解更多选择基于类或基于接口代理的信息,看10.6章“代理机制”( Section 10.6, “Proxying mechanisms”)。

自定义范围

bean的范围机制是可拓展的;你可以定义你自己的范围,或甚至重新定义存在的范围,尽管后者认为是不好的习惯,并且你不能重写内建的singleton  prototype范围。


创建一个自定义的范围

为了把你自定义的范围集成到Spring容器中,你需要实现org.springframework.beans.factory.config.Scope 接口,这个接口将在本节讲解。对于如何实现你自己的范围,参考Spring容器自身的定义的范围实现和 ScopeJAVA文档,那里讲解了你需要实现的一些细节。

 Scope接口有四个方法从范围内获取对象,删除它们和允许它们销毁。

下面的方法从一个潜在范围返回一个对象。例如,session范围的实现,返回一个session范围的Bean(如果它不存在,方法返回一个新的bean实例,然后绑定到session上供未来引用)。

Object get(String name, ObjectFactory objectFactory)

下面的方法从潜在范围删除一个对象。例如session范围实现,从潜在的session删除一个session范围的bean。对象应该是被返回的,但是如果你对象指定的名字不存在,可以返回Null。

Object remove(String name)

下面的方法当范围销毁或者指定的对象的范围销毁时,需执行的注册回调方法。参考JAVA文档或者Spring范围实现了解更多关于销毁回调的信息。

void registerDestructionCallback(String name, Runnable destructionCallback)

下面的方法包含了一个潜在范围的会话标识。这个标识对每一个范围都是不同的。对于session范围实现,这个标识可以是session标识。

String getConversationId()

使用自定义范围


在你写完和测试过一个或多个自定义Scope 实现以后,你需要让Spring知道你的范围存在。下面的方法是注册一个新的范围的核心方法:

void registerScope(String scopeName, Scope scope);


这个方法声明在ConfigurableBeanFactory 接口,它在大多通过BeanFactory装配的Spring的ApplicationContext 实现都可用。


方法的第一个参数适合范围关联的唯一的名字;例如Spring框架自己的 singleton 和 prototype范围。第二个方法参数是你希望注册和使用的实际自定范围的实例。

假设你写了下面的自定义 Scope实现,并且像下面一样注册了它。


[Note]

下面的例子用了Spring包含的SimpleThreadScope例子,但是不是默认注册的。结构应该和你自己定义的 Scope实现是一样的。


Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);

你然后通过你自定义范围规则创建一个bean的定义:

<bean id="..." class="..." scope="thread">

对于你自己的 Scope实现,你不仅可以在程序里注册,你也可以通过CustomScopeConfigurer 类注册:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="thread">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="bar" class="x.y.Bar" scope="thread">
        <property name="name" value="Rick"/>
        <aop:scoped-proxy/>
    </bean>

    <bean id="foo" class="x.y.Foo">
        <property name="bar" ref="bar"/>
    </bean>

</beans>

[Note]

当你把<aop:scoped-proxy/>放到FactoryBean 实现时,工厂bean是被在范围内的,而不是通过getObject()返回的对象。






  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值