7.5 Bean scopes bean作用域
@sunRainAmazing
创建bean定义时,您将创建一个用于创建由该bean定义定义的类的实际实例的配方。 bean定义是一个配方的想法很重要,因为这意味着,与类一样,您可以从单个配方创建许多对象实例。
您不仅可以控制要插入到从特定bean定义创建的对象中的各种依赖关系和配置值,还可以控制从特定bean定义创建的对象的范围。这种方法是强大和灵活的,您可以通过配置来选择您创建的对象的范围,而不必在Java类级别上烘烤对象的范围。 Bean可以被定义为部署在许多范围之一中:开箱即用,Spring Framework支持七个范围,其中五个范围只有在使用Web感知的ApplicationContext时才可用。
以下范围支持开箱即用。您还可以创建自定义作用域范围。
表7.3 Bean作用域
【注】从Spring 3.0开始,线程范围可用,但默认情况下未注册。 有关更多信息,请参阅SimpleThreadScope的文档。 有关如何注册此或任何其他自定义范围的说明,请参阅”使用自定义范围”一节。
7.5.1单例模式
只管理一个单例bean的一个共享实例,并且对于具有与该bean定义匹配的id或id的bean的所有请求都会导致Spring容器返回一个特定的bean实例。
换句话说,当您定义一个bean定义并将其定义为单例时,Spring IoC容器将仅创建由该bean定义定义的对象的一个实例。 该单个实例存储在这样的单例bean的缓存中,并且该命名bean的所有后续请求和引用返回缓存的对象。
Spring的单例概念与”四人帮”(GoF)模式书中定义的Singleton模式不同。 GoF Singleton硬编码对象的范围,使得每个ClassLoader创建一个特定类的一个实例。 Spring单体的范围最好按容器和每个bean进行描述。 这意味着如果在单个Spring容器中为特定类定义一个bean,则Spring容器将创建由该bean定义定义的类的唯一一个实例。 单例范围是Spring中的默认范围。 要将XML定义为XML中的单例,您可以编写,例如:
<bean id="accountService" class="com.foo.DefaultAccountService"/>
<! - 以下是等效的,尽管冗余(单例范围是默认值) - >
<bean id="accountService"class="com.foo.DefaultAccountService" scope="singleton"/>
7.5.2原型模式
bean的部署的非单例,原型范围导致在每次对该特定bean进行请求时创建一个新的bean实例。 也就是说,bean被注入到另一个bean中,或者通过容器上的getBean()方法调用来请求它。 通常,对于无状态bean,可以使用所有有状态bean的原型范围和单例范围。
下图说明了Spring原型范围。 数据访问对象(DAO)通常不被配置为原型,因为典型的DAO不具有任何会话状态; 这个作者更容易重用单身图的核心。
以下示例将bean定义为XML中的原型:
<bean id="accountService" class="com.foo.DefaultAccountService"scope="prototype"/>
与其他范围相反,Spring不管理原型bean的完整生命周期:容器实例化,配置或以其他方式组装原型对象,并将其交给客户端,而不再具有该原型实例的记录。因此,尽管在所有对象上调用初始化生命周期回调方法,无论范围如何,在原型的情况下,配置的销毁生命周期回调未被调用。客户端代码必须清理原型范围对象并释放原型bean所持有的昂贵资源。要使Spring容器释放由原型范围的bean保存的资源,请尝试使用自定义bean后处理器,该后端处理器可以引用需要清除的bean。
在某些方面,Spring容器对原型范围bean的作用是Java新操作符的替代。所有过去的生命周期管理都必须由客户处理。 (有关Spring容器中bean的生命周期的详细信息,请参见第7.6.1节”生命周期回调”。)
7.5.3具有原型bean依赖关系的单例bean
当您使用具有依赖于原型bean的单例范围bean时,请注意在实例化时解析依赖关系。因此,如果依赖性将原型范围的bean注入到单例范围的bean中,则将一个新的原型bean实例化,然后依赖注入到单例bean中。原型实例是提供给单例范围bean的唯一实例。
但是,假设您希望单例范围的bean在运行时反复获取原型范围的bean的新实例。您不能依赖 - 将一个原型范围的bean注入到单例bean中,因为当Spring容器实例化单例bean并解析并注入其依赖项时,注入仅发生一次。如果在运行时多次需要一个原型bean的新实例,请参见第7.4.6节”方法注入”
7.5.4请求,会话,全局会话,应用程序和WebSocket范围
只有使用Web感知的Spring ApplicationContext实现(如XmlWebApplicationContext),
request, session, globalSession, application, and websocket 作用域才可用。如果将这些范围与常规的Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,那么将引发一个IllegalStateException对未知的bean作用域的异常。
初始Web配置
为了支持请求的会话bean,globalSession,应用程序和websocket级别(web-scoped bean)的bean范围,在定义bean之前需要一些小的初始配置。 (标准范围,单例和原型不需要此初始设置。)
如何完成此初始设置取决于您的特定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>
或者,如果您的监听器设置有问题,请考虑使用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请求对象绑定到为该请求提供服务的线程。 这使得请求和会话范围的bean在呼叫链上进一步可用。
Request scope 请求作用域
考虑以下用于bean定义的XML配置:
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
Spring容器通过为每个HTTP请求使用loginAction bean定义来创建一个新的LoginAction bean实例。 也就是说,loginAction bean的作用域在HTTP请求级别。 您可以根据需要更改创建的实例的内部状态,因为从同一个loginAction bean定义创建的其他实例将不会在状态中看到这些更改; 它们特别针对个人请求。 当请求完成处理时,作用于该请求的bean将被丢弃。
使用注释驱动组件或Java Config时,可以使用@RequestScope注释将组件分配给请求作用域。
@RequestScope
@Component
public class LoginAction {
//...
}
Session scope
考虑以下用于bean定义的XML配置:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
Spring容器通过在单个HTTP Session的生命周期中使用userPreferences bean定义来创建UserPreferences bean的新实例。 换句话说,userPreferences bean在HTTP会话级别被有效地限定。 与请求范围的bean一样,您可以根据需要更改创建的实例的内部状态,知道还使用从相同userPreferences bean定义创建的实例的其他HTTP会话实例在状态下看不到这些更改 ,因为它们是特定于单个HTTP会话的。 当HTTP Session最终被丢弃时,范围限定于该特定HTTP Session的bean也被丢弃。
当使用注释驱动的组件或Java Config时,可以使用@SessionScope注释将组件分配给会话作用域。
@SessionScope
@Component
public class UserPreferences {
//...
}
Global session scope
考虑以下用于bean定义的XML配置:
<bean id="userPreferences" class ="com.foo.UserPreferences" scope="globalSession"/>
globalSession范围与标准HTTP会话范围(如上所述)相似,仅适用于基于Portlet的Web应用程序的上下文。 portlet规范定义了构成单个Portlet Web应用程序的所有portlet之间共享的全局会话的概念。 在globalSession范围定义的Bean被限定(或绑定)到全局portlet Session的生命周期。
如果您编写基于标准的基于Servlet的Web应用程序,并将一个或多个bean定义为具有globalSession范围,则使用标准HTTP会话范围,并且不会引发错误。
Application scope
考虑以下用于bean定义的XML配置:
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
Spring容器通过为整个Web应用程序使用一个AppPreferences bean定义来创建AppPreferences bean的新实例。 也就是说,appPreferences bean的作用域是在ServletContext级别,作为一个常规的ServletContext属性存储。 这有点类似于Spring单例bean,但是在两个重要方面有所不同:它是每个ServletContext的单例,而不是每个Spring的ApplicationContext(在任何给定的Web应用程序中可能有几个),它实际上是暴露的,因此 可视为ServletContext属性。
使用注释驱动组件或Java Config时,可以使用@ApplicationScope注释将组件分配给应用程序作用域。
@ApplicationScope@Component
public class AppPreferences {
//...
}
Scoped beans as dependencies
Spring IoC容器不仅管理对象(bean)的实例化,还管理协作者(或依赖关系)的连线。 如果要将HTTP请求范围bean(例如)注入到长寿命范围的另一个bean中,您可以选择注入AOP代理来代替作用域bean。 也就是说,您需要注入一个代理对象,该对象暴露与作用域对象相同的public接口,但也可以从相关范围(例如HTTP请求)中检索真实目标对象,并将方法调用委托给真实对象。
【注】您还可以在范围为单例的bean之间使用<aop:scoped-proxy />
,然后引用可以序列化的中间代理,因此可以在反序列化时重新获取目标单例bean。
当对范围原型的bean声明<aop:scoped-proxy />
时,共享代理上的每个方法调用将导致创建一个新的目标实例,该调用然后被转发到该目标实例。
此外,范围代理不是以生命周期安全的方式从较短范围访问bean的唯一方法。您也可以简单地将注入点(即构造函数/设置参数或自动连线字段)声明为ObjectFactory <MyTargetBean>
,允许getObject()调用在每次需要时根据需要检索当前实例,而不必依赖于实例或分开存储。
JSR-330变体称为提供程序,与提供者<MyTargetBean>
声明一起使用,并为每次检索尝试使用相应的get()调用。有关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">
<!-- 作为代理公开的HTTP Session作用域bean-->
<bean id="userPreferences" class="com.foo.UserPreferences"scope="session">
<!-- 指示容器代理周围的bean -->
<aop:scoped-proxy/>
</bean>
<!-- 一个注入到上述bean的代理的单例范围的bean -->
<bean id="userService" class="com.foo.SimpleUserService">
<!-- 对代理的userPreferences bean的引用 -->
<property name="userPreferences" ref="userPreferences"/>
</bean></beans>
要创建这样的代理,您将一个子<aop:scoped-proxy />
元素插入作用域定义(请参阅”选择要创建的代理类型”一节和第41章,基于XML Schema的配置)。 为什么在请求,会话,globalSession和自定义范围级别上限定范围的bean的定义需要<aop:scoped-proxy />
元素? 我们来检查以下单例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>
在前面的示例中,单例bean userManager注入了对HTTP会话范围bean userPreferences的引用。这里的重点是userManager bean是一个单例:它将每个容器一次实例化,其依赖性(在这种情况下只有一个,userPreferences bean)也只被注入一次。这意味着userManager bean将只对完全相同的userPreferences对象进行操作,也就是说,它最初被注入的对象。
将较短活动的作用域bean注入到较长寿命的作用域中时,这不是您想要的行为,例如将HTTP会话范围的协作bean作为依赖关系注入到单例bean中。相反,您需要单个userManager对象,并且在HTTP Session的整个生命周期中,您需要一个特定于所述HTTP会话的userPreferences对象。因此,容器创建一个对象,该对象暴露与UserPreferences类(理想情况下是UserPreferences实例的对象)完全相同的public接口,该对象可从范围机制(HTTP请求,会话等)获取真实的UserPreferences对象。容器将此代理对象注入到userManager bean中,该bean不知道此UserPreferences引用是代理。在这个例子中,当UserManager实例调用依赖注入的UserPreferences对象的方法时,它实际上是在代理上调用一个方法。然后,代理程序从(在这种情况下)HTTP Session提取真实的UserPreferences对象,并将方法调用委托给检索到的真实UserPreferences对象。
因此,当将请求,会话和全局范围的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/>
元素插入作用域定义(请参阅”选择要创建的代理类型”一节和 第41章,基于XML Schema的配置)。为什么在”request,session” globalSession和”自定义范围”级别定义的bean 定义<aop:scoped-proxy/>
要素?我们来检查以下的单例bean定义,并将其与上述范围所需要定义的对象(请注意,下列userPreferencesbean定义不完整)。
<bean id = "userPreferences" class = "com.foo.UserPreferences" scope = "session" />
<bean id = "userManager" class = "com.foo.UserManager" >
<property name = "userPreferences" ref = "userPreferences" /> </bean>
在前面的例子中,单引号userManager注入了一个引用HTTP Session标记的bean userPreferences。这里的重点是 userManagerbean是一个单例:它将每个容器一次实例化,其依赖(在这种情况下只有一个,userPreferencesbean)也只被注入一次。这意味着userManagerbean只能在完全相同的userPreferences对象上操作,也就是说,它最初被注入的对象。
将较短寿命的作用域bean注入到较长寿命的作用域中时,这不是您想要的行为,例如将HTTP Session标记的协作bean作为依赖关系注入到单例bean中。相反,您需要一个userManager 对象,并且在HTTP的生命周期中Session,您需要一个userPreferences特定于所述HTTP 的对象Session。因此,容器创建一个对象,该对象暴露与可以从范围机制(HTTP请求等)中获取真实对象的UserPreferences类(理想情况下是 UserPreferences实例的对象) 完全相同的public接口。容器将此代理对象注入到该bean中,该bean不知道此引用是代理。在这个例子中,当一个 实例调用依赖注入对象的方法时,实际上是在代理上调用一个方法。然后,代理程序 从(在这种情况下)HTTP 提取真实对象,并将方法调用委托给检索到的真实对象。
UserPreferencesSessionuserManagerUserPreferencesUserManagerUserPreferencesUserPreferencesSessionUserPreferences
因此,你注入时需要以下,正确而完整的配置 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>
选择要创建的代理类型
默认情况下,当Spring容器为使用该元素标记的bean创建代理时,将创建一个基于CGLIB的类代理。
【注】CGLIB代理只拦截public方法调用!不要在这样的代理上调用非公开方法; 它们不会被委派给实际的作用域目标对象。
或者,您可以通过指定元素属性false的值来配置Spring容器,为这些范围的bean创建基于标准的基于JDK接口的代理。使用基于JDK接口的代理意味着您的应用程序类路径中不需要额外的库来实现这种代理。但是,这也意味着作用域bean的类必须至少实现一个接口,并且其中注入作用域bean的所有协作者必须通过其接口之一引用该bean。proxy-target-class<aop:scoped-proxy/>
<! - DefaultUserPreferences实现UserPreferences接口 - >
<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>
有关选择基于类或基于接口的代理的更多详细信息,请参见第11.6节”代理机制”。
7.5.5自定义范围
bean的作用机制是可扩展的; 您可以定义自己的范围,甚至重新定义现有的作用域,尽管后者被认为是不好的做法,并且您不能覆盖内置singleton和prototype作用域。
创建自定义范围
要将自定义范围集成到Spring容器中,需要实现org.springframework.beans.factory.config.Scope本节所述的 接口。有关如何实现自己的范围的想法,请参阅Scope 随Spring框架本身以及Scopejavadocs一起提供的实现,这些 实现将更详细地介绍您需要实现的方法。
该Scope接口有四种从范围中获取对象的方法,将其从范围中移除,并允许它们被销毁。
以下方法从底层作用域返回对象。例如,会话范围实现返回会话范围的bean(如果不存在,则该方法返回一个新bean实例,并将其绑定到会话以供将来参考)。
Object get(String name,ObjectFactory objectFactory)
以下方法从底层作用域中删除该对象。例如,会话范围实现从底层会话中删除会话范围的bean。应该返回对象,但如果没有找到指定名称的对象,则可以返回null。
Object delete(String name)
以下方法注册范围在销毁时应执行的回调或范围中指定的对象被销毁时。有关销毁回调的更多信息,请参阅javadocs或Spring范围实现。
void registerDestructionCallback(String name,Runnable destructionCallback)
以下方法获取底层作用域的会话标识符。该标识符对于每个范围是不同的。对于会话范围的实现,该标识符可以是会话标识符。
String getConversationId()
使用自定义范围
在编写和测试一个或多个自定义Scope实现之后,您需要使Spring容器了解您的新范围。以下方法是Scope使用Spring容器注册新的方法:
void registerScope(String scopeName,Scope scope);
该方法在ConfigurableBeanFactory接口上声明,这可以ApplicationContext通过BeanFactory属性在Spring附带的大多数具体实现中使用。
该registerScope(..)方法的第一个参数是与范围关联的唯一名称; 在Spring容器本身中这样的名称的例子就是singleton和 prototype。该registerScope(..)方法的第二个参数是Scope您希望注册和使用的自定义实现的实际实例。
假设你编写你的自定义Scope实现,然后注册它如下。
【注】下面的示例使用SimpleThreadScope了Spring中包含的示例,但默认情况下未注册。对于您自己的自定义Scope 实现,说明将是一样的。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread",threadScope);
然后,您可以创建遵循自定义范围规则的bean定义Scope:
<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 = "xyBar" scope = "thread" >
<property name = "name" value = "Rick" />
<aop:scoped-proxy />
</bean>
<bean id = "foo" class = "xyFoo" >
<property name = "bar" ref = "bar" />
</bean></bean>
【注】当你放在<aop:scoped-proxy/>
一个FactoryBean实现中,它是工厂bean本身的范围,而不是返回的对象getObject()。
7.6定制bean的性质
7.6.1生命周期回调
要与容器的bean生命周期管理进行交互,可以实现Spring InitializingBean和DisposableBean接口。容器调用 afterPropertiesSet()前者,destroy()后者允许bean在初始化和销毁bean时执行某些操作。
【注】JSR-250 @PostConstruct和@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean不与Spring特定的接口耦合。有关详细信息,请参见第7.9.8节”@PostConstruct和@PreDestroy”。
如果您不想使用JSR-250注释,但您仍然希望删除耦合,请考虑使用init-method和destroy-method对象定义元数据。
在内部,Spring框架使用BeanPostProcessor实现来处理它可以找到的任何回调接口,并调用适当的方法。如果您需要自定义功能或其他生命周期行为,Spring不提供开箱即用的功能,您可以BeanPostProcessor自己实现。有关详细信息,请参阅 第7.8节”容器扩展点”。
除了初始化和销毁,回调之外,Spring管理的对象还可以实现Lifecycle接口,以便这些对象可以由容器自己的生命周期驱动启动和关闭进程。
本节介绍生命周期回调接口。
初始化回调
该org.springframework.beans.factory.InitializingBean接口允许bean在容器中设置了bean的所有必需属性之后执行初始化工作。该InitializingBean
void afterPropertiesSet()throws Exception;
建议不要使用`tializingBean界面,因为它不必要地将代码耦合到Spring。或者,使用@PostConstruct注释或指定POJO初始化方法。在基于XML的配置元数据的情况下,您可以使用该init-method属性来指定具有void no-argument签名的方法的名称。使用Java配置,您可以使用initMethod属性@Bean,请参阅”接收生命周期回调”一节。例如,以下内容:
<bean id = "exampleInitBean" class = "examples.ExampleBean" init-method = "init" />
public class ExampleBean {
public void init(){
//做一些初始化工作
}
}
…与…完全一样
<bean id = "exampleInitBean" class = "examples.AnotherExampleBean" />
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet(){
//做一些初始化工作
}
}
但不将代码耦合到Spring。
销毁回调
实现org.springframework.beans.factory.DisposableBean接口允许bean在包含它的容器被销毁时获取回调。该 DisposableBean接口指定单个方法:
void destroy()throws Exception ;
建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。或者,使用@PreDestroy注释或指定由bean定义支持的通用方法。使用基于XML的配置元数据,您可以使用该destroy-method属性<bean/>
。使用Java配置,您可以使用destroyMethod属性@Bean,请参阅 “接收生命周期回调”一节。例如,以下定义:
<bean id = "exampleInitBean" class = "examples.ExampleBean" destroy-method = "cleanup" />
public class ExampleBean {
public void cleanup(){
//做一些销毁工作(像释放池连接)
}
}
是完全一样的:
<bean id = "exampleInitBean" class = "examples.AnotherExampleBean" />
public class AnotherExampleBean implements DisposableBean {
public void destroy(){
//做一些销毁工作(像释放池连接)
}
}
但不将代码耦合到Spring。
【注】destroy-method一个<bean>
元素的属性可以被分配一个特殊的 (inferred)值,指示Spring自动检测特定bean类(实现或因此匹配的任何类)的publicclose或shutdown方法 。此特殊 值也可以设置在一个元素的属性上, 以将此行为应用于一整套bean(参见 “默认初始化和销毁方法”一节)。请注意,这是Java配置的默认行为。java.lang.AutoCloseablejava.io.Closeable(inferred)default-destroy-method<beans>
默认的初始化和销毁方法
当你写的初始化和销毁不使用Spring的具体方法回调InitializingBean和DisposableBean回调接口,你通常写有名字,如方法init(),initialize(),dispose(),等等。理想情况下,这种生命周期回调方法的名称在整个项目中是标准化的,因此所有开发人员都使用相同的方法名称并确保一致性。
您可以将Spring容器配置look为命名初始化,并在每个 bean 上销毁回调方法名称。这意味着作为应用程序开发人员,您可以编写应用程序类并使用调用的初始化回调 init(),而无需为init-method=”init”每个bean定义配置一个属性。当创建bean(并根据之前描述的标准生命周期回调合同)时,Spring IoC容器将调用该方法。此功能还对初始化和销毁方法回调执行一致的命名约定。
假设你的初始化回调方法是命名的,init()而且回调方法被命名destroy()。在下面的例子中,你的类将类似于类。
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao){
this .blogDao = blogDao;
}
//这是(不出意料的)初始化回调方法
public void init(){
if(this .blogDao == null){
throw new IllegalStateException("The [blogDao] property must be set)");
}
}
}
<beans default-init-method = "init" >
<bean id = "blogService" class = "com.foo.DefaultBlogService" >
<property name = "blogDao" ref = "blogDao" />
</bean></beans>
default-init-method顶级<beans/>
元素属性上的属性的存在导致Spring IoC容器识别一个initbean 调用的方法作为初始化方法回调。当一个bean被创建和组合时,如果bean类有这样一个方法,它将在适当的时候被调用。
您可以使用default-destroy-method顶级<beans/>
元素上的属性来类似地(在XML中)配置destroy方法回调 。
如果现有的bean类已经有在与惯例有差异命名回调方法,你可以通过指定覆盖默认(XML格式,这是)使用方法名init-method和destroy-method该属性<bean/>
本身。
Spring容器保证在bean提供所有依赖关系后立即调用配置的初始化回调。因此,在raw bean引用上调用初始化回调函数,这意味着AOP拦截器等尚未应用于该bean。目标bean 首先被完全创建, 然后应用具有其拦截器链的AOP代理(例如)。如果目标bean和代理被单独定义,您的代码甚至可以绕过原始目标bean,绕过代理。因此,将拦截器应用于init方法是不一致的,因为这样做会将目标bean的生命周期与其代理/拦截器相结合,并在代码直接与原始目标bean进行交互时留下奇怪的语义。
结合生命周期机制
在Spring 2.5中,你有控制bean的生命周期行为的三个选项:在 InitializingBean和 DisposableBean回调接口; 习俗 init()和destroy()方法; 和 @PostConstruct和@PreDestroy 注释。您可以组合这些机制来控制给定的bean。
【注】如果为一个bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,那么每个配置的方法都按照下面列出的顺序执行。但是,如果配置了相同的方法名称 - 例如, init()对于初始化方法,对于多个这样的生命周期机制,该方法将被执行一次,如上一节所述。
使用不同的初始化方法为同一个bean配置的多个生命周期机制如下所述:
方法注释 @PostConstruct
afterPropertiesSet()由InitializingBean回调接口 定义
自定义配置init()方法
销毁方法按相同顺序调用:
方法注释 @PreDestroy
destroy()由DisposableBean回调接口 定义
自定义配置destroy()方法
启动和关闭回调
该Lifecycle界面定义了具有其自身生命周期要求的任何对象的基本方法(例如启动和停止某些后台进程):
public interface Lifecycle{
void start();
void stop();
boolean isRunning();
}
任何Spring管理的对象都可以实现该接口。然后,当 ApplicationContext自身接收到启动和停止信号时,例如在运行时停止/重新起动情况,它会将这些调用级联到Lifecycle在该上下文中定义的所有实现。它通过委托给一个LifecycleProcessor:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
【注】请注意,常规org.springframework.context.Lifecycle界面只是显式启动/停止通知的简单合同,并不意味着在上下文更新时自动启动。考虑实现org.springframework.context.SmartLifecycle对特定bean(包括启动阶段)的自动启动的细粒度控制。此外,请注意,停止通知不能保证在销毁之前:在定期关闭时,所有Lifecyclebean将在广泛的销毁回调正在传播之前首先收到停止通知; 然而,在上下文生命周期的热刷新或中止刷新尝试时,只会调用destroy方法。
启动和关闭调用的顺序可能很重要。如果任何两个对象之间存在”依赖”关系,则依赖方将在其依赖关系之后启动,并在其依赖之前停止。然而,有时直接依赖是未知的。您可能只知道某种类型的对象应该在另一种类型的对象之前开始。在这些情况下,SmartLifecycle接口定义了另一个选项,即getPhase()在其超级界面上定义的方法 Phased。
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
当启动时,相位最低的物体首先启动,停止时按照相反的顺序进行。因此,实现SmartLifecycle的getPhase()方法及其方法返回Integer.MIN_VALUE将是第一个开始,最后一个停止。在频谱的另一端,相位值 Integer.MAX_VALUE将指示对象应该首先被启动并停止(可能是因为它依赖于其他进程要运行)。当考虑相位值时,同样重要的是要知道,任何Lifecycle不实现的”正常” 对象的默认阶段 SmartLifecycle都将为0。因此,任何负相位值都表示一个对象应在这些标准组件之前开始他们),反之亦然,任何正相值。
正如你可以看到通过SmartLifecycle接受回调定义的停止方法。任何实现必须run()在该实现的关闭过程完成后调用该回调方法。这将允许异步关闭,因为LifecycleProcessor接口 的默认实现DefaultLifecycleProcessor,将等待其每个阶段内的对象组的超时值来调用该回调。默认的每阶段超时是30秒。您可以通过在上下文中定义名为”lifecycleProcessor”的bean来覆盖默认的生命周期处理器实例。如果您只想修改超时,则定义以下内容就足够了:
<bean id = "lifecycleProcessor" class = "org.springframework.context.support.DefaultLifecycleProcessor" >
<! - 以毫秒为单位的超时值 - >
<property name = "timeoutPerShutdownPhase" value = "10000" /> </bean>
如上所述,LifecycleProcessor接口定义了用于刷新和关闭上下文的回调方法。后者将简单地驱动关闭过程,就像stop()已经被明确调用一样,但是当上下文关闭时会发生。另一方面,”刷新”回调使得SmartLifecyclebean的另一个功能 。当上下文被刷新(所有对象已被实例化和初始化之后)时,将调用该回调,并且在该点,默认生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值 。如果为”true”,则该对象将在该点启动,而不是等待显式调用上下文或其自己的start()方法(与上下文刷新不同,上下文启动不会自动发生为标准上下文实现)。”阶段”值以及任何”依赖”关系将以与上述相同的方式确定启动顺序。
在非Web应用程序中正常关闭Spring IoC容器
【注】本节仅适用于非Web应用程序。Spring的基于Web的 ApplicationContext实现已经有代码,在相关的Web应用程序关闭时,正常地关闭了Spring IoC容器。
如果在非Web应用程序环境中使用Spring的IoC容器; 例如,在富客户端桌面环境中; 你注册一个关闭钩子与JVM。这样做可以确保正常关机,并在单例Bean上调用相关的destroy方法,以便释放所有资源。当然,您仍然必须正确配置和实现这些destroy回调。
要注册一个关闭钩子,你调用registerShutdownHook()在ConfigurableApplicationContext接口上声明的方法:
import org.springframework.context.ConfigurableApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String [] args)throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext(
new String [] { "beans.xml" });
//为上述上下文添加一个关闭钩子
ctx.registerShutdownHook();
//app running here ...
//main方法退出,钩子在应用程序关闭之前被调用...
}
}
7.6.2 ApplicationContextAware和BeanNameAware
当一个ApplicationContext创建实现该org.springframework.context.ApplicationContextAware接口的对象实例时 ,该实例将被提供一个引用ApplicationContext。
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext)throws BeansException;
}
因此,Bean可以ApplicationContext通过编程方式来操作,通过ApplicationContext接口创建它们,或通过将引用转换为该接口的已知子类,例如ConfigurableApplicationContext暴露其他功能。一个用途是对其他bean的编程检索。有时这种能力是有用的; 然而,一般来说,你应该避免它,因为它将代码耦合到Spring,并且不遵循反转控制样式,其中协作者被提供给bean作为属性。ApplicationContext提供访问文件资源,发布应用程序事件和访问的其他方法 MessageSource。这些附加功能在第7.15节”ApplicationContext的附加功能”.
自Spring2.5以来,自动装配是获得参考的另一个替代方案 ApplicationContext。”传统” constructor和byType自动接线模式(如第7.4.5节”自动装配协作者”中所述)可以分别为ApplicationContext构造函数参数或setter方法参数提供类型的依赖关系 。为了更多的灵活性,包括自动连线字段和多参数方法的功能,请使用新的基于注解的自动布线功能。如果你这样做,那么ApplicationContext自动连接到一个字段,构造函数参数或者方法参数中,ApplicationContext如果所讨论的字段,构造函数或方法带有@Autowired注释,那么该类型是期望的。有关更多信息,请参见 第7.9.2节”@Autowired”。
当a ApplicationContext创建一个实现 org.springframework.beans.factory.BeanNameAware接口的类时,该类将被提供对在其关联对象定义中定义的名称的引用。
public interface BeanNameAware {
void setBeanName(String name)throws BeansException;
}
回调在普通bean属性的InitializingBean 集合之后但在初始化回调之前调用,例如afterPropertiesSet或自定义init方法。
7.6.3其他感知界面
除了ApplicationContextAware和BeanNameAware上面所讨论的,Spring提供了一系列的Aware,允许bean指示,他们需要一定的容器接口基础设施的依赖。最重要的Aware接口总结如下 - 作为一般规则,该名称是依赖关系类型的良好指示:
表7.4 感知界面(部分)
再次注意,这些接口的使用将您的代码与Spring API绑定,并不遵循反转控制样式。因此,建议使用需要对容器进行编程访问的基础架构bean。
7.7 Bean定义继承
bean定义可以包含很多配置信息,包括构造函数参数,属性值和容器特定信息,如初始化方法,静态工厂方法名称等。子bean定义从父定义继承配置数据。子定义可以覆盖某些值,或者根据需要添加其他值。使用父和子bean定义可以节省大量的打字。有效地,这是模板的一种形式。
如果以ApplicationContext编程方式使用接口,则子ChildBeanDefinition类定义由类表示。大多数用户不能在这个级别上与他们一起工作,而是以声明方式配置bean定义ClassPathXmlApplicationContext。当您使用基于XML的配置元数据时,通过使用parent属性指定子bean定义,指定父bean作为此属性的值。
<bean id = "inheritedTestBean" abstract = "true" class = "org.springframework.beans.TestBean" >
<property name = "name" value = "parent" />
<property name = "age" value = "1" /> </bean>
<bean id = "inheritsWithDifferentClass" class = "org.springframework.beans.DerivedTestBean"
parent ="inheritedTestBean" init-method ="initialize">
<property name = "name" value = "override" />
<! age属性值为1将从父类继承 - > </bean>
子bean定义如果没有指定,则使用父定义中的bean类,但也可以覆盖它。在后一种情况下,子bean类必须与父类兼容,即它必须接受父属的属性值。
子bean定义从父级继承范围,构造函数参数值,属性值和方法覆盖,并添加新值。static您指定的任何范围,初始化方法,销毁方法和/或工厂方法设置将覆盖相应的父设置。
其余的设置总是从子定义处得到:依赖, 自动装配模式,依赖检查,单,延迟初始化。
上述示例通过使用该abstract属性将父bean定义显式标记为抽象。如果父定义没有指定一个类,按照abstract需要显式标记父bean定义,如下所示:
<bean id = "inheritedTestBeanWithoutClass" abstract = "true" >
<property name = "name" value = "parent" />
<property name = "age" value = "1" /> </bean>
<bean id = "inheritsWithClass" class = "org.springframework.beans.DerivedTestBean"
parent = "inheritedTestBeanWithoutClass" init-method = "initialize" >
<property name = "name" value = "override" />
<!将从父bean定义继承1的值 - > </bean>
父bean不能自己实例化,因为它不完整,它也被明确标记为abstract。当定义abstract如此,它仅可用作纯模板bean定义,作为子定义的父定义。尝试使用这样一个abstract父bean,通过引用它作为另一个bean的ref属性或getBean()使用父bean id 进行显式调用返回错误.class似地,容器的内部 preInstantiateSingletons()方法忽略定义为抽象的bean定义。
【注】ApplicationContext预先实例化所有单身人士。因此,重要的是(至少对于单例bean),如果您有一个(父)bean定义,您打算仅使用模板,并且此定义指定一个类,则必须确保将抽象属性设置为true否则应用程序上下文将实际(尝试)预实例化该abstractbean。