spring 源码解析之initializeBean初始化 bean

前言

通过上一篇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实例的作用,其主要功能包括:

  1. 执行Aware回调 :关于 Aware 详细解释请看spring 源码解析番外篇之Aware

    • 函数首先调用invokeAwareMethods(beanName, bean)来处理BeanNameAwareBeanClassLoaderAwareBeanFactoryAware等实现这些接口的bean。这些Aware接口允许bean获取到相关的环境信息(如自身在工厂中的名字、类加载器或BeanFactory对象)。
  2. 应用前置初始化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进行额外的加工或增强。

  3. 执行初始化方法

    • 调用invokeInitMethods(beanName, wrappedBean, mbd)执行bean定义中指定的初始化方法,通常是@PostConstruct注解标注的方法。
    • 如果在这个过程中抛出了异常,函数会捕获并包装成BeanCreationException抛出,其中包含bean资源描述、bean名称以及异常消息。
  4. 应用后置初始化BeanPostProcessor

    • 同样根据mbd是否为空以及bean是否为合成bean,调用applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)方法。
    • 这个方法会在bean初始化完成后应用所有的后置初始化BeanPostProcessor,它们可以在此阶段进一步修改或增强bean。
  5. 返回处理后的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);
             }
          }
       }
    }
 }
  1. 检查和调用InitializingBean接口

    • 首先,函数会检查传入的Bean实例是否实现了Spring的InitializingBean接口。如果实现该接口,且当前合并的Bean定义(RootBeanDefinition)未明确标记有外部管理的初始化方法(如"afterPropertiesSet"),则会调用该接口的afterPropertiesSet()方法。这个方法通常在所有属性设置完成后执行特定的初始化逻辑。

    • hasAnyExternallyManagedInitMethod这个方法的作用是什么呢?

      • 是否存在由外部(如XML配置、注解或其他元数据)显式指定初始化方法,如上面的例子
      typescript 复制代码
       @Bean(initMethod = "init")
           public MyInitializingBean test() {
               return new MyInitializingBean();
           }
      • 这个就是通过外部指定初始化方法是 init 这个方法
  2. 处理自定义初始化方法:

    • 如果传入的mbd(RootBeanDefinition)不为空,并且 Bean 类型不是 NullBean.class,函数会进一步查找并处理自定义的初始化方法。
    • 获取Bean定义中的初始化方法名称数组(initMethodNames),遍历这些方法名。
    • 对于每个找到的初始化方法,首先判断方法名是否有效、Bean 是否没有实现 InitializingBean 接口或者方法名与 afterPropertiesSet 不同,同时该方法也未被外部管理。满足这些条件后,函数会通过 invokeCustomInitMethod 方法调用该自定义初始化方法。

总之,此函数旨在确保Bean在所有属性设置完毕后得到正确的初始化,无论是通过实现标准的InitializingBean接口,还是通过自定义的初始化方法。

InitializingBean

  • InitializingBean是Spring框架中提供的一种接口,主要用于定制bean的初始化逻辑。

  • 当一个类实现了InitializingBean接口后,Spring容器在完成对bean所有属性(依赖注入)设置之后,会自动调用其实现的afterPropertiesSet()方法。

  • 作用

    • 自定义初始化过程:开发人员可以在afterPropertiesSet()方法中编写需要在bean实例化并完成依赖注入后执行的任何初始化操作。
    • 简化配置:通过实现该接口,可以避免在XML配置文件或注解配置中显式地定义初始化方法,简化了配置工作。
  • 为什么要使用这个接口

    1. 生命周期管理:Spring IoC容器管理bean的整个生命周期,从创建、初始化到销毁。通过InitializingBean,开发者能够参与到bean生命周期中的初始化阶段,从而确保bean在被其他组件使用之前已经处于预期的状态。
    2. 依赖保证:由于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 源码的时候,就经常会有很多后置处理器的知识在其中,接下来就要把他们拿下了。大环境不好,只能开卷了,加油!

相关推荐
进击的女IT3 分钟前
SpringBoot上传图片实现本地存储以及实现直接上传阿里云OSS
java·spring boot·后端
Miqiuha10 分钟前
lock_guard和unique_lock学习总结
java·数据库·学习
杨半仙儿还未成仙儿1 小时前
Spring框架:Spring Core、Spring AOP、Spring MVC、Spring Boot、Spring Cloud等组件的基本原理及使用
spring boot·spring·mvc
一 乐1 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
数云界2 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
阑梦清川2 小时前
Java继承、final/protected说明、super/this辨析
java·开发语言
快乐就好ya3 小时前
Java多线程
java·开发语言
IT学长编程3 小时前
计算机毕业设计 二手图书交易系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·二手图书交易系统
CS_GaoMing4 小时前
Centos7 JDK 多版本管理与 Maven 构建问题和注意!
java·开发语言·maven·centos7·java多版本
艾伦~耶格尔4 小时前
Spring Boot 三层架构开发模式入门
java·spring boot·后端·架构·三层架构