当建立bean的定义时,就建立了如何建立类实例的规则。bean定义是一个规则的思想很重要,由于这意味着能够从一个规则建立许多对象的实例,与类同样。java
从一个特定的bean定义中建立的bean,不只能够控制它的各类依赖关系和配置值,还能够控制对象的做用域。这种方法是强大、灵活的,能够经过配置选择要建立对象的做用域,而不须要在Java类的层面。能够为Bean指定许多做用域中的一个,而且开箱即用,Spring Framework支持七个做用域,其中有五个只在基于web ApplicationContext中有效。web
下面的做用域开箱即用。也能够建立用户自定义的做用域。spring
从Spring 3.0开始,线程做用域可用,但默认状况下未注册。编程
只管理一个共享的singleton bean 的实例;而且全部使用id的bean请求若是匹配到这个bean定义,Spring容器都返回同一个特定的bean的实例。设计模式
换句话说,当定义一个bean定义并将它的做用域设置为singleton,Spring IoC容器将仅建立一个由该bean定义指定的对象的实例。这个单独的实例存储在singleton bean的缓存中,对该命名bean的全部后续请求和引用都返回缓存的对象。缓存
Spring的singleton bean的概念有别于GoF 设计模式书中的单例模式。GoF单例硬编码对象的做用域,使得每一个ClassLoader只建立一个特定类的实例。每一个容器一个bean是对Spring singleton做用域的最好描述。若是在单个Spring容器中为指定的类定义一个bean,则Spring容器将建立由bean定义指定的类的惟一一个实例。Singleton做用域是Spring bean的默认做用域。使用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"/>
使用prototype 的bean部署方式会在每次请求一个特定bean的时候都建立一个bean的实例。对bean的每次请求意味着将bean注入到其它bean中或者经过调用容器的getBean()方法请求它。应该对有状态的bean使用prototype做用域,对无状态的bean使用singleton做用域。websocket
下图阐述了Spring prototype 做用域。注意,一个dao通常不配置成prototype,由于一个典型的DAO不保持任何的会话状态;使用这幅图仅仅是由于能够重用singleton图的核心内容。session
下面的例子在XML中将一个bean定义为prototype:app
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
相对于其它做用域,Spring无论理prototype bean 的整个生命周期:容器实例化、配置并装配一个prototype对象,最后将其交给客户,此后再也不记录prototype实例。所以,尽管全部做用域的bean的initialization生命周期的回掉函数都会被调用,可是在prototype状况下,配置的destruction生命周期回掉函数不会被调用。客户端代码必须本身清楚prototype对象并释放它持有的昂贵资源。为了让Spring容器释放prototype bean的资源,可使用自定义的bean post-processor,它会持有须要清理的bean的引用。
在某些方面,Spring 规则认为prototype bean是Java new 操做法的替代。全部new以后的生命周期都必须由客户端管理。
当你使用有prototype bean依赖的singleton bean时,要注意依赖关系是在实例化时被解析。所以若是你依赖注入一个prototype bean到singleton bean中,一个新的prototype bean 会被实例化而且被依赖注入singleton bean。提供给singleton bean的prototype的实例是惟一的。
可是,假设你想在运行时让singleton bean每次都获得一个prototype bean的新实例。你就不能将prototype bean依赖注入到singleton bean中,由于注入仅仅发生一次,是在Spring容器实例化singleton bean并解析和注入依赖关系时。若是想要在运行时获取prototype bean的新实例不止一次,可使用“方法注入”。
Request,session,global session,application 和 WebSocket 做用域仅在使用基于web的Spring应用上下文(例如XmlWebApplicationContext)中可用。若是在普通的Spring IoC容器(例如ClassPathXmlApplicationContext)中使用这些做用域,会抛出IllegalStateException异常来讲明未知的bean做用域。
为支持Request,session,global session,application 和 WebSocket 做用域,在定义bean以前须要一些基础的初始化配置。(对于标准的做用域,singleton和prototype,不须要这些初始化配置)。
如何进行初始化设置取决于不一样的Servlet环境。
若是你使用Spring Web MVC来得到做用域中的bean,实际上就是使用Spring的DispatcherServlet或者DispatcherPortlet来处理一个请求,那么不须要特别的配置:DispatcherServlet和DispatcherPortlet已经暴露了全部相关的状态。
若是使用Servlet 2.5 的 web 容器,而且在Spring的DispatcherServlet以外(例如使用JSF或者Struts)处理请求,须要注册org.springframework.web.context.request.RequestContextListener和ServletRequestListener。对于Servlet 3.0以上,还可使用WebApplicationInitializer接口经过编程的方法实现。使用第一种方法,或者对旧版本的容器,在web应用的web.xml中添加如下的声明:
<web-app> ... <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> ... </web-app>
若是配置listener时出现问题,可使用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定义:
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
Spring容器使用loginAction bean 定义为每一个HTTP 请求建立一个LoginAction bean的实例。即loginAction bean 的做用域被设置为Http request级别。能够为所欲为的改变实例的内部状态,由于从相同的loginAction bean 定义建立的其它实例对这个状态的修改不可见;它们特定于单个请求。当请求处理完成,request做用域的bean就失效了。
当使用注解的方式配置,@RequestScope注解用来指定request做用域。
@RequestScope @Component public class LoginAction { // ... }
对于下面的XML格式的一个bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
Spring容器使用userPreferences bean定义为每一个HTTP Session生命周期建立一个UserPreferences bean的实例。换言之,userPreference bean的做用域被设置为Http session级别。能够为所欲为的改变实例的内部状态,由于从相同的userPreference bean 定义建立的其它实例对这个状态的修改不可见;它们特定于每一个会话。当会话结束,session做用域的bean就失效了。
当使用注解的方式配置,@SessionScope注解用来指定session做用域。
@SessionScope @Component public class UserPreferences { // ... }
对于下面的XML格式的一个bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
globalSession做用域与标准的HTTP session做用域类似,而且仅应用于基于portlet的web应用的上下文。portlet文档定义了global session的概念——在全部组成一个portlet web应用的所用portlet中共享的会话。globalSession bean的做用域与global portlet session的生命周期一致。
若是是在标准的基于Servlet的web应用程序中定义了globalSession做用域,会使用标准的Http session做用域,不会发生错误。
对于下面的XML格式的一个bean定义:
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
Spring容器在整个web应用中使用appPreferences bean定义仅仅建立一次AppPreferences的新实例。即appPreferences bean的做用域是ServletContext级别的,它被保存为ServletContext属性。它与Spring singleton bean在某种程度上类似,但有如下两点重要的不一样:它对于每一个ServletContext是单例,而不是每一个ApplicationContext(一个web应用中可能有多个ApplicationContext);它被暴露为一个ServletContext属性。
当使用注解的方式配置,@ApplicationScope注解用来指定application做用域。
@ApplicationScope @Component public class AppPreferences { // ... }
Spring IoC容器不只管理对象的实例化,还组织它们之间的协做(或者依赖)。若是你想注入一个HTTP request做用域的bean到另一个更长生命周期的bean,也许得选择注入一个AOP代理来替代做用域bean。也就是说,须要注入一个代理对象来暴露与做用域bean相同的公有接口,能够在相关的做用域(例如HTTP request)取回真正的目标对象而且在实际的对象上调用代理方法。
能够在singleton bean之间使用aop:scoped-proxy/,经过可序列化的中间代理的引用,能够在从新得到目标单例bean时将其反序列化。
当声明了一个指向prototype做用域的aop:scoped-proxy/时,共享代理的每一次方法调用都会建立新的目标bean.
做用域代理并非获取做用域较小的bean惟一的生命周期安全的途径。也能够简单的声明注入点(即构造函数/setter方法的参数或者自动装配的字段)为ObjectFactory<MyTargetBean>,它容许在每次须要的时候调用getObject()方法获取如今的实例,而不用持用实例或单独保存它。
JSR-330将其称为Provider,使用Provider<MyTargetBean>声明和相关的get()方法调用。
下面的例子中仅有一行配置,可是去理解背后的原理很重要:
<?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/来建立一个代理。为何做用域为request 、session、globalSession和用户自定义做用域的bean须要aop:scoped-proxy/元素?能够分析接下来的单例定义并与上述定义作比较(注意,下面的userPreferences 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有一个Http session做用域的bean userPreferences的引用被注入。突出的问题是userManager bean是一个单例:每一个容器只会实例化一次,而且它的依赖关系(在这里只有一个userPreferences bean)也只被注入一次。这意味着userManager bean仅仅只会操做一个相同的userPreferences对象,即最开始注入的那个。
这并非将一个短生命周期bean注入到长生命周期bean所须要的行为,例如将一个HTTP session做用域的bean注入到singleton bean中。而 你须要一个userManager对象的单例、每一个session须要一个userPreference对象。所以容器建立了一个对象用来暴露UserPreferences类的相同公有接口,能够经过这个对象来获取做用域(HTTP request,Session等)机制上的实际UserPreferences对象。容器将这个代理对象注入userManager bean,并不知道这是UserPreferences 引用的一个代理。在这个例子中,当UserManager实例调用被依赖注入的UserPreferences对象的方法时,它其实是调用了代理的方法。而后代理获取HTTP Session(在这个例子中)上的实际的UserPreferences对象,而且代理了实际UserPreferences对象的方法调用。
所以,当注入request、session和globalSession做用域的协做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>
默认状况下,当使用aop:scoped-proxy/元素来为bean建立一个代理,使用的是基于CGLIB的类代理。
CGLIB代理仅仅拦截公有方法调用。在此代理商不要待用非公有方法;这不会委派给实际的做用域目标对象。
做为替代,能够配置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>
bean做用域机制是可扩展的;能够定义本身的做用域,或者重定义现有的做用域,尽管重定义被认为是一种很差的实践而且不能覆盖内建的singleton和prototype做用域。
为了集成自定义做用域到Spring容器,须要实现org.springframework.beans.factory.config.Scope接口。如何实现自定义的做用域,能够查看Spring Frameworkd自身提供的Scope实现和Scope javadocs,它更详细的解释了须要实现的方法。
Scope接口有四个方法用来从做用域获取对象、从做用域删除对象,和容许它们被销毁。
下面的方法从底层做用域返回对象。例如,会话做用域的实现返回session做用域的bean(若是不存在,方法在绑定bean的新实例到session以后返回它)。
Object get(String name, ObjectFactory objectFactory)
下面的方法从底层做用域移除对象。方法须要返回对象,可是当指定名称的对象不存在时能够返回null。
Object remove(String name)
下面的方法注册当scope被销毁时或scope中的指定对象被销毁时,scope须要执行的回掉函数。
void registerDestructionCallback(String name, Runnable destructionCallback)
下面的方法获取底层做用域的会话标识符。不一样做用域的标识符不一样。对于会话做用域的实现,这个标识符能够是会话的标识符。
String getConversationId()
在测试几个用户自定义做用域实现后,须要让Spring容器了解你的新做用域。下面的方法是注册新做用域的核心方法:
void registerScope(String scopeName, Scope scope);
这个方法在ConfigurableBeanFactory接口中声明,在大多数的具体ApplicationContext实现上可用。
registerScope(...)方法的第一个参数是与做用域相关的惟一名称;Spring容器自身的名称例子是singleton和prototype。第二个参数是一个要注册和使用的Scope实现的实例。
假设实现一个Scope,能够用以下方法定义:
下面的例子中使用的SimpleThreadScope包含在Spring中,可是默认未被注册。这个注册方法对用户自定义的Scope实现相同。
Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope);
而后就能够建立遵循自定义范围规则的bean定义:
<bean id="..." class="..." scope="thread"/>
对于自定义的Scope实现,不只限于编程的注册方法。也可使用声明式的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>
当在FactoryBean实现中放置aop:scoped-proxy/元素,它做用域工厂bean自身,而不是它的getObject()方法返回的对象。