1.5Bean作用域
当您创建一个Bean定义时,您将创建一个菜谱来创建由该Bean定义定义的类的实际实例。bean定义是配方的想法很重要,因为它意味着,与类一样,您可以从一个配方创建许多对象实例。您不仅可以控制要插入到由特定bean定义创建的对象中的各种依赖项和配置值,还可以控制由特定bean定义创建的对象的范围。这种方法功能强大且灵活,因为您可以选择通过配置创建的对象的范围,而不必在Java类级别上考虑对象的范围。可以将bean定义为部署在多个范围中的一个。Spring框架支持六个作用域,其中四个只有在使用web感知的应用程序上下文时才可用。您还可以创建自定义范围。下表描述了支持的范围:
Scope | Description |
---|---|
singleton | (Default) Scopes a single bean definition to a single object instance for each Spring IoC container.为每个Spring IoC容器将单个bean定义限定为单个对象实例 |
-prototype- | -Scopes a single bean definition to any number of object instances.将单个bean定义的范围限定为任意数量的对象实例- |
request | Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的bean实例,该实例是在单个bean定义的基础上创建的。仅在可感知web的Spring ApplicationContext上下文中有效 |
session | Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.将单个bean定义的范围限定为HTTP会话的生命周期。仅在可感知web的Spring ApplicationContext上下文中有效 |
-application- | -Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.将单个bean定义的范围限定为ServletContext的生命周期。仅在可感知web的Spring ApplicationContext上下文中有效- |
websocket | Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.将单个bean定义的范围限定为WebSocket的生命周期。仅在可感知web的Spring ApplicationContext上下文中有效 |
**从Spring 3.0开始,线程范围可用,但默认情况下没有注册:请参见SimpleThreadScope。从Spring 4.2开始,事务范围也可用:SimpleTransactionScope。有关如何注册这些或任何其他自定义范围的说明,请参见使用自定义范围。
1.5.1. The Singleton Scope
只管理一个单例bean的一个共享实例,所有对ID或ID匹配该bean定义的bean的请求都会导致Spring容器返回该特定的bean实例。
换句话说,当您定义一个bean定义,并且它的作用域是一个单例对象时,Spring IoC容器将创建该bean定义定义的对象的一个实例。此单一实例存储在此类单例bean的缓存中,对于该命名bean的所有后续请求和引用都返回缓存的对象。下面的图像显示了:
Spring的单例bean概念不同于Gang of Four (GoF)模式书中定义的单例模式。GoF单例硬编码对象的范围,这样每个类加载器只能创建一个特定类的一个实例。Spring单例的范围最好描述为每个容器和每个bean。这意味着,如果您在单个Spring容器中为特定类定义一个bean,那么Spring容器将创建由该bean定义定义的类的一个且仅一个实例。单例范围是Spring中的默认范围。要在XML中将bean定义为单例,可以定义一个bean,如下面的示例所示:
1.5.2. The Prototype Scope
bean部署的非单例原型范围会在每次发出对特定bean的请求时创建一个新的bean实例。也就是说,bean被注入到另一个bean中,或者您通过容器上的getBean()方法调用请求它。通常,您应该为所有有状态bean使用原型范围,为无状态bean使用单例范围。
下图演示了Spring prototype作用域:
(数据访问对象(DAO)通常不配置为原型,因为典型的DAO不包含任何会话状态。我们更容易重用单例图的核心。)下面的示例将bean定义为XML中的原型:
与其他作用域不同,Spring不管理原型bean的完整生命周期。容器实例化、配置和以其他方式组装原型对象并将其传递给客户机,而不需要该原型实例的进一步记录。因此,尽管初始化生命周期回调方法会在所有对象上调用,而不考虑范围,但是在原型的情况下,配置的销毁生命周期回调不会被调用。客户机代码必须清理原型范围的对象,并释放原型bean所持有的昂贵资源。要让Spring容器释放原型作用域bean所持有的资源,可以尝试使用自定义bean后处理器,它持有对需要清理的bean的引用。在某些方面,Spring容器在原型作用域bean方面的角色是Java new操作符的替代。超过此点的所有生命周期管理都必须由客户机处理。(有关Spring容器中bean生命周期的详细信息,请参见生命周期回调。)
1.5.3. Singleton Beans with Prototype-bean Dependencies
当您使用依赖于多例bean的单例bean时,请注意依赖项是在实例化时解析的。因此,如果将一个多例bean注入到一个单例作用域bean中,就会实例化一个新的原型bean,然后将依赖注入到单例bean中。原型实例(prototype)是惟一提供给单例作用域bean的实例。但是,假设您希望单例作用域bean在运行时重复获取原型作用域bean的新实例。您不能依赖—将一个原型作用域bean注入到您的单例bean中,因为这种注入只发生一次,当Spring容器实例化单例bean并解析和注入它的依赖项时。如果在运行时不止一次需要原型bean的新实例,请参见方法注入1.4.6
1.5.4. Request, Session, Application, and WebSocket Scopes
只有在使用 web-aware的Spring ApplicationContext实现(例如XmlWebApplicationContext)时, Request, Session, Application, and WebSocket Scopes(请求、会话、应用程序和websocket范围)才可用。
如果将这些作用域与常规Spring IoC容器一起使用,比如ClassPathXmlApplicationContext,就会抛出一个IllegalStateException,它会报怨一个未知的bean作用域。
初始Web配置
为了支持bean在请求、会话、应用程序和websocket级别上的作用域(Web作用域bean),在定义bean之前需要进行一些较小的初始配置。(标准范围:单例和原型不需要这个初始设置。)
如何完成这个初始设置取决于特定的Servlet环境。
如果在Spring Web MVC中访问作用域bean,实际上,在Spring DispatcherServlet处理的请求中,不需要进行任何特殊设置。DispatcherServlet已经公开了所有相关状态。
如果使用Servlet 2.5 web容器处理Spring DispatcherServlet之外的请求(例如,当使用JSF或Struts时),则需要注册org.springframework.web.context.request。RequestContextListener ServletRequestListener。对于Servlet 3.0+,这可以通过使用WebApplicationInitializer接口以编程方式实现。或者,对于较旧的容器,将以下声明添加到web应用程序的web.xml文件中:
...
org.springframework.web.context.request.RequestContextListener
...
或者,如果监听器设置有问题,可以考虑使用Spring的RequestContextFilter。过滤器映射依赖于周围的web应用程序配置,因此必须根据需要更改它。下面的清单显示了web应用程序的过滤部分:
...
requestContextFilter
org.springframework.web.filter.RequestContextFilter
requestContextFilter
/*
...
DispatcherServlet、RequestContextListener和RequestContextFilter都做完全相同的事情,即将HTTP请求对象绑定到服务该请求的线程。这使得请求和会话范围的bean在调用链的更下方可用。
request scope
Spring容器通过为每个HTTP请求使用LoginAction bean定义来创建LoginAction bean的新实例。也就是说,loginAction bean的作用域在HTTP请求级别。您可以随意更改创建的实例的内部状态,因为从相同的loginAction bean定义创建的其他实例在状态中看不到这些更改。它们是针对单个请求的。当请求完成处理时,将丢弃作用域为请求的bean。当使用注释驱动的组件或Java配置时,可以使用@RequestScope注释将组件分配给请求范围。下面的示例展示了如何做到这一点:
@RequestScope
@Component
public class LoginAction {
// …
}
Session Scope
Consider the following XML configuration for a bean definition:
Spring容器通过为单个HTTP会话的生命周期使用UserPreferences bean定义来创建UserPreferences bean的新实例。换句话说,userPreferences bean有效地限定在HTTP会话级别的范围内。与请求范围内bean一样,你可以改变内部状态的实例创建尽可能多的你想要的,知道其他HTTP会话实例也使用相同的实例创建userPreferences bean定义看不到这些变化状态,因为他们是特定于一个单独的HTTP会话。当HTTP会话最终被丢弃时,作用域为该特定HTTP会话的bean也被丢弃。当使用注释驱动的组件或Java配置时,可以使用@SessionScope注释将组件分配给会话范围。
@SessionScope
@Component
public class UserPreferences {
// …
}
Application Scope
Consider the following XML configuration for a bean definition:
Spring容器通过为整个web应用程序使用一次AppPreferences bean定义来创建AppPreferences bean的新实例。也就是说,appPreferences bean的作用域在ServletContext级别,并存储为一个常规的ServletContext属性。这有点类似于弹簧单例bean,但在两个重要方面不同:它是一个单例每ServletContext不是每春天ApplicationContext的(可能有几个在任何给定的web应用程序),它实际上是暴露,因此可见ServletContext属性。当使用注释驱动的组件或Java配置时,可以使用@ApplicationScope注释将组件分配给应用程序范围。下面的示例展示了如何做到这一点:
@ApplicationScope
@Component
public class AppPreferences {
// …
}
coped Beans as Dependencies作用域bean作为依赖项
Spring IoC容器不仅管理对象(bean)的实例化,还管理协作者(或依赖项)的连接。如果您想将(例如)HTTP请求范围的bean注入到另一个更长的作用域的bean中,您可以选择注入AOP代理来代替作用域bean。也就是说,您需要注入一个代理对象,它公开与作用域对象相同的公共接口,但也可以从相关作用域(例如HTTP请求)检索实际目标对象,并将委托方法调用注入到实际对象。
–您还可以在范围为单例的bean之间使用
,然后引用通过一个可序列化的中间代理,从而能够在反序列化时重新获得目标单例bean。
–当针对范围原型的bean声明
时,共享代理上的每个方法调用都会创建一个新的目标实例,然后将调用转发到该实例。
–而且,范围代理不是以生命周期安全的方式从较短范围访问bean的唯一方法。您还可以将注入点(即构造函数或setter参数或autowired字段)声明为ObjectFactory
,允许每次需要时调用getObject()来检索当前实例,而不需要保存实例或单独存储实例。
–作为扩展的变体,您可以声明ObjectProvider
,它提供了几个额外的访问变体,包括getIfAvailable和getIfUnique。
–此方法的JSR-330变体称为Provider,并与Provider
声明和对应的get()调用一起使用。有关JSR-330的详细信息,请参阅这里。
下面例子中的配置只有一行,但重要的是要理解它背后的“为什么”和“如何”:
要创建这样一个代理,您需要将一个子
元素插入到一个作用域bean定义中(请参阅选择要创建的代理类型和基于XML模式的配置)。为什么定义在请求、会话和自定义范围级别上作用域的bean需要
元素?考虑下面的单例bean定义,并将其与您需要为前面提到的范围定义的内容进行对比(请注意,下面的userPreferences bean定义是不完整的):
在前面的示例中,单例bean (userManager)被注入了对HTTP Session-scoped bean (userPreferences)的引用。这里的要点是userManager bean是单例的:它只实例化一个容器一次,并且它的依赖项(在本例中只有一个,userPreferences bean)也只注入一次。这意味着userManager bean只对完全相同的userPreferences对象(即最初注入它的对象)进行操作。当将一个较短生存期的作用域bean注入到一个较长生存期的作用域bean中时,这不是您想要的行为(例如,将一个HTTPSession-scoped的协作bean作为依赖项注入到单例bean中)。相反,您需要一个单一的userManager对象,并且对于HTTP Session的生存期,您需要一个特定于HTTP会话的userPreferences对象。因此,容器创建一个对象,该对象公开与UserPreferences类完全相同的公共接口(理想情况下,对象是UserPreferences实例),该对象可以从作用域机制(HTTP请求、会话等等)获取真正的UserPreferences对象。容器将此代理对象注入userManager bean,该bean不知道此UserPreferences引用是代理。在本例中,当UserManager实例调用依赖注入的UserPreferences对象上的方法时,它实际上是在调用代理上的方法。然后代理从HTTP会话(在本例中)获取实际的UserPreferences对象,并将方法调用委托给检索到的实际UserPreferences对象。因此,在将请求和会话范围的bean注入协作对象时,需要以下(正确和完整的)配置,如下面的示例所示:
选择要创建的代理的类型
默认情况下,当Spring容器为用元素标记的bean创建代理时,将创建一个基于cglib的类代理。
*CGLIB代理只拦截公共方法调用!不要在这样的代理上调用非公共方法。它们没有委托给实际作用域的目标对象。
或者,您可以配置Spring容器,通过为元素的proxy-target-class属性的值指定false,为这种范围bean创建标准的基于JDK接口的代理。使用基于JDK接口的代理意味着在应用程序类路径中不需要额外的库来影响这种代理。但是,这也意味着作用域bean的类必须实现至少一个接口,并且所有注入作用域bean的合作者必须通过其接口之一引用bean。下面的例子
1.5.5. Custom Scopes自定义范围
bean作用域机制是可扩展的。您可以定义自己的范围,甚至重新定义现有的范围,尽管后者被认为是不好的实践,并且您不能覆盖内置的单例和原型范围。
要将自定义范围集成到Spring容器中,需要实现org.springframework.bean .factory.config。作用域接口,这将在本节中描述。要了解如何实现您自己的范围,请参阅Spring框架本身提供的范围实现和范围javadoc,这将更详细地解释您需要实现的方法。Scope接口有四个方法来从范围中获取对象、从范围中删除对象并销毁它们。例如,会话范围实现返回会话范围的bean(如果它不存在,则方法在将其绑定到会话以供将来引用后返回bean的新实例)。下面的方法从基础范围返回对象:
Object get(String name, ObjectFactory objectFactory)
例如,session范围实现中删除优先于session的 session-scoped bean。应该返回该对象,但是如果没有找到具有指定名称的对象,则可以返回null。下面的方法从基础范围中删除对象:Object remove(String name)
以下方法注册范围在销毁或销毁范围内指定对象时应执行的回调:
void registerDestructionCallback(String name, Runnable destructionCallback)
有关销毁回调的更多信息,请参阅javadoc或Spring作用域实现。
下面的方法获取底层范围的对话标识符:
String getConversationId()
这个标识符对于每个范围都是不同的。对于会话范围的实现,此标识符可以是会话标识符。
使用自定义范围
在编写和测试一个或多个自定义范围实现之后,您需要让Spring容器知道您的新范围。下面的方法是向Spring容器注册新范围的中心方法:
void registerScope(String scopeName, Scope scope);
这个方法是在ConfigurableBeanFactory接口上声明的,它可以通过Spring附带的大多数具体的ApplicationContext实现上的BeanFactory属性获得。registerScope(…)方法的第一个参数是与范围关联的惟一名称。Spring容器本身中此类名称的例子有singleton和prototype。registerScope(…)方法的第二个参数是您希望注册和使用的自定义范围实现的实际实例。假设您编写了自定义范围实现,然后注册它,如下例所示。
下一个示例使用SimpleThreadScope,它包含在Spring中,但默认情况下没有注册。对于您自己的自定义范围实现,说明将是相同的。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope(“thread”, threadScope);
然后,您可以创建遵循自定义范围的范围规则的bean定义,如下所示:
使用自定义范围实现,您不局限于范围的编程注册。您还可以通过使用CustomScopeConfigurer类声明性地进行范围注册,如下面的示例所示:
When you place
in a FactoryBean implementation, it is the factory bean itself that is scoped, not the object returned from getObject().
1.6自定义Bean的性质
Spring框架提供了许多接口,您可以使用它们定制bean的性质。本节将它们分组如下:
Lifecycle Callbacks
applicationcontext - ware和BeanNameAware
Other Aware interface
1.6.1. Lifecycle Callbacks
为了与bean生命周期的容器管理进行交互,您可以实现Spring InitializingBean和DisposableBean接口。容器为前者调用afterPropertiesSet(),为后者调用destroy(),以便bean在初始化和销毁bean时执行某些操作。
**JSR-250 @PostConstruct和@PreDestroy注释通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注释意味着您的bean没有耦合到特定于spring的接口。有关详细信息,请参见使用@PostConstruct和@PreDestroy。如果不希望使用JSR-250注释,但仍然希望删除耦合,请考虑init-method和destroy-method bean定义元数据。
在内部,Spring框架使用BeanPostProcessor实现来处理它能找到并调用适当方法的任何回调接口。如果您需要自定义特性或Spring默认不提供的其他生命周期行为,您可以自己实现BeanPostProcessor。有关更多信息,请参见容器扩展点。
除了初始化和销毁回调之外,spring托管对象还可以实现生命周期接口,以便这些对象可以参与启动和关闭过程,这是由容器自身的生命周期驱动的。
本节将描述生命周期回调接口。
初始化回调
org.springframe .bean .factory.InitializingBean接口允许bean在容器设置了bean上所有必要的属性之后执行初始化工作。InitializingBean接口指定了一个方法:void afterPropertiesSet() throws Exception;
我们建议您不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。或者,我们建议使用@PostConstruct注释或指定POJO初始化方法。对于基于xml的配置元数据,可以使用init-method属性指定具有空无参数签名的方法的名称。使用Java配置,您可以使用@Bean的initMethod属性。参见接收生命周期回调。考虑下面的例子:
public class ExampleBean {
public void init() {
// do some initialization work
}
}
上面的例子与下面的例子(包含两个清单)的效果几乎完全相同:
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
销毁回调
Implementing the org.springframework.beans.factory.DisposableBean interface lets a bean get a callback when the container that contains it is destroyed. The DisposableBean interface specifies a single method:
void destroy() throws Exception;
我们建议您不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。另外,我们建议使用@PreDestroy注释或指定bean定义支持的泛型方法。使用基于xml的配置元数据,您可以在上使用destroy-method属性。使用Java配置,您可以使用@Bean的destroyMethod属性。参见接收生命周期回调。考虑以下定义:
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
上面的定义与下面的定义几乎完全相同:
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
然而,前面两个定义中的第一个并没有将代码耦合到Spring。
您可以为
元素的destroy-method属性指定一个特殊的(推断的)值,该值指示Spring自动检测特定bean类上的公共关闭或关闭方法。(任何实现java.lang.AutoCloseable或java.io.Closeable将匹配。)您还可以在
元素的Default - Destroy -method属性上设置这个特殊的(推断的)值,以便将此行为应用于整个bean集(请参阅缺省初始化和销毁方法)。注意,这是Java配置的默认行为。
默认初始化和销毁方法
当您编写初始化和销毁不使用特定于spring的InitializingBean和dispose bean回调接口的方法回调时,您通常编写具有init()、initialize()、dispose()等名称的方法。理想情况下,这样的生命周期回调方法的名称是跨项目标准化的,以便所有开发人员使用相同的方法名称,并确保一致性。您可以配置Spring容器来查找每个bean上的命名初始化和销毁回调方法名。这意味着,作为应用程序开发人员,您可以编写应用程序类并使用一个名为init()的初始化回调函数,而不必为每个bean定义配置init-method="init"属性。Spring IoC容器在创建bean时调用该方法(并根据前面描述的标准生命周期回调契约)。该特性还为初始化和销毁方法回调强制了一致的命名约定。假设初始化回调方法名为init(),而销毁回调方法名为destroy()。然后,您的类类似于下面示例中的类:
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
然后,您可以在类似于下面的bean中使用该类:
顶层
元素属性上的default-init-method属性的存在使Spring IoC容器将bean类上的init方法识别为初始化方法回调。当创建和组装bean时,如果bean类有这样的方法,则在适当的时候调用它。
通过在顶层
元素上使用default-destroy-method属性,您可以类似地配置destroy方法回调(即在XML中)。
现有的bean类已经有回调方法,它们的命名与约定不一致,您可以通过使用
本身的init-method和destroy-method属性指定(即在XML中)方法名来覆盖默认值。
Spring容器保证在向bean提供所有依赖项后立即调用已配置的初始化回调。因此,对原始bean引用调用初始化回调,这意味着AOP拦截器等还没有应用到bean。首先完全创建目标bean,然后AOP代理(例如)带有拦截器链的应用。如果目标bean和代理是单独定义的,您的代码甚至可以绕过代理与原始目标bean交互。因此,将拦截器应用于init方法将是不一致的,因为这样做将把目标bean的生命周期耦合到它的代理或拦截器,并在代码直接与原始目标bean交互时留下奇怪的语义。
结合生命周期机制
从Spring 2.5开始,您有三个控制bean生命周期行为的选项:
1.初始化bean和一次性bean回调接口
2.自定义init()和destroy()方法
3.@PostConstruct和@PreDestroy注释。您可以组合这些机制来控制给定的bean。
**如果为bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名,那么每个配置的方法将按照本说明后面列出的顺序执行。但是,如果为这些生命周期机制中的一个以上配置了相同的方法名(例如,初始化方法的init()),则该方法将执行一次,如上一节所述。
使用不同的初始化方法为同一个bean配置多个生命周期机制,调用如下:
1.使用@PostConstruct注释的方法
2.afterPropertiesSet(),由InitializingBean回调接口定义
3.自定义配置的init()方法
销毁方法的调用顺序相同:
1.使用@PreDestroy注释的方法
2.销毁(),由一次性bean回调接口定义
3.自定义配置的destroy()方法
启动和关闭回调
生命周期接口为任何有自己生命周期需求的对象定义了基本的方法(例如启动和停止一些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何spring管理对象都可以实现生命周期接口。然后,当ApplicationContext本身接收启动和停止信号(例如,对于运行时的停止/重启场景)时,它将这些调用级联到该上下文中定义的所有生命周期实现。它通过委托给LifecycleProcessor来实现这一点,如下面的清单所示:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意LifecycleProcessor本身是Lifecycle接口的扩展。它还添加了另外两种方法来响应正在刷新和关闭的上下文。
注意,常规的org.springframework.context。生命周期接口是显式启动和停止通知的普通契约,并不意味着在上下文刷新时自动启动。对于特定bean的自动启动的细粒度控制(包括启动阶段),请考虑实现org.springframework.context。SmartLifecycle代替。
另外,请注意,停止通知不能保证在销毁之前发出。在常规关闭时,所有生命周期bean在传播常规销毁回调之前首先收到一个停止通知。然而,在上下文生存期内的热刷新或中止的刷新尝试上,只调用destroy方法。
启动和关闭调用的顺序可能很重要。如果任何两个对象之间存在“依赖关系”,依赖方在依赖项之后启动,在依赖项之前停止。然而,有时,直接依赖关系是未知的。您可能只知道某种类型的对象应该先于另一种类型的对象启动。在这些情况下,SmartLifecycle接口定义了另一个选项,即在其超接口上定义的getPhase()方法,phase。下面的清单显示了阶段性接口的定义:
public interface Phased {
int getPhase();
}
The following listing shows the definition of the SmartLifecycle interface:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
启动时,具有最低相位的对象首先启动。停止时,执行相反的顺序。因此,一个实现SmartLifecycle的对象,其getPhase()方法返回Integer.MIN_VALUE将是第一个启动和最后一个停止的对象之一。在频谱的另一端,相位值为整数.MAX_VALUE将指示对象应该最后启动,然后首先停止(可能是因为它取决于要运行的其他进程)。在考虑阶段值时,同样重要的是要知道,对于任何没有实现SmartLifecycle的正常生命周期对象,默认阶段都是0。因此,任何负相位值都表示对象应该在这些标准组件之前启动(在它们之后停止)。对于任何正相位值,则相反。
SmartLifecycle定义的stop方法接受回调。任何实现都必须在该实现的关闭过程完成后调用该回调函数run()方法。这在必要时支持异步关闭,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor会等待每个阶段中的对象组的超时值来调用回调。每个阶段的默认超时时间为30秒。您可以通过在上下文中定义一个名为lifecycleProcessor的bean来覆盖默认的lifecycle processor实例。如果只想修改超时,定义以下内容就足够了:
如前所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。后者驱动关闭进程,就像显式地调用stop()一样,但它发生在上下文关闭时。另一方面,“refresh”回调启用了SmartLifecycle bean的另一个特性。当上下文被刷新(在所有对象被实例化和初始化之后)时,将调用该回调。此时,默认的生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果为真,则从该点开始该对象,而不是等待显式调用上下文s或它自己的start()方法(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。阶段值和任何依赖关系决定了前面描述的启动顺序。
在非web应用程序中优雅地关闭Spring IoC容器
本节只适用于非web应用程序。Spring基于web的ApplicationContext实现已经有了适当的代码,可以在关闭相关web应用程序时优雅地关闭Spring IoC容器。
如果您在非web应用程序环境(例如,在富客户机桌面环境中)中使用Spring的IoC容器,请向JVM注册一个关机钩子。这样做可以确保优雅地关闭并调用单例bean上的相关销毁方法,以便释放所有资源。您仍然必须正确配置和实现这些销毁回调。
要注册一个关闭钩子,调用ConfigurableApplicationContext接口上声明的registerShutdownHook()方法,如下面的示例所示:
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("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
1.6.2. ApplicationContextAware and BeanNameAware
When an ApplicationContext creates an object instance that implements the org.springframework.context.ApplicationContextAware interface, the instance is provided with a reference to that ApplicationContext. The following listing shows the definition of the ApplicationContextAware interface:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,bean可以通过编程操作创建它们的ApplicationContext,方法是通过ApplicationContext接口,或者通过将引用转换为该接口的一个已知子类(例如ConfigurableApplicationContext,它公开了额外的功能)。一种用途是通过编程检索其他bean。有时这种能力是有用的。但是,一般来说,您应该避免使用它,因为它将代码耦合到Spring中,并且不遵循控制反转样式(将协作者作为属性提供给bean)。ApplicationContext的其他方法提供对文件资源的访问、发布应用程序事件和访问MessageSource。这些附加功能在ApplicationContext的附加功能中进行了描述。
从Spring 2.5开始,自动装配是获取对ApplicationContext引用的另一种选择。传统的构造函数和byType自动装配模式(如自动装配协作者中所述)可以分别为构造函数参数或setter方法参数提供ApplicationContext类型的依赖关系。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,可以使用新的基于注释的自动装配特性。如果您这样做了,那么ApplicationContext将被自动拖放到一个字段、构造函数参数或方法参数中,如果该字段、构造函数或方法携带@Autowired注释,那么该字段、构造函数或方法就需要ApplicationContext类型。更多信息,请参见使用@Autowired。
When an ApplicationContext creates a class that implements the org.springframework.beans.factory.BeanNameAware interface。类提供了对其关联对象定义中定义的名称的引用。下面的清单显示了BeanNameAware接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
回调函数在填充普通bean属性之后调用,但在初始化回调(如InitializingBean、afterPropertiesSet或自定义init-method)之前调用。
1.6.3. Other Aware Interfaces
Besides ApplicationContextAware and BeanNameAware (discussed earlier), Spring提供了广泛的可感知回调接口,让bean向容器表明它们需要某种基础设施依赖关系。通常,名称表示依赖项类型。下表总结了最重要的感知接口:
再次注意,使用这些接口将代码绑定到Spring API,并且不遵循控制反转样式。因此,我们建议将它们用于需要对容器进行编程访问的基础设施bean。
1.7.Bean定义继承
bean定义可以包含很多配置信息,包括构造函数参数、属性值和特定于容器的信息,比如初始化方法、静态工厂方法名称等等。子bean定义从父定义继承配置数据。子定义可以根据需要覆盖一些值或添加其他值。使用父bean和子bean定义可以节省大量输入。实际上,这是模板的一种形式。如果以编程方式使用ApplicationContext接口,则子bean定义由ChildBeanDefinition类表示。大多数用户在这个级别上不使用它们。相反,它们在类中声明性地配置bean定义,例如ClassPathXmlApplicationContext。当您使用基于xml的配置元数据时,您可以通过使用父属性来指示子bean定义,并将父bean指定为该属性的值。下面的示例展示了如何做到这一点:
如果没有指定bean类,则子bean定义使用父bean定义中的bean类,但也可以覆盖它。在后一种情况下,子bean类必须与父类兼容(也就是说,它必须接受父类的属性值)。
子bean定义继承父bean的作用域、构造函数参数值、属性值和方法,并具有添加新值的选项。指定的任何范围、初始化方法、销毁方法或静态工厂方法设置都会覆盖相应的父设置。
其余的设置总是取自子定义:依赖、自动装配模式、依赖项检查、单例和惰性初始化。
前面的示例使用抽象属性显式地将父bean定义标记为抽象。如果父bean定义没有指定类,则需要显式地将父bean定义标记为抽象,如下例所示:
父bean不能单独实例化,因为它是不完整的,而且它还显式地标记为抽象。当定义是抽象的时,它只能作为作为子定义的父定义的纯模板bean定义使用。尝试单独使用这样一个抽象的父bean,方法是将它引用为另一个bean的ref属性,或者使用父bean ID执行显式的getBean()调用,这会返回一个错误。Similarly, the container’s internal preInstantiateSingletons() method ignores bean definitions that are defined as abstract.
–ApplicationContext默认情况下预实例化所有单例。因此,它是重要的(至少对单例bean),如果你有一个(父)bean定义你只打算使用作为模板,这个定义指定了一个类,您必须确保设置抽象属性为true,否则应用程序上下文会(试图)pre-instantiate抽象的bean
1.8容器扩展点
通常,应用程序开发人员不需要子类化ApplicationContext实现类。相反,Spring IoC容器可以通过插入特殊集成接口的实现来扩展。接下来的几节将描述这些集成接口。
1.8.1. Customizing Beans by Using a BeanPostProcessor
BeanPostProcessor接口定义了回调方法,您可以实现这些方法来提供您自己的(或覆盖容器的默认)实例化逻辑、依赖项解析逻辑等等。如果希望在Spring容器实例化、配置和初始化bean之后实现一些定制逻辑,可以插入一个或多个定制BeanPostProcessor实现。
您可以配置多个BeanPostProcessor实例,并且可以通过设置order属性来控制这些BeanPostProcessor实例的执行顺序。只有当BeanPostProcessor实现有序接口时,才可以设置此属性。如果您编写自己的BeanPostProcessor,也应该考虑实现有序接口。有关详细信息,请参见BeanPostProcessor和有序接口的javadoc。还请参阅关于编程注册BeanPostProcessor实例的说明。
BeanPostProcessor实例操作bean(或对象)实例。也就是说,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor实例执行它们的工作。
BeanPostProcessor实例的作用域是每个容器。这只在使用容器层次结构时才相关。如果您在一个容器中定义一个BeanPostProcessor,那么它只在后处理该容器中的bean。换句话说,在一个容器中定义的bean不会由在另一个容器中定义的BeanPostProcessor进行后处理,即使两个容器都是相同层次结构的一部分。
要更改实际的bean定义(即定义bean的蓝图),您需要使用BeanFactoryPostProcessor,如使用BeanFactoryPostProcessor定制配置元数据中所述。
org.springframework.beans.factory.config.BeanPostProcessor接口恰好由两个回调方法组成。当这样一个类注册为后处理器的容器,每个容器创建bean实例,后处理器从容器之前得到一个回调容器初始化方法(如InitializingBean.afterPropertiesSet()或任何宣布init方法),任何bean初始化后回调。后处理器可以对bean实例执行任何操作,包括完全忽略回调。bean后处理器通常检查回调接口,或者使用代理包装bean。为了提供代理包装逻辑,一些Spring AOP基础设施类被实现为bean后处理器。ApplicationContext自动检测在实现BeanPostProcessor接口的配置元数据中定义的任何bean。ApplicationContext将这些bean注册为后处理程序,以便在创建bean时稍后调用它们。Bean后处理器可以像任何其他Bean一样部署在容器中。注意,当在配置类上使用@Bean工厂方法声明BeanPostProcessor时,工厂方法的返回类型应该是实现类本身,或者至少是org.springframework.bean .factory.config.BeanPostProcessor接口,清楚地指示该bean的后处理器特性。否则,ApplicationContext无法在完全创建它之前按类型自动检测它。由于为了应用于上下文中其他bean的初始化,需要尽早实例化BeanPostProcessor,所以这种早期类型检测非常重要。
**以编程方式注册BeanPostProcessor实例
虽然推荐的BeanPostProcessor注册方法是通过ApplicationContext自动检测(如前所述),但是您可以使用addBeanPostProcessor方法以编程方式针对ConfigurableBeanFactory注册它们。当您需要在注册前计算条件逻辑,甚至需要在层次结构的上下文中复制bean post处理器时,这将非常有用。但是,请注意,以编程方式添加的BeanPostProcessor实例并不尊重有序接口.在这里,注册的顺序决定了执行的顺序。还要注意,以编程方式注册的BeanPostProcessor实例总是在通过自动检测注册的实例之前处理,而不考虑任何显式的顺序。
**实现BeanPostProcessor接口的BeanPostProcessor实例和AOP自动代理类是特殊的,容器以不同的方式对待它们。它们直接引用的所有BeanPostProcessor实例和bean都在启动时实例化,作为ApplicationContext的特殊启动阶段的一部分。接下来,以排序的方式注册所有BeanPostProcessor实例,并将其应用于容器中的所有其他bean。因为AOP自动代理是作为BeanPostProcessor本身实现的,所以无论是BeanPostProcessor实例还是它们直接引用的bean都没有资格进行自动代理,因此没有将方面编织到它们之中。
对于任何这样的bean,您应该看到一条信息日志消息:bean someBean不适合被所有BeanPostProcessor接口处理(例如:不适合自动代理)。
如果您通过使用autowiring或@Resource(可能会回到autowiring)将bean连接到BeanPostProcessor中,Spring可能会在搜索与类型匹配的依赖项候选项时访问意外的bean,因此,使它们没有资格进行自动代理或其他类型的bean后处理。例如,如果您有一个带有@Resource注释的依赖项,其中字段或setter名称不直接对应于bean的声明名称,并且没有使用name属性,Spring将访问其他bean,以便按类型匹配它们。
下面的示例展示了如何在ApplicationContext中编写、注册和使用BeanPostProcessor实例。
示例:Hello World, beanpostprocessor风格
第一个例子说明了基本用法。该示例显示了一个自定义BeanPostProcessor实现,它调用容器创建的每个bean的toString()方法,并将生成的字符串打印到系统控制台。
下面的清单显示了自定义BeanPostProcessor实现类定义:
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
The following beans element uses the InstantiationTracingBeanPostProcessor:
注意,实例化tracingbeanpostprocessor是如何定义的。它甚至没有名称,而且因为它是一个bean,所以可以像注入任何其他bean一样注入依赖关系。(前面的配置还定义了一个由Groovy脚本支持的bean。Spring动态语言支持在“动态语言支持”一章中详细介绍。
以下Java应用程序运行上述代码和配置:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = (Messenger) ctx.getBean("messenger");
System.out.println(messenger);
}
}
上述应用程序的输出如下:
Bean ‘messenger’ created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
例子:RequiredAnnotationBeanPostProcessor
与自定义BeanPostProcessor实现一起使用回调接口或注释是扩展Spring IoC容器的常见方法。一个例子是Spring的RequiredAnnotationBeanPostProcessor——一个随Spring发行版一起发布的BeanPostProcessor实现,它确保用(任意)注释标记的bean上的JavaBean属性实际上(配置为)依赖于注入一个值。
1.8.2. Customizing Configuration Metadata with a BeanFactoryPostProcessor
下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor。这个接口的语义类似于BeanPostProcessor的语义,但有一个主要区别:BeanFactoryPostProcessor操作bean配置元数据。也就是说,Spring IoC容器允许BeanFactoryPostProcessor读取配置元数据,并可能在容器实例化除BeanFactoryPostProcessor实例之外的任何bean之前更改它。
您可以配置多个BeanFactoryPostProcessor实例,并且可以通过设置order属性来控制这些BeanFactoryPostProcessor实例的运行顺序。但是,只有当BeanFactoryPostProcessor实现有序接口时,才能设置此属性。如果您编写自己的BeanFactoryPostProcessor,也应该考虑实现有序接口。有关详细信息,请参阅BeanFactoryPostProcessor和有序接口的javadoc。
**如果您想更改实际的bean实例(即,从配置元数据创建的对象),那么您需要使用BeanPostProcessor(在前面使用BeanPostProcessor定制bean时描述过)。虽然在技术上可以在BeanFactoryPostProcessor中处理bean实例(例如,通过使用BeanFactory.getBean()),这样做会导致bean实例化过早,违反标准容器生命周期。这可能会导致负面的副作用,比如绕过bean的后处理。
此外,BeanFactoryPostProcessor实例的作用域是每个容器。这只在使用容器层次结构时才相关。如果在一个容器中定义一个BeanFactoryPostProcessor,那么它只应用于该容器中的bean定义。一个容器中的Bean定义不会由另一个容器中的BeanFactoryPostProcessor实例进行后处理,即使两个容器都属于同一层次结构的一部分。
当在ApplicationContext中声明bean工厂后处理器时,它将自动执行,以便将更改应用于定义容器的配置元数据。Spring包含许多预定义的bean工厂后处理程序,比如PropertyOverrideConfigurer和PropertyPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor—例如,注册自定义属性编辑器。
ApplicationContext自动检测部署到其中实现BeanFactoryPostProcessor接口的任何bean。它在适当的时候使用这些bean作为bean工厂的后处理程序。您可以像部署任何其他bean一样部署这些后处理器bean。
与使用beanpostprocessor一样,您通常不希望配置beanfactorypostprocessor来进行延迟初始化。如果没有其他bean引用bean(工厂)后处理器,则根本不会实例化该后处理器。因此,将其标记为延迟初始化将被忽略,即使
在元素的声明中将default-lazy-init属性设置为true, Bean(工厂)后处理器也将被急切地实例化。
示例:类名替换PropertyPlaceholderConfigurer
您可以使用PropertyPlaceholderConfigurer通过使用标准Java属性格式将属性值从bean定义外部化到一个单独的文件中。这样做使部署应用程序的人员能够定制特定于环境的属性,例如数据库url和密码,而不需要修改主XML定义文件或容器文件的复杂性或风险。
考虑以下基于xml的配置元数据片段,其中定义了具有占位符值的数据源:
该示例显示了从外部属性文件配置的属性。在运行时,PropertyPlaceholderConfigurer应用于替换数据源某些属性的元数据。要替换的值指定为表单${property-name}的占位符,它遵循Ant、log4j和JSP EL样式。
实际值来自另一个标准Java属性格式的文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,$ {jdbc.用户名}字符串在运行时被替换为值“sa”,同样的情况也适用于与属性文件中的键匹配的其他占位符值。PropertyPlaceholderConfigurer检查bean定义的大多数属性和属性中的占位符。此外,还可以自定义占位符前缀和后缀。
使用Spring 2.5中引入的上下文命名空间,您可以使用专用的配置元素配置属性占位符。您可以在location属性中以逗号分隔的列表的形式提供一个或多个位置,如下面的示例所示:
PropertyPlaceholderConfigurer不仅在您指定的属性文件中查找属性。默认情况下,如果不能在指定的属性文件中找到属性,它还会检查Java系统属性。您可以使用以下三个受支持的整数值之一来设置配置程序的systemPropertiesMode属性,从而自定义此行为:
never(0):从不检查系统属性。
回退(1):如果在指定的属性文件中无法解析,则检查系统属性。这是默认值。
覆盖(2):首先检查系统属性,在尝试指定的属性文件之前。这允许系统属性覆盖任何其他属性源。
**有关更多信息,请参见PropertyPlaceholderConfigurer javadoc。
您可以使用PropertyPlaceholderConfigurer来替换类名,当您必须在运行时选择特定的实现类时,这有时很有用。下面的例子说明了如何做到这一点:
classpath:com/something/strategy.properties
custom.strategy.class=com.something.DefaultStrategy
如果在运行时不能将类解析为有效的类,则在即将创建bean时,即在非惰性init bean的ApplicationContext的预实例化esingletons()阶段,bean的解析将失败。
例子:PropertyOverrideConfigurer
另一个bean工厂后处理器PropertyOverrideConfigurer类似于PropertyPlaceholderConfigurer,但与后者不同,原始定义可以具有bean属性的默认值,也可以完全没有值。如果覆盖属性文件没有特定bean属性的条目,则使用缺省上下文定义。
注意,bean定义不知道被覆盖,所以从XML定义文件中不能立即看出使用了覆盖配置程序。在多个属性的情况下PropertyOverrideConfigurer实例为同一个bean属性定义不同的值,由于覆盖机制,最后一个实例胜出。
属性文件配置行采用以下格式:beanName.property=value
下面的清单显示了这种格式的一个例子:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
此示例文件可以与容器定义一起使用,容器定义包含一个名为dataSource的bean,该bean具有驱动程序和url属性。
还支持复合属性名,只要路径的每个组件(被覆盖的最终属性除外)都是非空的(假设由构造函数初始化)。在下面的例子中,tom bean的fred属性的bob属性的sammy属性被设置为标量值123:tom.fred.bob.sammy=123
指定的覆盖值总是文字值。它们不会被转换成bean引用。当XML bean定义中的原始值指定bean引用时,此约定也适用。
使用Spring 2.5中引入的上下文命名空间,可以使用专用的配置元素配置覆盖属性,如下例所示:
1.8.3使用FactoryBean定制实例化逻辑
您可以实现org.springframework.bean .factory。FactoryBean接口用于本身就是工厂的对象。
FactoryBean接口是一个可插入Spring IoC容器实例化逻辑的点。如果您有复杂的初始化代码,相对于(潜在的)冗长的XML,用Java更好地表达,那么您可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将定制的FactoryBean插入容器中。
FactoryBean接口提供了三种方法:
对象getObject():返回工厂创建的对象的实例。实例可以共享,这取决于该工厂返回的是单例还是原型。
布尔isSingleton():如果FactoryBean返回单例,则返回true,否则返回false。
类getObjectType():返回getObject()方法返回的对象类型,如果类型事先不知道,则返回null。
FactoryBean概念和接口在Spring框架中的许多地方都使用。FactoryBean接口的50多个实现附带Spring本身。
当您需要向容器请求实际的FactoryBean实例本身而不是它生成的bean时,在调用ApplicationContext的getBean()方法时,在bean的id前面加上&符号。因此,对于id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的乘积,而调用getBean(“&myBean”)将返回FactoryBean