Spring 之 Bean的范围

来源:互联网 发布:伴读软件 编辑:程序博客网 时间:2024/05/18 00:00

当你创建一个bean定义时,你创建了一个菜谱来创建由这个bean定义的类的实际的实例。那个想法,一个bean定义是一个菜谱,是很重要的。因为其意味着,由于作为一个类,你可以从一个单一菜谱中创建许多对象实例。


 你不仅可以控制多种依赖和配置值,这些是要插入一个由特别bean定义的对象,而且也能控制这个对象的范围。这个方式很强大并且很灵活。你可以选择由配置创建对象的范围。替代得在Java类级别已经固定的对象的范围。Beans可以定义为大量范围中的一个:拆盒即用,Spring框架支持5中范围,其中可用的三个仅当使用了web提醒的ApplicaitonContext。


下面的范围支持开箱即用,你也可以创建自定义范围。



表5.3 Beans 范围

范围                     描述

singleton            对于每个Spring IoC容器的单个bean实例的单个bean定义的范围  只创建该bean的唯一实例,所有请求和引用都只使用这个实例

prototype            对于任何对象实例的单个bean定义的范围,每次请求都创建此bean的实例

request               每次HTTP请求生命周期的单个bean定义范围;即,每个HTTP请求返回一个bean实例。仅在ApplicationContext的上下文中有效

session             单个bean定义的HTTP会话生命周期的范围。仅在ApplicationContext的上下文中有效

global session  单个bean定义的全局HTTP会话的生命周期。一般地在门户导入的信息组件的上下文中有效。仅在ApplicationContext的上下文中有效

application        单个bean定义的一个ServletContext的生命周期。仅在ApplicationContext的上下文中有效


在Spring 3.0中,虽然线程范围是可用的,但是不会默认地注册。更多的可以看SimpleThreadScope。



5.5.1 The singleton scope


仅仅管理一个singleton bean的一个共享的实例,并且beans(其ids或者id匹配bean定义)的所有请求,导致Spring 容器返回一个特殊的bean实例。


换句话说,当你定义一个bean定义并且其范围为singleton时,Spring IoC容器确实创建了这个bean定义定义的对象的实例。这个单个实例存储在singleton bean的缓存中,并且所有后来的对着命名bean的请求和引用返回这个缓存对象。


Spring的singleton bean的概念与设计模式中的单例模式不同。单例模式硬编码了一个对象的范围,每个ClassLoader只创建特殊类的唯一实例。Spring singleton的范围最好描述为一个容器一个bean。意味着,如果你在单个Spring容器内为一个特别的类定义一个bean,那么Spring容器仅仅创建了那个bean定义的类的一个实例。singleton范围是Spring的默认范围。为了在XML中定义一个singleton 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"/>

5.5.2  The prototype scope

bean部署为非singleton,而是prototype范围导致每次请求一个bean时创建一个新的bean实例。即,这个bean注入另一个bean或者你在容器上调用getBean()方法请求它。一般来说,对于所有有状态的beans使用prototype范围并且对于非状态的beans使用singleton范围。


下面的图显示了Spring的prototype范围。一个数据访问对象(DAO)通常不配置成prototype,因为典型的DAO不能持有任何会话式状态;



下面的例子在XML中定义了prototype:

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

在与其他范围的对比中,Spring不管理一个prototype bean的完整生命周期:容器实例化,配置,否则封装一个prototype对象,并且交由客户端处理,没有这个prototype实例更进一步的记录。虽然所有对象,不管范围,都要调用初始化生命周期回调方法,针对prototype这种情况,配置的销毁生命周期回调不会调用。客户端代码必须清除prototype范围的对象并且释放prototype beans持有的昂贵资源。为了使Spring容器释放由prototype范围的bean持有的资源,尝试使用自定义的bean post-processor,其拥有需要清除bean的依赖。


在一些方面,Spring 容器关于prototype范围的角色是Java New 操作符的替代。所有生命周期经过这个点的管理必须由客户端管理.



5.5.3 有prototype-bean依赖的Singleton bean

当你使用singleton 范围的bean,并且其有peototype范围的bean作为依赖,注意其依赖要在初始化时解决掉。如果你将一个prototype范围bean依赖注入到一个singleton范围的bean,实例化一个新的prototype范围的bean并注入到singleton范围的bean中。这个prototype实例是唯一一个实例,将应用这个singleton 范围的bean。


然而,假设你想这个singleton 范围的bean在运行时重复获取一个新的prototype范围bean的实例。你不能注入一个prototype范围bean到你的singleton范围的bean,因为只注入一次,当Spring容器实例化singleton范围的bean并且完成其依赖注入。如果你在运行期不止一次需要一个prototype范围bean的实例,请参考方法注入。


5.5.4 请求,会话和全局会话范围


如果你使用了web-aware的Spring ApplicationContext实现(比如XmlWebApplicationContext),request,session和global session才是可用的。如果在常规的Spring IoC容器(比如ClassPathXmlApplicaitonContext)中使用上述范围,关于那些未知bean范围将抛出一个IllegalStateException。


初始化web配置

为支持这章讨论的beans的范围,在定义你的beans之前需要一些初始化bean(对于singleton,prototype标准范围不需要初始化设置)


如何完成初始化设置取决于你的Servlet环境


