Spring提供了一些接口可以用于自定义Bean的特性。我们将从下面几个方面进行介绍
- 生命周期回调
- ApplicationContextAware和BeanNameAware
- 其它Aware接口
生命周期回调
为了与容器的bean生命周期的管理进行交互,可以实现Spring InitializingBean和DisposableBean接口。容器为前者调用AfterPropertieSet(),为后者调用destroy(),以便bean在初始化和销毁bean时执行某些操作。Spring Framework 使用了 BeanPostProcessor 的实现来处理任何回调接口,它可以发现并调用合适的方法。如果您需要定制功能或Spring默认不提供的其它生命周期行为,可以实现BeanPostProcessor接口。
除了初始化和销毁回调外,Spring管理的对象还可以实现Lifecycle接口以便这些对象可以在容器自身的生命周期的驱动下参与启动和关闭过程。
初始化回调
org.springframework.beans.factory.InitializingBean接口允许在bean的所有属性被设置完成后执行初始化工作。InitializingBean接口有一个简单方法:
void afterPropertiesSet() throws Exception;
建议不要使用InitializingBean接口,因为它会让代码与Spring耦合。推荐使用@PostConstruct注释或指定POJO初始化方法。对于基于XML的配置元数据,可以使用init-method属性指定一个无参和无返回类型的方法。基于Java configuration,可以使用@Bean的initMethod属性。关于Java注解方式,后面章节会细讲。
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
上面两个方法效果一样,但第二个会有代码耦合
销毁回调
实现org.springframework.beans.factory.DisposableBean接口,当容器包含的bean被销毁时,允许它获得回调。
DisposableBean接口中仅有一个方法:
void destroy() throws Exception;
建议您不要使用DisposableBean回调接口,因为它不将代码耦合到Spring中。推荐使用@PreDestroy注释或指定bean定义支持的通用方法。使用基于XML的配置元数据,可以在<bean/>上使用destroy-method属性。基于Java configuration,可以使用@Bean的destroyMethod属性。关于Java注解方式,后面章节会细讲。
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
默认的初始化和销毁方法
当你写初始化和销毁方法回调时,如果不使用Spring特定的InitializingBean和DisposableBean回调接口,你通常会写一些名称为init()、initialize()、dispose()的方法,如此等等。理想情况下,这种生命周期回调方法的名称在整个项目中是标准化的,这样所有的开发者都会使用相同的方法名称,确保一致性。
你可以配置Spring容器来 "寻找 "每个Bean上命名的初始化和销毁回调方法名称。这意味着你,作为一个应用开发者,可以编写你的应用类,并使用一个名为init()的初始化回调,而不必在每个Bean定义中配置init-method="init "属性。当Bean被创建时,Spring IoC容器会调用该方法。
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.");
}
}
}
<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属性来类似地配置destroy方法回调。如果现有的Bean类已经有了与惯例不同的回调方法,你可以通过使用<bean/>本身的init-method和destroy-method属性来指定(在XML中)方法的名称,从而覆盖默认。
Spring容器保证在Bean被提供了所有的依赖关系后立即调用配置的初始化回调。因此,初始化回调是在原始Bean引用上调用的,这意味着AOP拦截器等还没有应用到Bean上。首先完全创建一个目标Bean,然后应用一个带有拦截器链的AOP代理。如果目标Bean和代理是分开定义的,你的代码甚至可以绕过代理,与原始的目标Bean进行交互。因此,将拦截器应用于init方法是不一致的,因为这样做会将目标Bean的生命周期与它的代理或拦截器联系起来,当你的代码直接与原始目标Bean交互时,会留下奇怪的语义。
Bean生命周期机制
从Spring 2.5开始,有三种控制Bean的生命周期的方式:
- InitializingBean和DisposableBean的回调接口
- 自定义init()和destroy()方法
- @PostConstruct和@PreDestroy注解。你可以结合这些机制来控制一个Bean的生命周期。
为同一个Bean配置多个生命周期机制,具有不同的初始化方法,被调用的情况如下:
- 用@PostConstruct注解
- InitializingBean回调接口所定义的afterPropertiesSet()
- 配置自定义的init()方法
销毁方法的调用顺序是一样
- 用@PreDestroy 注解
- DisposableBean回调接口所定义的destroy()
- 配置自定义的destroy()方法
启动和关闭回调
Lifecycle接口定义了启动/停止生命周期控制的方法
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
Spring管理的对象都可以实现Lifecycle接口。当ApplicationContext收到启动和停止信号时(例如,在运行时的停止/重启场景),它将这些调用级联到定义在该上下文中的所有Lifecycle实现。它通过委托给一个LifecycleProcessor来实现,如下表所示。
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
请注意,LifecycleProcessor是Lifecycle接口的一个扩展。它添加了两个方法来对上下文的刷新和关闭做出反应
请注意,常规的org.springframework.context.Lifecycle接口是一个启动和停止通知的普通接口,并不会在上下文刷新时自动启动。如果要对特定Bean的自动启动进行细粒度控制(包括启动阶段),可以考虑实现org.springframework.context.SmartLifecycle来代替。
另外,请注意,停止通知并不保证在销毁前出现。在定期shutdown时,所有Bean生命周期都会在一般销毁回调被传播之前首先收到停止通知。然而,在上下文生命周期中的热刷新或停止刷新尝试时,只有destroy()方法被调用。
启动和关闭调用的顺序可能很重要。如果任何两个对象之间存在 "依赖 "关系,被依赖方在其依赖方之后启动,在其依赖方之前停止。然而,有时候,直接的依赖关系是未知的。你可能只知道某种类型的对象应该在另一种类型的对象之前启动。在这些情况下,SmartLifecycle接口定义了另一个选项,即其超类接口Phased上定义的getPhase()方法。下面的列表显示了Phased接口的定义。
public interface Phased {
int getPhase();
}
SmartLifecycle接口:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
当启动时,相对较低阶段的对象先启动。当停止时,顺序则相反。因此,一个实现了SmartLifecycle并且其getPhase()方法返回Integer.MIN_VALUE的对象将是最先启动和最后停止的对象。在考虑phase值时,同样重要的是要知道,任何没有实现SmartLifecycle的 "normal "生命周期对象的默认相位是0。 因此,任何负的phase值表示一个对象应该在那些标准组件之前启动(并在它们之后停止)。反之,任何正的phase值也是如此。
由SmartLifecycle定义的stop()方法接受一个回调。任何实现都必须在该实现的关闭过程完成后调用该回调的run()方法。这在必要时可以实现异步关机,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor会等待每个阶段内的对象组调用该回调,直到其超时值。每个阶段的默认超时是30秒。你可以通过在上下文中定义一个名为lifecycleProcessor的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()方法所返回的布尔值。如果为真,该对象将在此时启动,而不是等待上下文或其自身start()方法的显式调用。如前所述,phase值和任何 "依赖 "关系决定了启动的顺序。
在非web application中优雅地关闭Spring IoC容器
如果你在非web应用环境中使用Spring的IoC容器,请向JVM注册一个shutdown钩子。这样做可以确保优雅地关闭,并在你的单例Bean上调用相关的destroy方法,从而释放所有资源。你仍然必须正确配置和实现这些销毁回调。
要注册一个shutdown钩子,请调用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("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...
}
}
ApplicationContextAware和BeanNameAware
如果一个类实现了org.springframework.context.ApplicationContextAware接口,那么该类的对象实例将被提供给它所运行的ApplicationContext的引用。ApplicationContextAware接口有一个以ApplicationContext为参数的方法setApplicationContext。
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
通过实现ApplicationContextAware接口,Bean可以访问Spring的ApplicationContext,因此Bean可以以编程方式操作创建它们的ApplicationContext。它的主要用途是使用应用上下文对其他Bean进行程序化检索。
ApplicationContextAware
ApplicationContextAware使用场景:
- 在单例bean中注入原型bean
- 从其它不在Spring容器中的代码中访问Spring Bean时
单例bean中注入原型bean
假设我们有两个类RequestManager和RequestHandler,其中RequestHandler类的bean scope是prototype,而RequestManager的bean scope是singleton。RequestManager依赖于RequestHandler,因为RequestHandler的作用域是prototype,所以你希望有一个新的RequestHandler实例,而不是在Spring容器中使用同一个实例。
RequestManager类可以实现ApplicationContextAware接口,并通过ApplicationContext的引用获得RequestHandler的实例。
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class RequestManager implements ApplicationContextAware{
private RequestHandler requestHandler;
private ApplicationContext applicationContext;
public void handleRequest(){
requestHandler = getRequestHandler();
requestHandler.handleRequest();
}
// method to return new instance
public RequestHandler getRequestHandler() {
return applicationContext.getBean("requestHandler", RequestHandler.class);
}
public void setRequestHandler(RequestHandler requestHandler) {
this.requestHandler = requestHandler;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
}
BeanNameAware
BeanNameAware 接口只有一个方法 setBeanName()。实现 org.springframework.beans.factory.BeanNameAware接口的 bean,在 bean加载的过程中可以获取指定name名称的引用。
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
该回调在Bean属性装载后被调用,但在初始化回调(如InitializingBean afterPropertiesSet或自定义init-method)之前被调用。
示例
import org.springframework.beans.factory.BeanNameAware;
public class CashPayment implements BeanNameAware{
private String name;
public void executePayment() {
System.out.println("Perform Payment using " + name);
}
@Override
public void setBeanName(String name) {
this.name = name;
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public CashPayment cashPayment() {
return new CashPayment();
}
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
CashPayment cashPayment = context.getBean("cashPayment", CashPayment.class);
cashPayment.executePayment();
context.registerShutdownHook();
}
}
Output:
Perform Payment using cashPaymentBean
其它Aware在后续专门讲解,请关注