二十九、IOC之InitializingBean 和 init-method

IOC之InitializingBean 和 init-method

Spring在初始化时进行三个检测扩展:之前说到的AwareBeanPostProcessor,还有本篇InitializingBean接口和init-method

因为DisposableBean接口 和 destroy-method与初始化的时候逻辑一样,故只分析InitializingBean接口和init-method

InitializingBean接口

InitializingBean接口只提供了一个方法afterPropertiesSet(),用来为bean提供定义初始化方法。

1
2
3
4
5
public interface InitializingBean {
// 该方法在 BeanFactory 设置完了所有属性之后被调用
// 该方法允许 bean 实例设置了所有 bean 属性时执行初始化工作,如果该过程出现了错误则需要抛出异常
void afterPropertiesSet() throws Exception;
}

Spring 在完成实例化后,设置完所有属性,进行 Aware接口和 BeanPostProcessor前置处理之后,会接着检测当前 bean 对象是否实现了 InitializingBean 接口。如果是,则会调用其 afterPropertiesSet() 方法,进一步调整 bean 实例对象的状态,最后会执行BeanPostProcessor后置处理。

实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestInitializingBean implements InitializingBean {
private String name;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean init...");
this.name = "jievhahaTest";
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
1
2
3
4
5
6
7
public class MyApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
TestInitializingBean testInitializingBean = (TestInitializingBean) applicationContext.getBean("testInitializingBean");
System.out.println(testInitializingBean.getName());
}
}
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="testInitializingBean" class="com.jievhaha.TestInitializingBean">
<property name="name" value="test"/>
</bean>
</beans>

最后执行结果为:

1
2
3
4
Bean[testInitializingBean]开始初始化
InitializingBean init...
Bean[testInitializingBean]完成初始化
jievhahaTest //假如在afterPropertiesSet中没有设置,最后结果为test
invokeInitMethods

Bean初始化阶段, Spring 容器会主动检查当前 bean 是否已经实现了 InitializingBean 接口,如果实现了,则会掉用其 afterPropertiesSet() 方法。这个主动检查、调用的动作是由 invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd) 方法来完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// AbstractAutowireCapableBeanFactory.java

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
// 首先会检查是否是 InitializingBean ,如果是的话需要调用 afterPropertiesSet()
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) { // 安全模式
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
// 属性初始化的处理
((InitializingBean) bean).afterPropertiesSet();
return null;
}, getAccessControlContext());
} catch (PrivilegedActionException pae) {
throw pae.getException();
}
} else {
// 属性初始化的处理
((InitializingBean) bean).afterPropertiesSet();
}
}

if (mbd != null && bean.getClass() != NullBean.class) {
// 判断是否指定了 init-method(),
// 如果指定了 init-method(),则再调用制定的init-method
String initMethodName = mbd.getInitMethodName();
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
// 激活用户自定义的初始化方法
// 利用反射机制执行
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
  • 首先,检测当前 bean 是否实现了 InitializingBean 接口,如果实现了则调用其 afterPropertiesSet() 方法。
  • 然后,再检查是否也指定了 init-method,如果指定了则通过反射机制调用指定的 init-method 方法。

init-method属性

直接实现InitializingBean接口破坏了Spring的核心理念:无侵入性,故有另一种实现方式:init-method

即spring配置文件注册bean时,给<bean>标签添加init-method属性,也可以直接使用<beans>标签的default-init-method属性来统一指定初始化方法,不用每个<bean>标签指定init-method属性。

总结

invokeInitMethods(...) 方法中,我们知道 init-method 指定的方法会在 afterPropertiesSet() 方法之后执行,如果 afterPropertiesSet() 方法的执行的过程中出现了异常,则 init-method 是不会执行的,而且由于 init-method 采用的是反射执行的方式,所以 afterPropertiesSet() 方法的执行效率一般会高些,但是并不能排除我们要优先使用 init-method,主要是因为它消除了 bean 对 Spring 的依赖,Spring 没有侵入到我们业务代码,这样会更加符合 Spring 的理念。诚然,init-method 是基于 xml 配置文件的,就目前而言,我们的工程几乎都摒弃了配置,而采用注释的方式,那么 @PreDestory 可能比较适合。


二十九、IOC之InitializingBean 和 init-method
http://www.muzili.ren/2022/06/11/IOC之InitializingBean 和 init-method/
作者
jievhaha
发布于
2022年6月11日
许可协议