likes
comments
collection
share

spring 源码解析之initializeBean初始化 bean

作者站长头像
站长
· 阅读数 1

前言

initializeBean概述

在Spring框架中,initializeBean的主要作用是初始化bean。具体来说,它执行以下步骤:

  • 如果bean实现了InitializingBean接口,那么initializeBean会调用bean的afterPropertiesSet方法。这个方法在bean的属性初始化后会被执行。
  • 如果在配置文件中通过init-method或注解指定了初始化方法,那么initializeBean会调用这个方法。

先看源码

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

总之,这个函数实现了Spring IoC容器中bean生命周期管理的关键步骤,确保了bean在实例化后能正确地执行初始化逻辑,并通过BeanPostProcessor机制进行灵活扩展和定制。

举个例子

上面说的两种初始化bean的方式可以同时使用。如果同时使用,系统会先调用afterPropertiesSet方法,然后再调用init-method中指定的方法

这是一个示例,展示了如何使用InitializingBean接口和init-method

 @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接口以及是否定义了自定义的初始化方法,并调用必要的回调方法。

 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配置、注解或其他元数据)显式指定初始化方法,如上面的例子
      •  @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

这个方法逻辑也很简单,就是调用自己自定义的初始化方法

 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();
    }
 }

我当时在学习就有一个疑问,就是为什么这里要获取两次初始化方法?

 // 获取初始化方法
    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 源码的时候,就经常会有很多后置处理器的知识在其中,接下来就要把他们拿下了。大环境不好,只能开卷了,加油!