Spring
框架提供一些接口,你可以使用这些接口去自定义bean
的性质。这个章节包括下面内容:
-
生命周期回调
-
其他的
Aware
接口
1.6.1 生命周期回调
为了与容器的bean
的生命周期的管理交互,你可以实现Spring
提供的InitializingBean
和DisposableBean
接口。容器为前者调用afterPropertiesSet()
并为后者调用destroy()
,以使Bean
在初始化和销毁Bean
时执行某些操作。
在现代化的
Spring
应用中,JSR-250
的@PostConstruct
和@PreDestroy
注解是一般被考虑的最好实践去接收生命周期回调。使用这些注解意味着你的这些bean
不需要耦合Spring
规范接口。更多详情,查看@PostConstruct
和@PreDestroy
如果你不想去使用
JSR-250
注解,但是你仍然想移除耦合,考虑init-method和destroy-method
bean的元数据定义。
内部地,Spring
框架使用BeanPostProcessor
实现去处理任何的回调接口,它能找到和调用适合的方法。如果你需要定制特性或者其他的生命周期行为Spring
默认没有提供,你需要自己实现一个BeanPostProcessor
。关于更多的信息,查看容器扩展点。
除了初始化和销毁回调外,Spring
管理的对象还可以实现Lifecycle
接口以便这些对象可以在容器自身的生命周期的驱动下参与启动和关闭过程。
生命周期回调接口在这章节中描述。
-
初始化回调
org.springframework.beans.factory.InitializingBean
接口允许在bean
的所有属性被设置之前执行初始化工作。InitializingBean
接口有一个简单方法:void afterPropertiesSet() throws Exception;
我们推荐你不要去使用
InitializingBean
接口,因为它不必要的耦合Spring
。或者,我们建议使用@PostConstruct
注解或者指定一个POJO
初始化方法。在基于XML
配置元数据实例中,你可以使用init-method
属性去指定方法的名称并且方法没有返回值和无参数签名。在JavaConfig
中,你可以使用@Bean
的initMethod
属性。查看接收生命周期回调。考虑下面的例子:<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean { public void init() { // do some initialization work } }
上面的例子几乎和下面的例子完全一样(由两个清单组成):
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean { @Override public void afterPropertiesSet() { // do some initialization work } }
然而,前面两个例子中的第一个没有耦合
Spring
代码。参考代码:
com.liyong.ioccontainer.starter.XmlInitializationIocContainer
-
销毁回调
当包含
bean
的容器被销毁时,实现org.springframework.beans.factory.DisposableBean
接口的bean
获得回调。DisposableBean
接口有个简单方法:void destroy() throws Exception;
我们推荐你不要使用
DisposableBean
回调接口,因为它不必要的耦合Spring
代码。或者,我们建议使用@PreDestroy
注解或者指定一个通用方法通过bean
的定义支持。基于XML
的配置元数据,你可以在上使用destroy-method
属性。通过JavaConfig
配置,你可以使用@Bean
的destroyMethod
属性。查看接收生命周期回调。考虑下面的定义:<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean { public void cleanup() { // do some destruction work (like releasing pooled connections) } }
上面的定义有几乎完全与下面定义效果相同:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean { @Override public void destroy() { // do some destruction work (like releasing pooled connections) } }
然而,前面两个定义中的第一个没有耦合Spring代码。
你可以赋值元素的
destroy-method
属性值,这指示Spring
自动的在bean
类上检测一个公共的close
或者shutdown
方法。(因此,任何实现java.lang.AutoCloseable
或java.io.Closeable
的类都将匹配)你可以在元素的default-destroy-method
属性设置这个指定的值去应用这个行为到整个bean
容器(查看默认初始化和销毁方法)。注意:这个一个默认行为在JavaConfig
中。参考代码:
com.liyong.ioccontainer.starter.XmlDestuctionIocContainer
-
默认初始化和销毁方法
当你写初始化和销毁方法回调的时候,不要使用
Spring
指定的InitializingBean
和DisposableBean
回调接口,典型的命名例如:init()
、initialize()
、dispose()
等等。理想地,生命周期回调方法的命名在项目中被标注化,因此所有的开发者使用项目的名字确保一致性。你可以配置
Spring
框架容器去在每个bean
查找被命名初始化和销毁回调方法名称。作为一个应用开发者,这意味着你可以写你的应用类和使用初始化回调调用init()
,没有必要配置每个bean
的定义属性init-method="init"
。当bean
被创建时(并按照之前描述的标准生命周期回调),Spring IoC
容器调用方法。这个特性强调为初始化和销毁回调方法命名的一致性。假设你的初始化回调方法被命名为
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
中使用该类:<beans default-init-method="init"> <bean id="blogService" class="com.something.DefaultBlogService"> <property name="blogDao" ref="blogDao" /> </bean> </beans>
顶层元素属性
default-init-method
中描述导致Spring IoC
容器在bean
的类上去识别一个叫做init
的方法作为初始化方法回调。当bean
被创建和组装好,如果bean
的类有一个方法,它在适当的时候被调用。你可以配置销毁方法回调类似(在
XML
)的通过使用在顶层元素上的default-destroy-method
方法。现有的
Bean
类已经具有回调方法的名称,这些方法的命名方式与约定不符,你可以通过元素属性init-method
和destroy-method
指定方法名称。Spring
容器保证配置初始化方法在bean
提供所有依赖后立即被调用。因此,在原始bean
引用上调用初始化回调,这意味着AOP
拦截器等还没有应用到bean
上。首先完全创建目标bean
,然后应用带有其拦截器链的AOP
代理。如果目标Bean
和代理分别定义,则你的代码甚至可以绕过代理与原始目标Bean
进行交互。因此,将拦截器应用于init
方法是不一致的,因为这样做会将目标bean
的生命周期耦合到它的代理或拦截器,并在代码直接与原始目标bean
交互时留下奇怪的语义。 -
组合生命周期机制
从Spring2.5后,你有三种可以选择的方式去控制生命周期行为:
InitializingBean
和DisposableBean
回调接口- 自定义
init()
和destroy()
@PostConstruct
和@PreDestroy
注解.。你可以组合这些机制去控制bean
。
如果为bean配置多个生命周期机制并且每个机制被配置不同的方法名称,每个配置的方法都按照此注释后列出的顺序执行。但是,如果为多个生命周期机制中的多个生命周期机制配置了相同的方法名称(例如,为初始化方法使用
init()
),则该方法将执行一次,如上一节所述。为同一个
bean
配置的多种生命周期机制具有不同的初始化方法,如下所示:- 方法注解
@PostConstruct
- 通过
InitializingBean
回调接口定义afterPropertiesSet()
- 自定义配置
init()
方法
销毁方法的调用顺序相同:
- 方法注解
@PreDestroy
- 通过
DisposableBean
回调接口定义destroy()
- 自定义配置
destroy()
方法
-
启动和关闭回调
Lifecycle
接口定义了必须要的方法为这些bean
生命周期所必须(例如:启动和停止一些后台处理)。public interface Lifecycle { void start(); void stop(); boolean isRunning(); }
任何
Spring
管理的对象可能实现Lifecycle
接口。然而,当ApplicationContext
接收到启动和停止信号(例如:在运行时停止和重启场景),它将这些调用级联到在该上下文中定义的所有生命周期实现。通过代理到LifecycleProcessor
处理,在下面清单显示:public interface LifecycleProcessor extends Lifecycle { void onRefresh(); void onClose(); }
注意:
LifecycleProcessor
是Lifecycle
接口的拓展。这个接口增加了两个额外的方法去接收上下文的刷新和关闭。注意:
常规的
org.springframework.context
。生命周期接口是显式启动和停止通知的普通契约,并不意味着在上下文刷新时自动启动。为了更细粒度控制bean
的自动启动(包括启动阶段),考虑使用org.springframework.context.SmartLifecycle
替换。停止通知不会被保证在销毁之前到来。在常规的关闭上,所有
Lifecycle
的bean
第一次接受停止通知在销毁回调被传播之前。但是,在上下文生命周期中进行热更新或更新尝试失败时,仅调用destroy
方法。启动和关闭顺序调用是非常重要的。如果依赖关系存在任何对象之间,依赖侧开始在被依赖之后并且它的停止在它的依赖之前。然而,在运行时,这个直接依赖是未知的。你可能只知道某种类型的对象应该先于另一种类型的对象开始。在这种情况下,
SmartLifecycle
接口定义其他可选,即getPhase()
方法在它的父接口被定义,Phased
。下面的清单显示Phased接口的定义。public interface Phased { int getPhase(); }
下面清单显示
SmartLifecycle
接口定义public interface SmartLifecycle extends Lifecycle, Phased { boolean isAutoStartup(); void stop(Runnable callback); }
启动时,阶段值最低的对象首先启动。停止时,顺序相反。因此,实现
SmartLifecycle
接口并且getPhase()
方法返回Integer.MIN_VALUE
的对象将在第一个启动和最后一个停止。另一种类型,Integer.MAX_VALUE
阶段值指示这个对象最后一个被启动并且第一个被销毁 (可能因为它依赖其他处理运行)。当考虑这个阶段值的时候,重要的是要知道,任何未实现SmartLifecycle
的“正常”生命周期对象的默认阶段为0
。因此,任何负的阶段值表示对象应该启动在这些标准的组件之前(停止在它们之后)。对于任何正阶段值,情况正好相反。停止方法通过
SmartLifecycle
接受一个回调被定义。任何实现必须在实现的关闭处理完成后调用回调的run()
方法。这将在必要时启用异步关闭,因为LifecycleProcessor
接口的默认实现DefaultLifecycleProcessor
会等待其超时值,以等待每个阶段内的对象组调用该回调。每个阶段默认超时时间30秒。你可以通过定义一个bean
命名为lifecycleProcessor
在上下文中覆盖这个默认生命周期处理实例。如果你想仅仅修改超时时间,定义以下内容就足够了:<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor"> <!-- timeout value in milliseconds --> <property name="timeoutPerShutdownPhase" value="10000"/> </bean>
像前面提到的,
LifecycleProcessor
接口定义回调方法为上下文更好的刷新和关闭。后者驱动关闭过程,就好像已经显式调用了stop()
一样,但是它在上下文关闭时发生。另一方面,“refresh
”回调启用了SmartLifecycle
bean
的另一个特性。当这个上下文被刷新(所有的bean
被初始化和实例化),即回调被调用。在那个阶段,默认的生命周期处理器通过每个SmartLifecycle
对象的isAutoStartup()
方法检测返回boolean
值。如果true
,对象在那个阶段被启动而不是等待上下文或者自身的start()
方法显示调用(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。像前面所描述的,phase
值和任何依赖关系确定了启动顺序。参考代码:
com.liyong.ioccontainer.starter.XmlLifecycleIocContainer
-
在非Web应用程序中优雅关闭Spring IoC容器
这个部分仅仅适用非
web
应用程序。Spring
的基于Web
的ApplicationContext
实现提供优雅的关闭Spring IoC
容器,当相关的web
应用程序关闭的时候。如果你使用
Spring
的IoC
容器在非web
应用环境(例如,在一个富客户端桌面环境),在JVM
注册一个关闭钩子。这样做确保优雅的关闭和调用在单例bean
上的销毁方法,因此所有资源被释放。你必须仍然正确地配置和实现这些销毁回调。去注册一个关闭钩子,调用
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"); //增加一个Hook构造回调 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
接口的定义:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,这些bean
可以编程地操作ApplicationContext
创建它们,通过ApplicationContext
接口或者转换引用为已知的这个接口的子类(例如:ConfigurableApplicationContext
,它暴露了附加的功能)。一种用途是通过编程方式检索其他bean
。有时候这个能力非常有用。然而,一般的,你应该避免它,因为它耦合Spring
的代码并且不遵循控制反转格式,将协调者作为bean
的属性。ApplicationContext
其他方法提供获取资源文件、发布应用事件和访问MessageSource
。这些额外的特性在ApplicationContext的附加能力中描述。
自动装配是获得对ApplicationContext
的引用的另一种选择。传统的constructor
和byType
自动装配模式(在自动装配协作器中描述)能够为构造函数或者Setter
方法参数提供一个ApplicationContext
类型的依赖。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,可以使用基于注解的自动装配特性。如果这样做,ApplicationContext
自动装配到字段、构造参数或方法参数,如果字段、构造函数或方法带有@Autowired
注解那么该参数期望ApplicationContext
类型。更多的信息,查看使用@Autowired。
当ApplicationContext
创建一个类并且实现org.springframework.beans.factory.BeanNameAware
接口时,该类将获得对其关联对象定义中定义的名称的引用。下面清单显示BeanNameAware
接口定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
这个回调在bean
被填充之后被调用,但是在初始化回调之前例如InitializingBean
, afterPropertiesSet
或者自定义init-method
。
参考代码:
com.liyong.ioccontainer.starter.XmlAwareIocContainer
1.6.3 其他Aware接口
除了ApplicationContextAware
和BeanNameAware
之外,Spring
提供了广泛的Aware
回调接口,这些接口使Bean
向容器指示它们需要一些基础结构依赖性。作为基本规则,这个名字指示依赖类型。下面的表格总结最重要的Aware
接口:
Name | Injected Dependency | Explained in… |
---|---|---|
ApplicationContextAware |
注入 ApplicationContext . |
ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware |
注入ApplicationEventPublisher . |
Additional Capabilities of the ApplicationContext |
BeanClassLoaderAware |
Class loader used to load the bean classes. | Instantiating Beans |
BeanFactoryAware |
注入 BeanFactory . |
ApplicationContextAware and BeanNameAware |
BeanNameAware |
注入BeanName |
ApplicationContextAware and BeanNameAware |
BootstrapContextAware |
注入BootstrapContext |
JCA CCI |
LoadTimeWeaverAware |
注入LoadTimeWeaver . |
Load-time Weaving with AspectJ in the Spring Framework |
MessageSourceAware |
注入MessageSource |
Additional Capabilities of the ApplicationContext |
NotificationPublisherAware |
注入NotificationPublisher . |
Notifications |
ResourceLoaderAware |
注入ResourceLoader | Resources |
ServletConfigAware |
注入ServletConfig . |
Spring MVC |
ServletContextAware |
注入ServletContext . |
Spring MVC |
再次注意,使用这些接口关联到你的代码到Spring API
并且不遵循控制反转的风格。我们推荐使用基础设施bean
需要编程的去容器获取。
作者
个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。
博客地址: http://youngitman.tech