如果在Spring Web MVC内访问范围beans,实际上,由Spring的DispatchServlet或者DispatchPortlet处理一个请求,稍后没有特殊的启动是必须的:DispatchServlet和DispatchPortlet已经暴露了所有相关的状态。


如果你使用Servlet 2.5 容器,在Spring的DispatchServlet的外部处理请求(比如当使用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>


或者,如果对于你的监听启动有一些问题,考虑提供的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, RequestContextListenerRequestContextFilter全部都走相同的事情,即将HTTP请求对象绑定到线程,这个线程为请求服务。这使得request和session范围的beans在调用链中用的更长。


请求范围

考虑下面的bean定义

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

Spring容器通过使用loginAction 定义(针对每个HTTP请求)创建了LoginAction bean的新的实例。即,loginAction的范围是HTTP请求级别。你可以改变实例的内部状态,如你所想的。因为来自相同loginAction bean定义创建的实例将不会看到状态的这些变化;它们特定于单个请求。当那个请求完成处理时,request范围的bean就丢弃了。


会话范围

考虑下面的bean定义

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

Spring容器创建了UserPreferences bean的一个新的实例,对于单个HTTP会话的生命周期使用userPreferences bean定义。换句话说,userPreferences bean的范围是HTTP session级别的。作为request范围的bean,你可以根据自己需要改变创建的实例的内部状态,其他HTTP会话实例(由相同的userPreferences bean定义创建的实例)看不到这些状态的变化,因为它们特定于单个HTTP会话。当这个HTTP会话最后销毁时,那个与这个特定HTTP会话关联的关联的bean也就销毁了。


全局会话范围


考虑下面的bean定义

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

global session范围与标准HTTP会话范围相似,仅应用于基于门户web应用程序的上下文中。门户应用程序规范定义了global session的概念,在所有的门户应用(组成了单一的门户web应用程序)中共享,定义了global session范围的beans绑定到全局门户会话的生命周期。


如果你写一个标准的基于Servlet的web应用程序并且你定义了一个或者更多的范围为global session的beans,就使用标准的HTTP会话范围,并且不会发生任何错误。


应用范围

考虑下面的代码

<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>

Spring容器创建了AppPreferences bean的一个新的实例,对于整个web应用程序仅使用一次appPreferences bean 定义。即,appPreferences bean是ServletContext级别的范围,存储在一个ServletConetext属性中。这与Spring的singleton bean有点相似,但是了有两个方面不同:其一是每个ServletContext对应一个singleton,而不是每个Spring的ApplicationContext,(在所给的web应用程序中可能有几个Spring ApplicationContext),并且它是暴露的因此ServletContext属性是可见的。



作为依赖的范围beans


Spring IoC容器不仅仅管理你的对象的实例化,也要与合作者(依赖)关联。如果你想将一个HTTP 请求范围的bean注入到另一个bean中,你必须注入一个AOP代理替代范围bean。即,你需要注入一个代理对象(其暴露了相同的public 接口),作为第二个范围对象,但是了也可以从相关的范围和调用真实对象的委托方法重新获取真正的目标对象。


注意: 你不必要使用<aop:scoped-proxy/>来关联范围为singleton或者prototype的beans


下面的配置只有一行,但是对于理解为什么这样做很重要。


<?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/>。为什么bean定义在request,session,global session和自定义范围级别需要<aop:scoped-proxy/>元素?让我们来看下面的singleton bean 定义并与你所需要的上述范围进行对比。(下面的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注入到HTTP 会话范围bean userPreferences的引用中。这里的重点是userManager bean 其范围为singleton:每个容器每次只实例化一次,并且其依赖(这里是userPreferences)也只注入一次。这就意味着userManager bean将智能在相同的userPerferences对象上操作,即,原始注入的bean。



当你将短生命范围的bean注入到唱生命范围的bean时,这不是你想要的行为,例如,将一个HTTP会话范围的依赖bean注入到一个singleton范围的bean中。不如说,你需要单个userManager对象,并且对于一个HTTP会话的生命周期,你需要一个userPreferences对象。容器创建了一个对象(暴露了UserPreferences类相同的公共接口),理想情况是其为UserPreferences实例,可以从范围机制中再次获取真正的UserPreferences对象(HTTP 请求,会话等等)。容器将这个代理对象注入到userManager bean中,其对于UserPreferences引用是一个代理是不可知的。在这个例子中,当UserManager实例调用了依赖注入到UserPreferences对象的方法,它调用了代理的一个方法。代理稍后从HTTP会话中重新获取UserPreferences对象,并在重新获取的真实的UserPreferences对象上委托了方法调用。


稍后你需要下面的,正确的和完整的配置,当注入request,session和global-session beans 到合作的对象时,


<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容器创建了bean的代理,其由元素<aop:scoped-proxy/>标识,并创建了基于CGLIB类代理。


注意: CGLIB代理仅仅拦截public 方法调用。不调用这个代理的非public方法。它们将不会委托给真实的范围目标。


作为可选地,你可以配置Spring容器为这样范围beans创建基于接口代理标准的JDK,通过指定元素<aop:scoped-proxy/>的属性proxy-target-class的值为false。使用基于接口代理的JDK意味着在你的应用系统路径中不需要额外的包来影响这样的代理。然而,它也意味着这样范围的beans必须至少实现一次这个接口,并且注入到这个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>



0 0
原创粉丝点击