前言
通过上一篇spring 源码解析之populateBean属性注入学习了 spring 在完成 bean 实例创建的时候,进行了属性的注入,这时候 bean 基本已经完成了所有的工作了,剩下的就是初始化的操作了
initializeBean概述
在Spring框架中,
initializeBean
的主要作用是初始化bean。具体来说,它执行以下步骤:
- 如果bean实现了
InitializingBean
接口,那么initializeBean
会调用bean的afterPropertiesSet
方法。这个方法在bean的属性初始化后会被执行。
- 如果在配置文件中通过init-method或注解指定了初始化方法,那么initializeBean会调用这个方法。
先看源码
java
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
//用于执行BeanNameAware、BeanClassLoaderAware和BeanFactoryAware等Aware回调方法
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
// 如果mbd不为空并且不是合成bean,则执行BeanPostProcessor的beforeInitialization方法
// 这里明明是mbd == null,为什么要解释成不为空呢,下面会有解释
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
// 执行bean的初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);
}
// 如果bean定义不存在或者bean不是合成的,则调用applyBeanPostProcessorsAfterInitialization方法,对bean进行初始化后的后处理器。
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
该initializeBean
方法在Spring框架中起到核心的初始化bean实例的作用,其主要功能包括:
-
执行Aware回调 :关于 Aware 详细解释请看spring 源码解析番外篇之Aware
- 函数首先调用
invokeAwareMethods(beanName, bean)
来处理BeanNameAware
、BeanClassLoaderAware
和BeanFactoryAware
等实现这些接口的bean。这些Aware接口允许bean获取到相关的环境信息(如自身在工厂中的名字、类加载器或BeanFactory对象)。
- 函数首先调用
-
应用前置初始化BeanPostProcessor:
-
如果传入的
mbd
参数(即RootBeanDefinition对象)为空或者不是合成bean,则会调用applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)
方法。此时明明是是mbd == null 这个条件,为什么我在代码注释会解释成不为空呢?
- 这里的逻辑并不是基于
mbd是否为空
来决定是否执行后置处理器,而是基于mbd是否为合成bean
(isSynthetic()返回true)。 当mbd为null时
,表示可能是在处理一个不是通过Spring容器定义的现有bean实例,这种情况下仍然需要执行后置处理器以保持对所有bean的一致性处理。- 由于
合成bean
是Spring内部创建并用于特殊目的的bean,它们通常不需要应用常规的初始化后处理器逻辑
,所以只有当mbd不为合成bean时(包括mbd为null的情况),才会调用前后置处理器的方法。
-
这个方法会按照定义顺序遍历并应用所有前置初始化的BeanPostProcessor,这些处理器可以在bean初始化前对bean进行额外的加工或增强。
-
-
执行初始化方法:
- 调用
invokeInitMethods(beanName, wrappedBean, mbd)
执行bean定义中指定的初始化方法,通常是@PostConstruct
注解标注的方法。 - 如果在这个过程中抛出了异常,函数会捕获并包装成
BeanCreationException
抛出,其中包含bean资源描述、bean名称以及异常消息。
- 调用
-
应用后置初始化BeanPostProcessor:
- 同样根据
mbd
是否为空以及bean是否为合成bean,调用applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)
方法。 - 这个方法会在bean初始化完成后应用所有的后置初始化BeanPostProcessor,它们可以在此阶段进一步修改或增强bean。
- 同样根据
-
返回处理后的bean实例:
- 最终,函数返回经过初始化及BeanPostProcessor处理后的bean实例,可能是一个被包装过的bean。
总之,这个函数实现了Spring IoC容器中bean生命周期管理的关键步骤,确保了bean在实例化后能正确地执行初始化逻辑,并通过BeanPostProcessor机制进行灵活扩展和定制。
举个例子
上面说的两种初始化bean的方式可以同时使用。如果同时使用,系统会先调用afterPropertiesSet
方法,然后再调用init-method
中指定的方法
这是一个示例,展示了如何使用InitializingBean
接口和init-method
:
java
@Component
public class MyInitializingBean implements InitializingBean {
public MyInitializingBean() {
System.out.println("我是MyInitializingBean构造方法执行...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("我是afterPropertiesSet方法执行...");
}
@PostConstruct
public void postConstruct() {
System.out.println("我是postConstruct方法执行...");
}
public void init(){
System.out.println("我是init方法执行...");
}
@Bean(initMethod = "init")
public MyInitializingBean test() {
return new MyInitializingBean();
}
}
在这个示例中,你可以看到执行顺序优先级:构造方法 > postConstruct
> afterPropertiesSet
> init
方法。这就是initializeBean
的作用和使用方法。
invokeInitMethods
该函数用于初始化一个Bean对象,包括检查Bean是否实现了InitializingBean接口以及是否定义了自定义的初始化方法,并调用必要的回调方法。
java
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd)
throws Throwable {
// 是否实现了InitializingBean接口
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.hasAnyExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isTraceEnabled()) {
logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
// 调用InitializingBean的afterPropertiesSet方法
((InitializingBean) bean).afterPropertiesSet();
}
if (mbd != null && bean.getClass() != NullBean.class) {
// 获取bean的初始化方法
String[] initMethodNames = mbd.getInitMethodNames();
if (initMethodNames != null) {
for (String initMethodName : initMethodNames) {
// 该类没有实现InitializingBean接口,或者改方法名不等于afterPropertiesSet
// 且该方法没有被初始化方法管理
if (StringUtils.hasLength(initMethodName) &&
!(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.hasAnyExternallyManagedInitMethod(initMethodName)) {
invokeCustomInitMethod(beanName, bean, mbd, initMethodName);
}
}
}
}
}
-
检查和调用InitializingBean接口
:-
首先,函数会检查传入的Bean实例是否实现了Spring的
InitializingBean
接口。如果实现该接口,且当前合并的Bean定义(RootBeanDefinition)未明确标记有外部管理的初始化方法
(如"afterPropertiesSet"),则会调用该接口的afterPropertiesSet()方法。这个方法通常在所有属性设置完成后执行特定的初始化逻辑。 -
hasAnyExternallyManagedInitMethod
这个方法的作用是什么呢?- 是否存在由外部(如XML配置、注解或其他元数据)显式指定初始化方法,如上面的例子
typescript@Bean(initMethod = "init") public MyInitializingBean test() { return new MyInitializingBean(); }
- 这个就是通过外部指定初始化方法是 init 这个方法
-
-
处理自定义初始化方法:
- 如果传入的
mbd(RootBeanDefinition)
不为空,并且 Bean 类型不是NullBean.class
,函数会进一步查找并处理自定义的初始化方法。 - 获取Bean定义中的初始化方法名称数组(
initMethodNames
),遍历这些方法名。 - 对于每个找到的初始化方法,首先判断方法名是否有效、Bean 是否没有实现 InitializingBean 接口或者方法名与 afterPropertiesSet 不同,同时该方法也未被外部管理。满足这些条件后,函数会通过
invokeCustomInitMethod
方法调用该自定义初始化方法。
- 如果传入的
总之,此函数旨在确保Bean在所有属性设置完毕后得到正确的初始化,无论是通过实现标准的InitializingBean接口,还是通过自定义的初始化方法。
InitializingBean
-
InitializingBean
是Spring框架中提供的一种接口,主要用于定制bean的初始化逻辑。 -
当一个类实现了
InitializingBean
接口后,Spring容器在完成对bean所有属性(依赖注入)设置之后
,会自动调用
其实现的afterPropertiesSet()
方法。 -
作用:
- 自定义初始化过程:开发人员可以在afterPropertiesSet()方法中编写需要在bean实例化并完成依赖注入后执行的任何初始化操作。
- 简化配置:通过实现该接口,可以避免在XML配置文件或注解配置中显式地定义初始化方法,简化了配置工作。
-
为什么要使用这个接口:
- 生命周期管理:Spring IoC容器管理bean的整个生命周期,从创建、初始化到销毁。通过InitializingBean,开发者能够参与到bean生命周期中的初始化阶段,从而确保bean在被其他组件使用之前已经处于预期的状态。
- 依赖保证:由于afterPropertiesSet()是在所有属性设置完毕后调用的,因此开发者可以确保在这个时候所有的依赖对象都已经成功注入,并可以安全地进行基于这些依赖对象的进一步初始化处理。
然而,尽管InitializingBean接口提供了便利,但直接依赖于Spring API会让代码与Spring框架耦合度提高。因此,在实际应用中,更推荐使用Java配置、注解配置或者
@PostConstruct
注解等更加面向Java编程模型的方式来实现初始化逻辑,以保持更好的可测试性和可移植性。
invokeCustomInitMethod
这个方法逻辑也很简单,就是调用自己自定义的初始化方法
java
protected void invokeCustomInitMethod(String beanName, Object bean, RootBeanDefinition mbd, String initMethodName)
throws Throwable {
// 获取bean的类
Class<?> beanClass = bean.getClass();
// 获取方法描述器
MethodDescriptor descriptor = MethodDescriptor.create(beanName, beanClass, initMethodName);
// 获取方法名
String methodName = descriptor.methodName();
// 获取初始化方法
Method initMethod = (mbd.isNonPublicAccessAllowed() ?
BeanUtils.findMethod(descriptor.declaringClass(), methodName) :
ClassUtils.getMethodIfAvailable(beanClass, methodName));
// 初始化方法为空 抛异常或者跳过
if (initMethod == null) {
if (mbd.isEnforceInitMethod()) {
throw new BeanDefinitionValidationException("Could not find an init method named '" +
methodName + "' on bean with name '" + beanName + "'");
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No default init method named '" + methodName +
"' found on bean with name '" + beanName + "'");
}
// Ignore non-existent default lifecycle methods.
return;
}
}
if (logger.isTraceEnabled()) {
logger.trace("Invoking init method '" + methodName + "' on bean with name '" + beanName + "'");
}
// 获取实现了InitializingBean接口的初始化方法
Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, beanClass);
try {
// 确保可访问
ReflectionUtils.makeAccessible(methodToInvoke);
// 执行初始化方法
methodToInvoke.invoke(bean);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
我当时在学习就有一个疑问,就是为什么这里要获取两次初始化方法?
ini
// 获取初始化方法
Method initMethod = (mbd.isNonPublicAccessAllowed() ?
BeanUtils.findMethod(descriptor.declaringClass(), methodName) :
ClassUtils.getMethodIfAvailable(beanClass, methodName));
Method methodToInvoke = ClassUtils.getInterfaceMethodIfPossible(initMethod, beanClass);
- 如果
mbd.isNonPublicAccessAllowed()
为真,表示允许访问非公共(private、protected
)的初始化方法。这时调用BeanUtils.findMethod(descriptor.declaringClass(), methodName)
来查找指定类上声明的任意访问权限的初始化方法。 - 若
不允许访问非公共方法
,则通过ClassUtils.getMethodIfAvailable(beanClass, methodName)
查找public可见的初始化方法。 - 以上是为了找到符合配置要求且Bean上
实际存在的初始化方法
。 ClassUtils.getInterfaceMethodIfPossible(initMethod, beanClass)
这个方法是在已经获取到的initMethod
基础上,检查并尝试返回
该方法在给定bean类实现的接口中的版本(如果存在)。- 当Bean实现了某个接口,并且该接口中定义了与初始化方法同名的方法时,可能会优先选择调用接口上的方法来执行初始化逻辑。
总结来说,先获取initMethod是为了找到并确定初始化方法的实际定义,而第二次获取methodToInvoke则是为了确保在有接口方法重写的情况下,正确地使用接口版本的方法进行调用,遵循Java多态的原则
总结
至此,万事大吉,springbean 源码的所有知识点都已经整理完了,这一路走来真的不容易啊。这一篇几乎是我看 spring 源码以来最轻松的一篇了。既然 bean 的学完了,接下来继续 AOP 跟处理器的内容,在我们看 spring 源码的时候,就经常会有很多后置处理器的知识在其中,接下来就要把他们拿下了。大环境不好,只能开卷了,加油!