spring -ioc-bean scope 做用域

###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

singleton Bean定义

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"/>

7.5.2 原生类(prototype)做用域

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

7.5.4 request,session,global session,application,and Websocket scopes

这些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 {
		// ...
	}

Session scope

思考一下的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 类代理就会建立.

  • cglib 代理只拦截公共方法的调用.不要在这个代理调用非公共方法,他们(代理)没法代理整个实际对象.

另外,你可用经过配置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()方法不会返回任何对象.

相关文章
相关标签/搜索