写点什么

Spring 5 中文解析核心篇 -IoC 容器之自定义 Bean 性质

用户头像
青年IT男
关注
发布于: 2020 年 09 月 04 日
Spring 5 中文解析核心篇-IoC容器之自定义Bean性质

Spring框架提供一些接口,你可以使用这些接口去自定义bean的性质。这个章节包括下面内容:



  1. 生命周期回调



  1. ApplicationContextAware和[BeanNameAwar](https://docs.spring.io/spring/docs/5.2.6.RELEASE/spring-framework-reference/core.html#beans-factory-aware)



  1. 其他的Aware接口



###### 1.6.1 生命周期回调



为了与容器的bean的生命周期的管理交互,你可以实现Spring提供的InitializingBeanDisposableBean接口。容器为前者调用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中,你可以使用@BeaninitMethod属性。查看[接收生命周期回调](https://docs.spring.io/spring/docs/5.2.6.RELEASE/spring-framework-reference/core.html#beans-java-lifecycle-callbacks)。考虑下面的例子:



<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的配置元数据,你可以在<bean/>上使用destroy-method属性。通过JavaConfig配置,你可以使用@BeandestroyMethod属性。查看[接收生命周期回调](https://docs.spring.io/spring/docs/5.2.6.RELEASE/spring-framework-reference/core.html#beans-java-lifecycle-callbacks)。考虑下面的定义:



<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代码。



> 你可以赋值<bean>元素的destroy-method属性值,这指示Spring自动的在bean类上检测一个公共的close或者shutdown方法。(因此,任何实现java.lang.AutoCloseablejava.io.Closeable的类都将匹配)你可以在<beans>元素的default-destroy-method属性设置这个指定的值去应用这个行为到整个bean容器(查看默认初始化和销毁方法)。注意:这个一个默认行为在JavaConfig中。



> 参考代码:com.liyong.ioccontainer.starter.XmlDestuctionIocContainer



  • 默认初始化和销毁方法



当你写初始化和销毁方法回调的时候,不要使用Spring指定的InitializingBeanDisposableBean回调接口,典型的命名例如: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>



顶层<beans/>元素属性default-init-method中描述导致Spring IoC容器在bean的类上去识别一个叫做init的方法作为初始化方法回调。当bean被创建和组装好,如果bean的类有一个方法,它在适当的时候被调用。



你可以配置销毁方法回调类似(在XML)的通过使用在顶层<beans/>元素上的default-destroy-method方法。



现有的Bean类已经具有回调方法的名称,这些方法的命名方式与约定不符,你可以通过<bean/>元素属性init-methoddestroy-method指定方法名称。



Spring容器保证配置初始化方法在bean提供所有依赖后立即被调用。因此,在原始bean引用上调用初始化回调,这意味着AOP拦截器等还没有应用到bean上。首先完全创建目标bean,然后应用带有其拦截器链的AOP代理。如果目标Bean和代理分别定义,则你的代码甚至可以绕过代理与原始目标Bean进行交互。因此,将拦截器应用于init方法是不一致的,因为这样做会将目标bean的生命周期耦合到它的代理或拦截器,并在代码直接与原始目标bean交互时留下奇怪的语义。



  • 组合生命周期机制



从Spring2.5后,你有三种可以选择的方式去控制生命周期行为:



* InitializingBean和[DisposableBean](https://docs.spring.io/spring/docs/5.2.6.RELEASE/spring-framework-reference/core.html#beans-factory-lifecycle-disposablebean)回调接口

* 自定义init()destroy()

* @PostConstruct 和 @PreDestroy 注解.。你可以组合这些机制去控制bean



> 如果为bean配置多个生命周期机制并且每个机制被配置不同的方法名称,每个配置的方法都按照此注释后列出的顺序执行。但是,如果为多个生命周期机制中的多个生命周期机制配置了相同的方法名称(例如,为初始化方法使用init()),则该方法将执行一次,如上一节所述。



为同一个bean配置的多种生命周期机制具有不同的初始化方法,如下所示:



1. 方法注解@PostConstruct

2. 通过InitializingBean回调接口定义afterPropertiesSet()

3. 自定义配置init() 方法



销毁方法的调用顺序相同:



1. 方法注解@PreDestroy

2. 通过DisposableBean回调接口定义destroy()

3. 自定义配置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();
}



注意:LifecycleProcessorLifecycle接口的拓展。这个接口增加了两个额外的方法去接收上下文的刷新和关闭。



> 注意:

>

> 常规的org.springframework.context。生命周期接口是显式启动和停止通知的普通契约,并不意味着在上下文刷新时自动启动。为了更细粒度控制bean的自动启动(包括启动阶段),考虑使用org.springframework.context.SmartLifecycle替换。

>

> 停止通知不会被保证在销毁之前到来。在常规的关闭上,所有Lifecyclebean第一次接受停止通知在销毁回调被传播之前。但是,在上下文生命周期中进行热更新或更新尝试失败时,仅调用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的基于WebApplicationContext实现提供优雅的关闭Spring IoC容器,当相关的web应用程序关闭的时候。



如果你使用SpringIoC容器在非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 ApplicationContextAwareBeanNameAware



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的引用的另一种选择。传统的constructorbyType自动装配模式(在自动装配协作器中描述)能够为构造函数或者Setter方法参数提供一个ApplicationContext类型的依赖。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,可以使用基于注解的自动装配特性。如果这样做,ApplicationContext自动装配到字段、构造参数或方法参数,如果字段、构造函数或方法带有@Autowired注解那么该参数期望ApplicationContext类型。更多的信息,查看使用[@Autowired](https://docs.spring.io/spring/docs/5.2.6.RELEASE/spring-framework-reference/core.html#beans-autowired-annotation)



ApplicationContext创建一个类并且实现org.springframework.beans.factory.BeanNameAware接口时,该类将获得对其关联对象定义中定义的名称的引用。下面清单显示BeanNameAware接口定义:



public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}



这个回调在bean被填充之后被调用,但是在初始化回调之前例如InitializingBeanafterPropertiesSet或者自定义init-method



参考代码:com.liyong.ioccontainer.starter.XmlAwareIocContainer



###### 1.6.3 其他Aware接口



除了ApplicationContextAwareBeanNameAware之外,Spring提供了广泛的Aware回调接口,这些接口使Bean向容器指示它们需要一些基础结构依赖性。作为基本规则,这个名字指示依赖类型。下面的表格总结最重要的Aware接口:



再次注意,使用这些接口关联到你的代码到Spring API并且不遵循控制反转的风格。我们推荐使用基础设施bean需要编程的去容器获取。



作者



个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。



博客地址: http://youngitman.tech



CSDN: https://blog.csdn.net/liyong1028826685



微信公众号:

发布于: 2020 年 09 月 04 日阅读数: 72
用户头像

青年IT男

关注

站在巨人肩上看得更远! 2018.04.25 加入

从事金融行业,就职过易极付、思建科技、网约车平台等一流技术团队,目前就职于银行负责支付系统建设。对金融行业有强烈的爱好。实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域

评论

发布
暂无评论
Spring 5 中文解析核心篇-IoC容器之自定义Bean性质