自定义bean属性
1 生命周期回掉函数
为了与容器管理bean的生命周期相互作用,可以实现Spring的InitializingBean和DisposableBean接口。容器通过调用前者的afterPropertiesSet()函数和后者的destroy()函数,允许bean在初始化和销毁时执行某些操作。
JSR-250的@PostConstruct和@PreDestory注解一般被认为在现代Spring应用中是接受生命周期回掉的最佳实践。使用这些注解意味着不需要与Spring的特定接口耦合。
如果不想使用JSR-250,但是仍想消除耦合,可以使用init-method和destroy-method对象定义元数据。
在内部,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属性指定一个无参数、返回类型为void签名的方法名。使用Java配置,可以使用@Bean的initMethod属性。
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
上例等价于下面的例子,但是代码并不与Spring耦合。
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet(){
// do some initialization work
}
}
销毁回调函数
实现org.springframework.beans.factory.DisposableBean接口允许容器一个bean在被容器销毁时获得回调。DisposableBean接口定义了一个方法:
void destroy() throws Exception;
并不推荐使用DisposableBean回调接口,因为没有必要将代码与Spring耦合。作为替代,可以使用@PreDestory注解或者指定一个bean定义支持的范型方法。使用XML格式的配置元数据,可以使用<bean/>元素的destroy-method属性。使用Java配置,可以使用@Bean的destroyMethdo属性。
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup(){
// do some destruction work (like releasing pooled connections)
}
}
上例等价于下面的例子,但是代码不与Spring耦合。
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
public void destroy(){
//do some destruction work (like releasing pooled connections)
}
}
<bean>元素的destroy-method属性可以指定一个特殊的值——inferred,来指示Spring自动发现指定bean类的公有的close或者shutdown方法(因此任何实现java.lang.AutoCloseable或者java.io.Closeable的类都将匹配)。这个特殊的值(inferred)也可以设置为<beans>元素的default-destroy-method属性以便将此行为应用整个bean的集合。注意,这是Java配置的默认行为。
默认的初始化和销毁方法
当你不使用Spring指定的InitializingBean和DisposableBean回调接口来编写初始化和销毁回调函数,一般的可以将方法命名为init(), initialize(), dispose()等等。理想状态下,这些生命周期回调函数的名字在一个工程中是标准化的,因此所有开发者使用相同的方法名字并保证一致性。
可以配置Spring容器查找每个bean的初始化和销毁回调函数名。这意味着应用开发者可以编写应用类并且使用名为init()的初始化回调函数,而不用在每个bean定义配置init-method="init"属性。这个特性也保证了初始化和销毁回调函数的命名一致性。
假设初始化方法命名为init()、销毁方法命名为destory(),那么Class会类似于下面的例子
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.foo.DefaultBlogService">
<property name="blogDao" ref="blogDao"/>
</bean>
</beans>
这个存在于最上层<beans/>元素的人default-init-method属性导致Spring IoC容器将bean的init方法识别为初始化回调函数。当一个bean被创建和装配,如果bean的类有如下方法,它会在适当的时间被调用。
可以类似的的在<beans/>元素中设置default-destroy-method属性来配置销毁回调函数。
当bean的类中已经存在与约定的名称不同的回调函数时,可以使用<bean/>元素自身的init-method和destroy-method属性来覆盖默认的属性。
Spring容器保证被配置的初始化回调函数会在bean的依赖关系装配完成后立即被调用。因此是在生的bean引用上调用初始化回调函数,这意味着AOP拦截器等等尚未作用于bean。一个目标bean首先被完整创建,然后应用一个具有拦截器链的AOP代理。如果目标bean和代理被分别定义,代码甚至可以绕过代理与原始目标bean进行交互。因此将拦截器应用于初始化方法是不一致的,因为这么做会将目标bean的生命周期和代理/拦截器耦合在一起同时当代码直接于目标bean进行交互时会产生奇怪的语义。
联合使用生命周期机制
在Spring 2.5中有三种控制bean生命周期的行为:InitializingBean和DisposableBean的回调接口;用户自定义的init()和destroy()方法;和@PostConstruct、@PreDestroy注解。可以联合使用这三种机制来控制个bean。
如果一个bean被配置了多个生命周期机制,并且每个机制配置了不同的方法名,那么每个配置的方法都会按下述的顺序执行。然而,如果多种机制配置了相同的方法名——例如,init()初始化方法—,那么方法会被执行一次。
同一个bean配置了多种生命周期机制,不同名称的方法会按下面的顺序执行:
- 标记了@PostConstruct注解的方法;
- IntializingBean接口中定义的afterPropertiesSet()方法;
- 用户配置的init()方法。
销毁方法按如下顺序调用:
- 标记了@PreDestroy注解的方法;
- DisposableBean接口定义的destroy()方法;
- 用户配置的destroy()方法。
启动和关闭回调函数
Lifecycle接口为任何具有自己生命周期要求的对象定义必要的方法(例如开始和停止一些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring管理的对象可以实现这个接口。然后,当ApplicationContext自身接收到开始和停止信号,例如在运行时的停止/重启场景,它会级联的调用定义在其中的所用Lifecycle实现。它通过委托给LifecycleProcessor来完成这项工作:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
请注意,LifeCycleProcessor自身就是Lifecycle接口的扩展。它也添加了两个接口来响应上下文被刷新和关闭的情况。
请注意,常规的org.springframework.context.Lifecycle接口只是明确的启动/停止通知的简单契约,并不意味着在上下文刷新时自动启动。考虑实现org.springframework.context.SmartLifecycle,以便对特定bean的自动启动进行细粒度的控制(包括启动阶段)。另请注意,停止通知不能保证在销毁之前:在常规关闭时,所有Lifecycle bean将在传播给一般销毁回调之前首先收到停止通知;然而,在上下文生命周期的热刷新或中止的刷新尝试时,只会调用destroy方法。
启动和关闭调用的顺序很重要。如果两个对象之间有依赖关系,依赖的一方将会比它的依赖关系晚启动,并且早关闭。然而,许多时候直接的依赖关系是未知的。您也许只知道一些特定类型的对象要比另一些类型的对象优先启动。在这种情况下,SmartLifecycle接口定义了另一种选择,在它的父接口Phased中定义的getPhase()方法。
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
在启动时,具有低phase的对象首先启动,并且在关闭时顺序相反。因此,实现了SmartLifecycle接口并且getPhase()方法的返回值是Integer.MIN_VALUE的对象将对一个被启动并最后一个被结束。在另一端,phase的值是Integer.MAX_VALUE表明对象将被最晚启动、最早结束(有可能它需要其它的处理来运行)。任何普通的没有实现SmartLifecycle接口的Lifecycle对象的phase值为0。因此任何负的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应用中优雅的关闭Spring IoC容器
这部分仅应用于非web应用。Spring基于web的ApplicationContext实现已经包含在相关的web应用关闭时优雅的关闭Spring IoC容器的相关代码。
如果在非web应用环境中使用Spring IoC容器;例如在一个桌面客户端环境;可以注册一个JVM关闭回调。这么做保证了优雅的关闭并且会调用singleton 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(
new String []{"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...
}
}
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的能力。
在Spring 2.5中,自动装配是另一个获取ApplicationContext引用的选择。传统的构造函数和根据类型的自动装配模式可以为构造函数参数或setter方法参数提供ApplicationContext类型的依赖。为了更多的灵活性,包括自动装配字段的能力和多种参数方法,可以使用你的基于注解的自动装配功能。如果这样做,ApplicationContext将自动装配那些标注了@Autowired注解的类型为ApplicationtContext的字段、构造函数参数或者方法参数。
当ApplicationContext创建了一个类实现了org.springframework.beans.factory.BeanNameAware接口,该类被提供其定义的名称的引用。
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
这个方法的调用发生在普通属性设置之后在初始化回调(例如InitializingBean的afterPropertiesSet方法或者用户自定义的初始化方法)调用之前。
3 其它感知接口
除了上文所述的ApplicationContextAware和BeanNameAware,Spring还提供了一批感知接口允许bean向容器指明它们需要的明确基础设施依赖。最重要的感知接口整理如下,作为一般规则,名称是依赖关系类型的良好指示:
名字 | 注入的依赖 |
---|---|
ApplicationContextAware | 声明的ApplicationContext |
ApplicationEventPlulisherAware | ApplicationContext 中的事件发布器 |
BeanClassLoaderAware | 加载Bean使用的类加载器 |
BeanFactoryAware | 声明的BeanFactory |
BeanNameAware | Bean的名字 |
BootstrapContextAware | 容器运行的资源适配器BootstrapContext ,通常仅在JCA环境下有效 |
LoadTimeWeaverAware | 加载期间处理类定义的weaver |
MessageSourceAware | 解析消息的配置策略 |
NotificationPublisherAware | Spring JMX通知发布器 |
PortletConfigAware | 容器当前运行的PortletConfig ,仅在web下的Spring ApplicationContext 中可见 |
PortletContextAware | 容器当前运行的PortletContext ,仅在web下的Spring ApplicationContext 中可见 |
ResourceLoaderAware | 配置的资源加载器 |
ServletConfigAware | 容器当前运行的ServletConfig ,仅在web下的Spring ApplicationContext 中可见 |
ServletContextAware | 容器当前运行的ServletContext ,仅在web下的Spring ApplicationContext 中可见 |
再次注意,使用这些接口将代码和Spring API绑定,并且不遵循控制反转样式。因此,对于基础设施bean建议使用编程的访问方式。