铿然架构 | 作者 / 铿然一叶 这是 铿然架构 的第 114 篇原创文章
1. 介绍
为什么要学习bean生命周期和了解Aware?
在bean的生命周期过程中提供了一些钩子,使得我们有机会实现定制能力,例如封装已有的Bean,生成一个代理对象,从而添加一些行为。
aware有"意识到"、"感知到"的意思,应用层的bean可以通过实现xxxAware接口感知到spring内置的对象,利用它们完成一些操作,例如实现BeanFactoryAware接口可以获得BeanFactory对象,进一步获取到需要的bean实例。
因此,学习bean生命周期和了解aware的目的是为了实现定制能力,并且结合bean生命周期和aware的执行过程,才知道在何时能使用哪个aware,两者要结合起来看。
2. Bean生命周期和aware调用过程
Bean从创建到消亡的生命周期如下:
蓝色部分为类中自行定义实现,绿色部分通过实现接口方法实现,黄色通过注解实现。
注意:jdk9以上已经不带javax.annotation包,以上涉及"@PostConstruct"和"@PreDestroy"注解,需要额外引入依赖包:
java
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
2.1 启动spring日志
启动spring后,以上bean的生命周期方法自动调用,日志如下:
java
MyBean构造器初始化
BeanNameAware.setBeanName:myBean
serviceAddress=10.8.9.4
BeanClassLoaderAware.setBeanClassLoader
BeanFactoryAware.setBeanFactory
ApplicationContextAware.setApplicationContext
@PostConstruct
InitializingBean.afterPropertiesSet
@Bean.initMethod
BeanPostProcessor.postProcessBeforeInitialization, bean=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat@1ecfcbc9, beanName=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat
BeanPostProcessor.postProcessAfterInitialization, bean=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat@1ecfcbc9, beanName=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat
BeanPostProcessor.postProcessBeforeInitialization, bean=org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory@5a6482a9, beanName=tomcatServletWebServerFactory
BeanPostProcessor.postProcessBeforeInitialization, bean=org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration@7f9ab969, beanName=org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration
BeanPostProcessor.postProcessAfterInitialization, bean=org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration@7f9ab969, beanName=org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration$TomcatWebSocketConfiguration
.....
ChildBean构造器初始化
BeanPostProcessor.postProcessBeforeInitialization, bean=com.kengcoder.springframeawsome.bean.ChildBean@3dd4a6fa, beanName=childBean
BeanPostProcessor.postProcessAfterInitialization, bean=com.kengcoder.springframeawsome.bean.ChildBean@3dd4a6fa, beanName=childBean
BeanPostProcessor.postProcessBeforeInitialization, bean=org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages@5cbe2654, beanName=org.springframework.boot.autoconfigure.AutoConfigurationPackages
BeanPostProcessor.postProcessAfterInitialization, bean=org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages@5cbe2654, beanName=org.springframework.boot.autoconfigure.AutoConfigurationPackages
......
从上面的例子可以看到:
● 属性初始化在构造器调用之后,代码如下:
地址信息通过配置参数获取:
java
@Value("${address}")
private String serviceAddress;
打印属性信息代码(实现了BeanNameAware接口):
java
@Override
public void setBeanName(String name) {
System.out.println("BeanNameAware.setBeanName:" + name);
System.out.println("serviceAddress=" + serviceAddress);
}
打印日志结果:
ini
MyBean构造器初始化
BeanNameAware.setBeanName:myBean
serviceAddress=10.8.9.4
可以看到,在BeanNameAware.setBeanName调用时已经获取到属性,推导出属性初始化在构造器调用之后,其他钩子方法之前执行。
● 几个aware的接口方法在bean构造器之后,同时在下列方法之前调用:
less
@PostConstruct
InitializingBean.afterPropertiesSet
@Bean.initMethod
● 通过如下类方法注入的bean实例化在@Bean.initMethod之后被调用。
java
public void setChildBean(@Autowired ChildBean childBean) {
System.out.println("setChildBean");
this.childBean = childBean;
}
● BeanPostProcessor.postProcessBeforeInitialization和BeanPostProcessor.postProcessAfterInitialization会因为多个bean初始化多次被调用,也因此有机会获取这些bean,并用来实现定制逻辑。
2.2 停止spring
停止spring,bean生命周期日志如下:
java
@PreDestroy
DisposableBean.destroy
@Bean.destroyMethod
3. 定制化例子
3.1 静态方法获取bean实例
通常静态方法存在于工具类中,这些类没有成员变量,因此不需要实例化,也就不需要使用@Component、@Service等注解来实例化,当工具类要使用一个三方bean实例时,由于没有使用上述注解,也无法使用诸如@Autowired的注解来注入三方bean,此时就要用到aware接口,下面来看例子。
3.1.1 类结构:
3.1.2 代码
3.1.2.1 ThirdPartyBean
三方bean,模拟一个简单操作,通过spring注入:
java
@Component
public class ThirdPartyBean {
public void exec() {
System.out.println("ThirdPartyBean be called.");
}
}
3.1.2.2 BeanFactoryHolder
实现BeanFactoryAware接口,获取传入的BeanFactory并持有它,让其他类可以获取到BeanFactory:
java
@Component
public class BeanFactoryHolder implements BeanFactoryAware {
private static BeanFactory beanFactory;
public static BeanFactory getBeanFactory() {
return beanFactory;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
// 注意这里的写法,前面必须加上BeanFactoryHolder
BeanFactoryHolder.beanFactory = beanFactory;
}
}
3.1.2.3 ThirdPartyBeanUtil
工具类,获取三方bean并调用:
java
public class ThirdPartyBeanUtil {
public static void exec() {
ThirdPartyBean thirdPartyBean = BeanFactoryHolder.getBeanFactory().getBean(ThirdPartyBean.class);
thirdPartyBean.exec();
System.out.println("ThirdPartyBeanUtil be called.");
}
}
3.1.3 调用日志
成功获取到三方bean,调用成功:
erlang
ThirdPartyBean be called.
ThirdPartyBeanUtil be called.
3.2 属性初始化之后完成bean的初始化
有时候bean的初始化需要参考bean的属性,这些属性通过@Value注解从配置文件中读取,此时按照上面的顺序以及语义来看,比较适合实现InitializingBean接口的afterPropertiesSet方法,在该方法中完成bean初始化。
3.3 改变已有bean的行为
例如已有一个Restful接口bean,用于发送resuful请求,想增加一些动作,例如上报统计信息,那么可以实现BeanPostProcessor接口的postProcessAfterInitialization方法生成一个代理类。
3.3.1 类结构
3.3.2 代码
3.3.2.1 RestfulBean
restful请求类:
java
@Component
public class RestfulBean {
public String post(String url, String jsonData) {
System.out.println("url=" + url + ", jsonData=" + jsonData);
return "success";
}
}
3.3.2.2 RestfulBeanWrapper
代理类:
java
@Component
public class RestfulBeanWrapper extends RestfulBean implements BeanPostProcessor {
private RestfulBean restfulBean;
public String post(String url, String jsonData) {
System.out.println("上报统计信息···");
return restfulBean.post(url, jsonData);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class beanClass = bean.getClass();
if (beanClass == RestfulBean.class) {
restfulBean = (RestfulBean) bean;
return this;
}
return bean;
}
}
3.3.2.3 RestfulBeanController
用于触发调用RestfulBean:
java
@RestController
@RequestMapping(path = "/restfulBeanController", method = {RequestMethod.GET})
public class RestfulBeanController {
@Autowired
private RestfulBean restfulBean;
@RequestMapping("/exec")
public String exec() {
return restfulBean.post("http://localhost:8080/restservice", "helo world!");
}
}
3.3.3 调用日志
java
上报统计信息···
url=http://localhost:8080/restservice, jsonData=helo world!
可以看到代理类增加的操作被执行,RestfulBean的原有方法也正确执行。
4. 总结
● 本文探讨了Bean的生命周期,重点介绍了在生命周期各阶段可利用的钩子方法,这些方法为Bean的定制化提供了灵活性和扩展能力。
● 通过具体示例阐释了如何利用这些钩子实现定制功能,虽然这些示例可能还有更好的替代方法,但它们有效地展示了其中一种实现,在适当的业务场景下,可以根据需要选择使用这些方法。
● "aware"和钩子接口的处理机制,在某种程度上类似于发布/订阅模式。在此模式中,Spring提供一个接口供应用层订阅(即实现该接口)。当满足条件时,Spring会触发通知(调用实现类重载的接口方法),并将事件对象(接口方法参数)传递给订阅者,以供其使用,所谓万变不离其宗,最终都归于某种设计模式或设计原则。
其他阅读:
萌新快速成长之路
如何编写软件设计文档
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