###7.5 Bean 的做用域html
当你建立一个bean定义时,你也建立了一个菜谱.你能够经过一个菜谱来建立bean的多个实例.java
你不只能够向bean里配置各类依赖和配置值,也能够定义bean的做用域.有7种做用域,你能够选择一种.你可使用5种web项目中独有的scopes. bean做用域web
Scope Description singleton 当ioc容器启动时就生成一个bean实例 prototype 定义一个bean能够生成无数bean实例 request bean定义的生命周期和HTTP请求相关.每一个http请求都有本身须要的bean的实例,web项目专用 session 定义一个与Http session生命周期相关的bean,web项目专用 globalSession http全局session的生命周期相关的.通常只用于Portlet环境.通常只在web相关的ApplicationContext中获取 application 定义一个bean定义与ServletContext相关. websocket 定义一个bean的做用域与一个WebSocket相关.
####7.5.1 单例做用域 一个单例Bean只有一个共享的实例,且全部能经过id或者ids匹配到bean的请求,spring容器都会返回一个特定的bean的实例. 另外一方面看,全部的单例bean都被容器统一辈子成,并存储到缓存里,有请求就到缓存里取出实例对象的引用.spring
spring 的单例Bean的概念不一样于GOF书里提到的那个单例模式.GoF的单例模式是硬编码来控制class的做用域,即每一个特定的类的实例只被ClassLoader加载一次.而spring的单例则是计算的是每一个容器每一个bean.这意味着若是你要在一个单一的spring容器只给一个class定义一个bean,那么spring容器会为该Bean加载有且只有一个实例.singleton是spring的默认做用域.要将一个bean定义为单例,能够这么写:编程
<bean id="accountService" class="com.foo.DefaultAccountService"/> <!-- 下面的例子和上面的是同样的 (singleton scope 是默认的) --> <bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
bean的非单例,原生做用域部署结果是当对一个特定的bean发起请求时,会建立一个bean的实例.这样,该bean会注入到其余bean里或者你能够经过getBean()方法调用容器来请求他.一般,使用prototype做用域来定义有状态的bean,使用singleton来定义无状态的bean.缓存
下面的图标代表如何使用spring prototype做用域.DAO对象通常不适合定义为原型,由于它不持有任何状态. 以下定义:安全
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
不一样于其余的做用域,spring不会管理原生bean的生命周期,容器会实例化,配置,另外还组装一个原生对象,或者把他扔给客户端,但不会对此有记录.所以,尽管初始化调用的方法调用不区分做用域,但对于prototype来讲,配置的销毁时的调用不会被调用.客户端必需要清理prototype做用域的对象,并释放它们占用的昂贵的资源.要使spring容器来释放原生做用域持有的bean对象,你可用使用一个自定义的bean post-processor,它持有全部要清理的beans的引用.websocket
必定程度上,spring 容器原生bean的角色是替代new 操做符.全部的生命周期都要客户端处理;session
###7.5.3 拥有原生bean依赖的单例bean 当你使用带着原生bean依赖的单例bean时,可用看到这些依赖在实例化时会被释放.若是有原生做用的依赖注入到一个单例bean中,一个新的原生bean就会实例化并注入到单例bean中.这个原生实例是有容器提供的专供给单例bean的. 可是,若是你想单例bean在运行期间得到一个原生bean的新实例.这个是不可能的.由于当容器初始化该singletion bean并解决依赖时,注入只发生一次.若是你想要在运行时获得新的实例,能够参看7.4.6,方法注入.mvc
这些bean是web项目专用的,你只能在spring ApplicationContext的web-aware应用中使用,例如XmlWebApplicationContext中才能获取. ###初始web配置 为了支持这五种做用域,定义这些bean以前须要一些少许配置;(single和prototype不须要). 如何完成这些配置取决于你的特定的Servlet环境.
若是你使用spring web mvc来获取做用域bean,实际上,这个请求是由DispatcherServlet或DispatcherPortlet处理的,然后不须要特定的步骤.DispatcherServlet和DispatcherPortlet早已暴露了一切相关状态.
若是你使用Servlet2.5的web 容器,请求不是有spring的DispatcherServlet来处理的,你须要注册这个org.springframework.web.context.request.RequestContextListener servletRequestListener.对于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,session做用域的bean在调用链中可用.
###请求做用域 以下的xml配置
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
当有请求发生时,spring容器会根据loginAction bean的定义来建立一个loginAction的实例.这是由于loginAction定义为http请求.你能够改变这个实例的状态,由于这个bean定义会产生不少实例,以对应每一个请求.当你的请求完成了,request做用域的bean就会被抛弃.
当你使用注解驱动组件或java配置时,@RequestScope注解将会用来标识一个组件为requst做用域.
@RequestScope @Component public class LoginAction { // ... }
思考一下的xml配置.
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
spring容器使用userPerferences的bean定义来建立了一个生命周期和HTTP Session 相同的userPerferences的实例.也就说,userperference Bean的定义是HTTP session 级别的.每一个单独的HTTP Session都有一个特定的userPreferences 的bean.当一个Http Seesion被抛弃的话,那么这个bean也会被抛弃.
注解驱动或java的配置形式以下:
@SessionScope @Component public class UserPreferences { // ... }
###全局Session做用域 思考一下bean的定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
同session的相同,不过只是基于web的portlet项目使用.由于它只有global portlet Session有关.
固然,你编写了一个标准的Servlet-based的web项目,只要写了session scope的bean,即便有多个globalSession scope的bean,也不会报错. ###Application scope 思考如下的配置
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
spring容器会建立web项目相关的AppPreference Bean.这个bean的做用域是ServletContext级别的,做为一个常规的ServletContext的属性存储.它能够看出是一个singleton,但不一样之处在于:它只是每一个ServletContext的singleton,singleton是针对每一个ApplicationContext的(一个应用中可能有好几个ApplicationContext).它实际是ServletContext的一个属性.
你可使用 @ApplicationScope注解来代表其做用域
@ApplicationScope @Component public class AppPreferences { // ... }
###Scoped beans as dependencies
spring 容器不只管理对象的实例化,还要组装其协做对象.若是你想将一个request 做用域的bean注入到一个更长时间的bean里,你须要选择一个AOP代理的注入来替代这个request scope Bean.你须要注入一个和该scope Bean有相同接口的代理对象,能够在相关的做用域(例如http请求)中获得真实的目标对象,代理要调用该真实对象的方法.
你能够在single Beans中使用aop:scoped-proxy/标签,这个引用就能够经过相关的代理进行序列化和反序列化,这样就能从新得到单例bean的实例
你能够在原型bean之间使用aop:scoped-proxy/,在公共代理上每次方法的调用均可以从新建立该方法目标bean新的实例.
然而,做用域代理不是惟一的以生命周期安全的方法去获取更小做用域bean的方式.你能够简单的将你的注入点(如构造器/set方法 参数或者自动注入字段)宣布为ObjectFactory<MyTargetBean>,它容许你每次使用getObject()方法来按需获取实例,不论是否已持有该对象或存储该对象.
jsr-330 将该变种成为供应商(Provider).使用一个Provider<MyTargetBean>声明,每次获取要调用相应的get()方法.
以下的配置只是简单的一行,但你要明白这背后的缘由和how
<?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>
建立一个代理,你将aop:scoped-proxy/元素插入到scoped bean定义中.为何request,session,globalSession,custom-scope级别的bean定义须要aop:scoped-proxy/元素呢?让咱们分析一下单例bean的定义并对比为何你要对上面的bean进行什么额外定义.
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/> <bean id="userManager" class="com.foo.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
上面的例子中,单例bean userManager将注入Session-scope 的bean userPreferences的实例.须要注意的是userManager是一个单例Bean,每一个容器它只实例化一次,它的依赖也只注入一次.这意味着userManager实际上使用的都是相同的userPreferences对象.
你不须要将短生命的协做bean注入到长生命周期里,例如你不打算将session-scoped 协助bean注入到单例bean里.实际上,你须要一个单例的userManager对象,对于http Session的生命周期,你须要一个特定于每一个http Session的UserPreferences对象.所以容器会建立一个拥有userPreference全部接口的对象,它还能够从做用域机制中获得真实UserPreference对象.容器将会把一个代理对象注入到UserManager bean里,bean却发现不到这点.在这个例子中,当UserManager实例调用了UserPreference的一个方法,它其实是调用了代理对象的一个方法.这个代理对象会从HTTP Session中获取真正的UserPreference对象,而且代理已得到UserPreference对象的须要被调用的方法.
因此当你将request,session,globalSession-scoped 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>
####选择正确的代理类型 Choosing the type of proxy to create 默认的,当spring容器为标记aop:scoped-proxy/元素的bean建立一个代理时,一个CGLIB-based 类代理就会建立.
另外,你可用经过配置spring容器为这些scoped bean建立标准的JDK 接口代理,你要指明aop:scoped-proxy/元素中的proxy-targer-class属性为false.使用JDK 基于接口的代理意味着你的应用不须要额外的jar包来影响这些代理.可是,这也意味着你的scoped bean的class至少要实现一个接口,而且全部的协做对象必须经过接口才能注入这些scoped 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>
对于选择class-based后者interface-based代理,查看11.6章节,"proxy mechanisms".
###7.5.5自定义做用域(Custom scopes) bean做用域机制的扩展,你可用定义本身的做用域,甚至能够重定义已存在的做用域.尽管从新定义做用域是一个坏的实践,并且你不能重写内置的singleton和prototype做用域. ####新建一个自定义做用域(Creating a custom scope) 要将你的自定义做用域集成到spring容器,你须要实现org.springframework.beans.factory.config.Scope接口,本节会描述. Scope接口有四种方法从scope里获取对象,把它们从scope移除,并使他们销毁.
下面的方法是从如下的scope中返回对象.例如,session scope实现的将返回一个session-scoped 的bean(若是这个bean的实例不存在,这个方法会从新建立一个bean的新实例,并把它绑定到session中以便之后调用).
Object get(String name, ObjectFactory objectFactory)
下面的方法将从做用域中移除对象.仍是以session scope 的实现为例,将会从如下的session里移除session-scoped bean.这个对象会返回,可是你若是这个带名词的对象找不到就会返回null.
Object remove(String name)
下面的方法注册做用域应该执行的回调,当这些它被销毁或当在此做用域中特定的对象被销毁时.查看javadoc或spring scoped实现来查看更多的销毁回调信息.
void registerDestructionCallback(String name, Runnable destructionCallback)
下面的方法会获得一个该做用域下的会话标志.这个标志不一样于其余scope.对于一个session scoped实现来讲,这个标志多是session标志 ####Using a custom scope (使用自定义注解) 在你编写并测试你的自定义scope时,你须要让你的spring容器知道这些新的scope.下面的方法是spring注册新的scope的核心方法:
void registerScope(String scopeName, Scope scope);
这个方法是由ConfigurableBeanFactory接口申明的,它能够在大部分spring的ApplicationContext接口的实现类经过Beanfactory属性获的. registerScope(..)方法的第一参数是与scope相关的惟一命名,例如spring容器里singleton和prototype.第二个参数是你要注册的已经实现了Scope接口的类的实例.
你编写了一个本身的接口,并如此实现它.
Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope);
而后你可用使用你的自定义的scope来建立bean的定义.
<bean id="..." class="..." scope="thread">
对于已实现的自定义scope,你不只能够经过编程的方式去注册,也能够经过xml的方式去注册.你须要用到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>
当你将aop:scoped-proxy/放到一个 FactoryBean 实现里,因为这个工厂Bean自身的做用域,因此getObject()方法不会返回任何对象.