目录
1.6.1. 生命周期回调(Lifecycle Callbacks)
1.6.2. ApplicationContextAware 和 BeanNameAware
1.6. 自定义bean原生特性
Spring框架提供了许多接口,您可以使用这些接口来定制bean的性质。本节将它们分组如下:
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托管对象还可以实现Lifecycle接口,以便这些对象可以参与启动和关闭过程,由容器自身的生命周期驱动。
本节介绍了Lifecycle回调接口。
初始化回调
使用org.springframework.beans.factory.InitializingBean接口,容器在容器上设置了所有必需的属性后,就可以执行初始化工作。 InitializingBean接口指定一个方法:
java 示例代码
void afterPropertiesSet() throws Exception;
kotlin示例代码
fun afterPropertiesSet()
我们建议不要使用InitializingBean接口,因为它不必要地将代码耦合到Spring。或者,我们建议使用@PostConstruct注解或指定POJO初始化方法。对于基于XML的配置元数据,可以使用init method属性指定具有void no argument签名的方法的名称。使用Java配置,可以使用@Bean的initMethod属性。请参阅接收生命周期回调。请参考以下示例:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
java 示例代码
public class ExampleBean {
public void init() {
// do some initialization work
}
}
kotlin示例代码
class ExampleBean {
fun init() {
// do some initialization work
}
}
前面的示例与下面的示例(由两个列表组成)具有几乎完全相同的效果:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
java 示例代码
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
kotlin示例代码
class AnotherExampleBean : InitializingBean {
override fun afterPropertiesSet() {
// do some initialization work
}
}
然而,前面两个示例中的第一个并没有将代码耦合到Spring。
销毁回调
实现org.springframework.beans.factory.DisposableBean接口可以让bean在包含它的容器被销毁时获得回调。DisposableBean接口指定一个方法:
java 示例代码
void destroy() throws Exception;
kotlin示例代码
fun destroy()
我们建议不要使用DisposableBean回调接口,因为它不必要地将代码耦合到Spring。或者,我们建议使用@PreDestroy注解或指定bean定义支持的泛型方法。使用基于XML的配置元数据,可以在<bean/>上使用destroy method属性。使用Java配置,可以使用@Bean的destroyMethod属性。请参阅接收生命周期回调。参考以下定义:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
java 示例代码
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
kotlin示例代码
class ExampleBean {
fun cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
上述定义与以下定义具有几乎完全相同的效果:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
java 示例代码
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
kotlin示例代码
class AnotherExampleBean : DisposableBean {
override fun destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是,前面两个定义中的第一个并没有将代码耦合到Spring。
您可以为<bean>元素的destroy-method属性分配一个特殊的(推断的)值,该值指示Spring自动检测特定bean类上的公共关闭或关闭方法。 (因此,任何实现java.lang.AutoCloseable或java.io.Closeable的类都将匹配。)您还可以在<beans>元素的default-destroy-method属性上设置此特殊(推断)值,以将此行为应用于 一整套豆(请参阅默认初始化和销毁方法)。 请注意,这是Java配置的默认行为。
默认初始化和销毁方法
在编写不使用特定于Spring的initializengbean和DisposableBean回调接口的初始化和销毁方法回调时,通常会编写名称为init()、initialize()、dispose()等的方法。理想情况下,这种生命周期回调方法的名称在整个项目中是标准化的,这样所有开发人员都使用相同的方法名称并确保一致性。
您可以将Spring容器配置为“look”命名的初始化,并销毁每个bean上的回调方法名称。 这意味着,作为应用程序开发人员,您可以编写应用程序类并使用称为init()的初始化回调,而不必为每个bean定义配置init-method ="init"属性。 当创建bean时,Spring IoC容器调用该方法(并按照前面描述的标准生命周期回调协定)。 此功能还对初始化和销毁方法回调强制执行一致的命名约定。
假设您的初始化回调方法命名为init(),而destroy回调方法命名为destroy()。 然后,您的类类似于以下示例中的类:
java 示例代码
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.");
}
}
}
kotlin示例代码
class DefaultBlogService : BlogService {
private var blogDao: BlogDao? = null
// this is (unsurprisingly) the initialization callback method
fun init() {
if (blogDao == null) {
throw IllegalStateException("The [blogDao] property must be set.")
}
}
}
然后,您可以在类似于以下内容的Bean中使用该类:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
顶级<beans />元素属性上存在default-init-method属性,导致Spring IoC容器将Bean类上称为init的方法识别为初始化方法回调。 创建和组装bean时,如果bean类具有这种方法,则会在适当的时间调用它。
您可以通过使用顶级<beans />元素上的default-destroy-method属性类似地(在XML中)配置destroy方法回调。
如果现有的Bean类已经具有按约定命名的回调方法,则可以通过使用<bean/>本身的init-method和destroy-method属性指定(在XML中)方法名称来覆盖默认方法。
Spring容器保证在为bean提供所有依赖项后立即调用已配置的初始化回调。 因此,在原始bean引用上调用了初始化回调,这意味着AOP拦截器等尚未应用于bean。 首先完全创建目标bean,然后应用带有其拦截器链的AOP代理(例如)。 如果目标Bean和代理分别定义,则您的代码甚至可以绕过代理与原始目标Bean进行交互。 因此,将拦截器应用于init方法将是不一致的,因为这样做会将目标Bean的生命周期耦合到其代理或拦截器,并且当您的代码直接与原始目标Bean进行交互时会留下奇怪的语义。
组合生命周期机制
从Spring2.5开始,您有三个控制bean生命周期行为的选项:
-
InitializingBean
和DisposableBean
回调接口 -
自定义
init()
和destroy()
方法 -
@PostConstruct
和@PreDestroy
注解。您可以结合使用这些机制来控制给定的bean。
如果为一个bean配置了多个生命周期机制,并且为每个机制配置了不同的方法名称,则将按照此注解后列出的顺序执行每个已配置的方法。 但是,如果为多个生命周期机制中的多个生命周期配置了相同的方法名称(例如,为初始化方法使用init()),则该方法将执行一次,如上一节所述。
为同一bean配置的多个生命周期机制,使用不同的初始化方法,调用如下:
-
为方法添加
@PostConstruct 注解
-
由InitializingBean回调接口定义的afterPropertiesSet()
-
自定义配置
init() 方法
销毁方法的调用顺序相同:
-
为方法添加
@PreDestroy注解
-
由DisposableBean
回调接口定义的destroy()
-
自定义配置
destroy() 方法
启动和关闭回调
Lifecycle接口为具有自己的生命周期要求(例如启动和停止某些后台进程)的任何对象定义基本方法:
java 示例代码
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
kotlin示例代码
interface Lifecycle {
fun start()
fun stop()
val isRunning: Boolean
}
任何Spring管理的对象都可以实现Lifecycle接口。 然后,当ApplicationContext本身接收到启动和停止信号时(例如,对于运行时的停止/重新启动方案),它将把这些调用级联到在该上下文中定义的所有Lifecycle实现。 它通过委派给LifecycleProcessor来做到这一点,如以下清单所示
java 示例代码
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
kotlin示例代码
interface LifecycleProcessor : Lifecycle {
fun onRefresh()
fun onClose()
}
请注意,LifecycleProcessor本身是Lifecycle接口的扩展。 它还添加了两种其他方法来对刷新和关闭的上下文做出响应。
请注意,常规的org.springframework.context.Lifecycle接口是用于显式启动和停止通知的普通协议,并不意味着在上下文刷新时自动启动。 为了对特定bean的自动启动(包括启动阶段)进行细粒度的控制,请考虑改为实现org.springframework.context.SmartLifecycle。
另外,请注意,不能保证会在销毁之前发出停止通知。 在常规关闭时,在传播常规销毁回调之前,所有Lifecycle bean首先都会收到停止通知。 但是,在上下文的生命周期内进行热刷新或刷新尝试失败时,仅调用destroy方法。 |
启动和关闭调用的顺序可能很重要。 如果任何两个对象之间存在“依赖”关系,则依赖方在其依赖之后开始,而在依赖之前停止。 但是,有时直接依赖项是未知的。 您可能只知道某种类型的对象应该先于另一种类型的对象开始。 在这些情况下,SmartLifecycle接口定义了另一个选项,即在其超级接口Phased上定义的getPhase()方法。 以下清单显示了Phased接口的定义:
java 示例代码
public interface Phased {
int getPhase();
}
kotlin示例代码
interface Phased {
val phase: Int
}
以下清单显示了SmartLifecycle接口的定义:
java 示例代码
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
kotlin示例代码
interface SmartLifecycle : Lifecycle, Phased {
val isAutoStartup: Boolean
fun stop(callback: Runnable)
}
启动时,相位最低的对象先启动。停此时,按相反的顺序行驶。因此,实现SmartLifecycle且其getPhase()方法返回Integer.MIN_值的对象将是最先开始和最后停止的对象之一。在频谱的另一端,Integer.MAX_value的相位值将指示对象应最后启动并首先停止(可能是因为它依赖于要运行的其他进程)。在考虑阶段值时,还必须知道任何未实现SmartLifecycle的“正常”生命周期对象的默认阶段为0。因此,任何负相位值都表示对象应该在这些标准组件之前开始(并在它们之后停止)。对于任何正相位值,反之亦然。
SmartLifecycle定义的stop方法接受回调。任何实现都必须在该实现的关闭过程完成后调用该回调的run()方法。这将在必要时启用异步关闭,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor将等待每个阶段中的对象组调用该回调的超时值。每个阶段的默认超时为30秒。通过在上下文中定义一个名为lifecycle processor的bean,可以覆盖默认的生命周期处理器实例。如果只想修改超时,定义以下内容就足够了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。 后者驱动关闭过程,就好像已经明确调用了stop()一样,但是它在上下文关闭时发生。 另一方面,“刷新”回调启用SmartLifecycle bean的另一个功能。 刷新上下文后(在所有对象都实例化和初始化之后),该回调将被调用。 届时,默认生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。 如果为true,则从那时开始启动该对象,而不是等待上下文或其自己的start()方法的显式调用(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。 相位值和任何“依赖”关系决定了启动顺序,如前所述。
在非Web应用程序中优雅地关闭Spring IoC容器
本节仅适用于非web应用程序。Spring的基于web的ApplicationContext实现已经准备好了代码,以便在相关web应用程序关闭时优雅地关闭Spring IoC容器。
如果在非web应用程序环境中(例如,在富客户机桌面环境中)使用Spring的IoC容器,请向JVM注册一个关闭挂钩。这样做可以确保正常关机,并调用singleton bean上的相关销毁方法,以便释放所有资源。您仍然必须正确配置和实现这些销毁回调。
要注册关闭挂钩,请调用在ConfigurableApplicationContext接口上声明的registerShutdownHook()方法,如以下示例所示:
java 示例代码
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...
}
}
kotlin示例代码
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main() {
val ctx = 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
和 BeanNameAware
当ApplicationContext创建实现org.springframework.context.ApplicationContextAware接口的对象实例时,将为该实例提供对该ApplicationContext的引用。 以下清单显示了ApplicationContextAware接口的定义:
java 示例代码
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
kotlin示例代码
interface ApplicationContextAware {
@Throws(BeansException::class)
fun setApplicationContext(applicationContext: ApplicationContext)
}
因此,bean可以通过ApplicationContext接口或通过将引用转换为该接口的已知子类(例如ConfigurableApplicationContext,它公开了其他功能)来以编程方式操纵创建它们的ApplicationContext。 一种用途是通过编程方式获取其他bean。 有时,此功能很有用。 但是,通常应避免使用它,因为它会将代码耦合到Spring,并且不遵循控制反转样式,在该样式中,将协作者作为属性提供给bean。 ApplicationContext的其他方法提供对文件资源的访问,发布应用程序事件以及访问MessageSource。 这些附加功能在ApplicationContext的其他功能中进行了描述。
自动装配是获得对ApplicationContext的引用的另一种方法。 传统的构造函数和byType自动装配模式(如“自动装配协作器”中所述)可以分别为构造函数参数或setter方法参数提供ApplicationContext类型的依赖项。 要获得更大的灵活性,包括能够自动连接字段和使用多个参数方法,请使用基于注解的自动装配功能。 如果这样做,则将ApplicationContext自动连接到需要使用ApplicationContext类型的字段,构造函数参数或方法参数中(如果有问题的字段,构造函数或方法带有@Autowired注解)。 有关更多信息,请参见使用@Autowired。
当ApplicationContext创建一个实现org.springframework.beans.factory.BeanNameAware接口的类时,该类将获得对其关联对象定义中定义的名称的引用。 以下清单显示了BeanNameAware接口的定义:
java 示例代码
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
kotlin示例代码
interface BeanNameAware {
@Throws(BeansException::class)
fun setBeanName(name: String)
}
回调在正常bean属性填充之后调用,但在初始化回调(如InitializingBean、afterPropertiesSet或自定义init方法)之前调用。
1.6.3. 其它Aware
接口
除了ApplicationContextAware和BeanNameAware(前面已经讨论过)之外,Spring还提供了多种Aware回调接口,这些接口使Bean向容器指示它们需要某种基础结构依赖性。 通常,名称表示依赖项类型。 下表总结了最重要的Aware接口:
Table 4. Aware interfaces
Name | Injected Dependency | Explained in… |
ApplicationContextAware | Declaring ApplicationContext . | ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware | Event publisher of the enclosing ApplicationContext . | Additional Capabilities of the ApplicationContext |
BeanClassLoaderAware | Class loader used to load the bean classes. | Instantiating Beans |
BeanFactoryAware | Declaring BeanFactory . | ApplicationContextAware and BeanNameAware |
BeanNameAware | Name of the declaring bean. | ApplicationContextAware and BeanNameAware |
BootstrapContextAware | Resource adapter BootstrapContext the container runs in. Typically available only in JCA-aware ApplicationContext instances. | JCA CCI |
LoadTimeWeaverAware | Defined weaver for processing class definition at load time. | Load-time Weaving with AspectJ in the Spring Framework |
MessageSourceAware | Configured strategy for resolving messages (with support for parametrization and internationalization). | Additional Capabilities of the ApplicationContext |
NotificationPublisherAware | Spring JMX notification publisher. | Notifications |
ResourceLoaderAware | Configured loader for low-level access to resources. | Resources |
ServletConfigAware | Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext . | Spring MVC |
ServletContextAware | Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext . | Spring MVC |
再次注意,使用这些接口会将您的代码与Spring API绑定在一起,并且不遵循“控制反转”样式。 因此,我们建议将它们用于需要以编程方式访问容器的基础结构Bean。