likes
comments
collection
share

聊透spring @Configuration配置类

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

 本章节我们来探索Spring中一个常用的注解@Configuration。我们先来了解一下该注解的作用是:用来定义当前类为配置类

 那啥是配置类啊,有啥用啊。这个我们得结合实际使用场景来说,通常情况下。加了@Configuration的配置类内部,都会包含一个或多个@Bean注解的方法

为了简化定义,在后续我们称@Bean注解的方法为工厂方法。

 配置类的奥秘就在这里,Spring会保证多次调用@Bean标注的工厂方法,不会重复产生新的对象,始终是同一个,这也贯彻了Spring的单例哲学。

 多次调用创建方法,产生的竟然是同一个对象,这貌似违背了编程的基础原理。怎么可能😱,一定是Spring做了什么,那就跟随着贰师兄的脚步一起解开@Configuration的神秘面纱吧。

这里大家很容易产生一个误解,认为只有在加了@Configuration的配置类中,使用@Bean,才能将自定义创建的对象放入Spring容器中。其实不然,在Spring中:万物皆为配置类在任何能够被Spring管理的类(比如加了@Component的类)中,定义@Bean方法,都能将自定义对象放入到Spring容器,@Bean本身的能力和@Configuration无关哦

1. @Configuration的作用

 我们先通过一个简单的案例,看一下@Configuration的作用。

需要特别说明的是:@Configuration继承了@Component,这意味着@Configuration拥有@Component的全部功能,这也正是只加@Configuration,也能被Spring扫描并处理的原因。

  • 情况一:被@Component标识
@Component
public class MarkedByComponent {
    @Bean
    public A a(){
        return new A();
    }

    @Bean
    public B b(){
        a();
        return new B();
    }
}

// 输出信息:
// A created...
// A created...
// B created...
  • 情况二:被@Configuration标识
@Configuration
public class MarkedByConfiguration {
    @Bean
    public A a(){
        return new A();
    }

    @Bean
    public B b(){
        a();
        return new B();
    }
}

// 输出信息:
// A created...
// B created...
  • 结论  通过上述输出我们发现:在多次调用a()的情况下,被@Component标识的类,A会被创建多次。而被@Configuration标识的配置类,A只会被创建一次。此时我们可以大胆猜测:在@Configuration标识的配置类中,重复调用@Bean标识的工厂方法,Spring会对创建的对象进行缓存。仅在缓存中不存在时,才会通过工厂方法创建对象。后续重复调用工厂方法创建对象,先去缓存中找,不直接创建对象

这里小伙伴可能有个疑惑,a()只在b()被调用一次,为什么说多次呢,其实除了在b()中声明式调用,由于a()被@Bean标识,Spring也会主动调用一次的哦。

2. @Configuration的原理分析

 上一章节,我们通过一个简单的案例,了解@Configuration的作用:使@Bean标识的工厂方法,不会重复创建bean对象。同时也大胆猜测了,Spring对@Configuration标识的配置类,做了缓存处理,从而让@Bean工厂方法,有了"幂等的能力"。本章节我们一起探索一下,这个增强的缓存逻辑,是怎么做到的?

  要对一个类的功能进行增强。代理 这个词是不是已经在脑海中呼之欲出了,是的,就是代理。Spring对@Configuration标识的类做了代理,从而进行功能的增强。

 当然,现在我们离直接分析代理功能还有点遥远,毕竟步子不能迈的太大,不然容易扯着蛋。一步步来,首先我们先看看Spring是如何解析@Configuration注解的,毕竟需要先找出来,才能代理增强嘛。

2.1 @Configuration解析

  对@Configuration的解析过程是在spring扫描类的时候进行的。这里需要介绍一下,Spring把加了@Configuration注解的称为全配置类,其他的称为半配置类,两者判别标准是:是否加了@Configuration注解。

// ConfigurationClassParser.java
@Nullable
protected final SourceClass doProcessConfigurationClass(
      ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException {
    //... 省略部分非相关代码

    // 解析是否加了@ComponentScan, 并进行扫描
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
          sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
          !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
       for (AnnotationAttributes componentScan : componentScans) {

          // 1: 解析ComponentScan配置信息,完成扫描(扫描出来的类在beanDefinitionMap中)
          Set<BeanDefinitionHolder> scannedBeanDefinitions =
                this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

          // 2: 遍历扫描出来的类,检查是不是配置类(配置类需要继续递归解析)
          for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
             BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
             if (bdCand == null) {
                bdCand = holder.getBeanDefinition();
             }
             // 重点:判断类是不是配置类, 并标注类属性信息(是否为全配置类、@Order值等)
             if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                // 3 如果扫描出来的类,是配置类,还需要递归处理扫描出来的配置类
                parse(bdCand.getBeanClassName(), holder.getBeanName());
             }
          }
       }
    }
    //... 省略部分非相关代码

}

//ConfigurationClassUtils.java
// 全配置类标记值
public static final String CONFIGURATION_CLASS_FULL = "full";
// 半配置类标记值
public static final String CONFIGURATION_CLASS_LITE = "lite";

public static boolean checkConfigurationClassCandidate(
      BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
     //... 省略部分非相关代码
      
    // 2.1 从注解信息中, 获取@Configuration的注解信息(如存在,标记为为全配置类)
    Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
    if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
       beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
    }
    
    // 有@Component,@ComponentScan,@Import,@ImportResource注解,被标记成半配置类
    else if (config != null || isConfigurationCandidate(metadata)) {
       beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    }
    else {
       return false;
    }

    //... 省略部分非相关代码
}

  我们梳理一下这部分代码的逻辑:

  1. 获取配置类上@ComponentScan(basePackages="xxx")注解,交给组件componentScanParser解析,该组件会扫描出对应包路径下需要Spring处理的类,并封装成BeanDefinition,同时返回扫描的类定义信息。
  2. 遍历扫描出的类,检查是否为配置类,配置类需要继续解析(配置类可能有需要处理的Spring功能)。
  3. 在判断是否为配置类时,也会给类打上标签,加了@Configuration的标记为全配置类,其他的标记为半配置类。标识的方式就是在BeanDefinitionattributes属性中加入XX.configurationClass:full标识。

判断是否为配置类时,不仅仅只是加了@Configuration的为配置类,加了@Component@ComponentScan@Import@ImportResource等注解的,也是配置类,只是为半配置类而已。

上述我们说@Configuration的作用是标记为配置类,这里看其实是不准确的,准确说应该是标记为全配置类。但是这是更内部的逻辑,通常来说,@Configuration标记的就是配置类,其他标记的为普通类。

  总结一下,在扫描阶段,Spring会对扫描出来的类进行全配置类还是半配置类的标识。当然这里也仅仅是标识出来,并没有使用,这是在给后面生成代理对象做准备。

聊透spring @Configuration配置类

这里我们不得不吐槽一下,作为业界标杆的Spring,在方法命名上也如此混乱,在checkConfigurationClassCandidate()这个表明check动作的方法里,做了很多BeanDefinition属性解析赋值的操作,简直是在混淆视听啊有没有。看来命名确实是编程界最大的难题啊。

2.2 全配置类增强

  在上一章节,我们分析了Spring对标注了@Configuration配置类的查找和解析过程,总结起来就是:判断类上是否有@Configuration注解,有就是全配置类,没有就是半配置类。对于全配置的增强,我们也反复提到过,是借助代理来实现的。具体的的调用逻辑我们一起来看一下。

//ConfigurationClassPostProcessor.java
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   //...省略部分代码
   // 对全配置类进行增强
   this.enhanceConfigurationClasses(beanFactory);
}


public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
   Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
   // 1: 查找需要增强的配置类
   // 遍历已经注册多的beanName
   for (String beanName : beanFactory.getBeanDefinitionNames()) {
      // ...省略部分代码
      // 1.2 查找全配置类,放入configBeanDefs
      if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
         // ...省略部分代码
         // 将符合条件的,放入configBeanDefs中
         configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
      }
   }

   // 2: 对全配置类进行增强
   // 2.1 创建一个配置类的增强器
   ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
   for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
      AbstractBeanDefinition beanDef = entry.getValue();
      beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
      Class<?> configClass = beanDef.getBeanClass();
      // 2.2 对类进行增强,使用cglib单例
      Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
      if (configClass != enhancedClass) {
         if (this.logger.isTraceEnabled()) {
            this.logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
                  "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
         }
         // 2.3 修改BeanClass为加强类,这里之所以不使用加强对象,是因为还需要将加强类交给spring实例化
         beanDef.setBeanClass(enhancedClass);
      }
   }
}

  我们梳理一下这部分代码的逻辑:

  1. 查找需要增强的配置类,这里直接找出类BeanDefinitionattributes属性中XX.configurationClass标识值为full的即可。
  2. 对配置类生成代理,进行功能增强。
  3. 修改BeanDefinition的beanClass属性为代理类,后续Spring在产生实例时,使用的就是代理类了。

聊透spring @Configuration配置类  这里有个重点,就是生成代理类,Spring采用的是CGLIB增强的方式,下面我们顺藤摸瓜,看看Spring是怎么操作的。

2.3 生成代理类

// ConfigurationClassEnhancer.java
// 回调器列表
private static final Callback[] CALLBACKS = new Callback[] {
      new BeanMethodInterceptor(),
      new BeanFactoryAwareMethodInterceptor(),
      NoOp.INSTANCE
};

// 回调过滤器
private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);


//为原始类生成代理类
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
   // 1: newEnhancer() 生成生成CGLIB实例
   // createClass() 生成代理类
   Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));

   // 2: 返回增强后的类
   return enhancedClass;
}

// 生成CGLIB实例
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
   Enhancer enhancer = new Enhancer();
   enhancer.setSuperclass(configSuperClass);
   // 设置接口为EnhancedConfiguration
   enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
   enhancer.setUseFactory(false);
   enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
   // 给代理类生成一个$$beanFactory字段
   enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
   // 设置回调过滤器为EnhancedConfiguration,能根据传入
   enhancer.setCallbackFilter(CALLBACK_FILTER);
   enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
   return enhancer;
}

// 生成代理类
private Class<?> createClass(Enhancer enhancer) {
   Class<?> subclass = enhancer.createClass();
   // 增加拦截器
   Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
   return subclass;
}

  我们直接来看代码,这里其实大量运用了CGLIB相关的知识,感兴趣的小伙伴可以自行百度补课,不补也没关系,这里会带大家简单了解一下:

  1. 先是创建了一个增强器Enhancer,为增强器设置了相关属性,我们看一下核心属性:
    • superclass: 父类,也就是被代理类,CGLIB的原理是为被代理类生成子类,从而实现功能增强。
    • interfaces: 实现的接口,这里硬性指定为EnhancedConfiguration。(后续会使用该标识)
    • namingPolicy:设置代理类的名称策略,默认为BySpringCGLIB,这也是Spring生成的代理类类型包含xxBySpringCGLIB的原因。
    • strategy:生成策略,这是设置的BeanFactoryAwareGeneratorStrategy,默认逻辑是给代理类动态增加$$beanFactory字段(后续会使用)。
    • callbackFilter:回调过滤器,在CGLib回调时可以设置不同方法执行不同的回调逻辑,或者根本不执行回调。回调过滤器的功能就是对执行方法选择合适的拦截器。这里一定要区分清楚拦截器和回调过滤器的功能:
      • 拦截器Callback:在调用目标方法时,CGLib会调用拦截器进行调用拦截,来实现增强的代理逻辑,当然里面可以对应原始方法(在父类中)。
      • 回调过滤器CallbackFilter:为执行方法选择拦截器,CGLIB可以设置多个拦截器,然后根据具体执行的方法再进行选择分发。
  2. 借助增强器Enhancer,生成代理类,并注册拦截器。
  3. 返回代理类。

聊透spring @Configuration配置类

2.3.1 拦截器的选择

  在上述过程,我们已经知道Spring为标注了@Configuration配置类生成了代理类,并指定了回调过滤器拦截器,同时将生成对象的类型修改为了代理类。那Spring在实例化的时候,实例化的就是代理对象了。在配置类中工厂方法(@Bean标注的方法)调用的时候,会先通过回调过滤器选择合适的拦截器,然后再由拦截器进行拦截、并实现功能增强

//ConfigurationClassEnhancer.ConditionalCallbackFilter.java

// 根据方法信息,选择拦截器
@Override
public int accept(Method method) {
  for (int i = 0; i < this.callbacks.length; i++) {
     Callback callback = this.callbacks[i];
     // 重点:拦截器的选择, BeanMethodInterceptor和BeanFactoryAwareMethodInterceptor继承了ConditionalCallback,NoOp.INSTANCE 没有继承
     if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method)) {
        return i;
     }
  }
  throw new IllegalStateException("No callback available for method " + method.getName());
}


//ConfigurationClassEnhancer.BeanFactoryAwareMethodInterceptor.java
// 匹配逻辑: 只处理setBeanFactory方法
@Override
public boolean isMatch(Method candidateMethod) {
   return isSetBeanFactory(candidateMethod);
}

public static boolean isSetBeanFactory(Method candidateMethod) {
   return (candidateMethod.getName().equals("setBeanFactory") &&
         candidateMethod.getParameterCount() == 1 &&
         BeanFactory.class == candidateMethod.getParameterTypes()[0] &&
         BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
}

//ConfigurationClassEnhancer.`BeanMethodInterceptor.java
// 匹配逻辑: 只处理方法上加了@Bean注解的,并且不是setBeanFactory()
@Override
public boolean isMatch(Method candidateMethod) {
   return (candidateMethod.getDeclaringClass() != Object.class &&
         !BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod) &&
         BeanAnnotationHelper.isBeanAnnotated(candidateMethod));
}

  通过上述源码,我们可以快速找到回调过滤器选择拦截器的逻辑: if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method)),我们分析一下这个判断的条件,翻译过来就是:

  • 如果是ConditionalCallback类型的拦截器,直接通过回调器isMatch()来判断。
  • 如果不是ConditionalCallback类型的拦截器,则直接判断为匹配成功。

  初看可能有点懵,我们结合传递进来的拦截器来看,拦截器列表是:BeanMethodInterceptorBeanFactoryAwareMethodInterceptorNoOp.INSTANCE,其中前两个都是ConditionalCallback类型的,最后一个则是默认的空实现。同时传递的回调过滤器列表,是个有序数组,所以会优先匹配BeanMethodInterceptorBeanFactoryAwareMethodInterceptor,如果这两个都不能匹配,则默认匹配到空实现NoOp.INSTANCE

聊透spring @Configuration配置类

关于BeanMethodInterceptorBeanFactoryAwareMethodInterceptor的匹配逻辑,在代码注释中已经有明确的说明,比较清晰,大家注意看哦。

2.3.2 拦截器的功能

  经过上述的不厌其烦的啰嗦,我们已经知道,Spring为标注了@Configuration配置类生成了代理类,在调用配置类的方法时,会先通过回调过滤器选择拦截器,然后由拦截器对方法进行增强。同时也清楚主要是靠BeanMethodInterceptorBeanFactoryAwareMethodInterceptor这两个拦截器器发挥作用,本章节我们一起看一下这两个拦截器的功能。

  在分析这两个拦截器的功能之前,我们先来回忆一下@Configuration配置类的作用,让加了@Bean的方法有了"幂等的能力",不会重复创建对象。

这里小伙伴需要注意哦,@Bean注解本身的能力就是把我们自己产生的对象,放入到Spring容器中,便于我们依赖注入,这个@Configuration无关,在半配置类下该功能也是正常的哦。@Configuration的加持,只是使其有了幂等性哦。

  现在我们已经知道@Configuration的作用,实现原理我们也清楚,通过CGLIB生成代理子类,具体的实现我们猜想是:把@Bean工厂方法生成的对象先放入到Spring容器中缓存,重复调用的时候,从缓存中直接获取。实际上也确实如此,Spring就是这么做的。

 OK,现在一切都真相大白,只剩下最后一层神秘的面纱没有解开,就是Spring怎么做到的。要想达到这样的效果,其实有两个问题需要先解决:

  1. 对@Bean的工厂方法产生的对象,能够去Spring容器中查重,不存在则生成对象,存则在直接获取的。
  2. 注入Spring容器,也就是beanFactory,因为需要到容器中查重、获取,所以需要先容器对象。

 Spring正是用BeanMethodInterceptorBeanFactoryAwareMethodInterceptor这两个拦截器,来解决这两个问题的。我们先来看注入Spring容器beanFactory的实现。

2.3.2.1 给代理类注入bean容器

 为了实现@Bean方法的"幂等",我们需要依赖beanFactory,可是没有啊,怎么办呢,要求程序员使用@Autowired BeanFactory 手动放一个?手动是不可能,这辈子都是不可能的,主要是丢不起这个人。手动不行,那就自动放进来吧,这也就是为什么Spring在生产的代理类中,强行加了一个beanFactory属性的原因。

  • 为代理类增加beanFactory属性
//ConfigurationClassEnhancer.java
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
   Enhancer enhancer = new Enhancer();
   // ...省略其他字段的赋值
   // 给代理类生成一个$$beanFactory字段
   enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
   return enhancer;
}


private static class BeanFactoryAwareGeneratorStrategy extends
      ClassLoaderAwareGeneratorStrategy {

   public BeanFactoryAwareGeneratorStrategy(@Nullable ClassLoader classLoader) {
      super(classLoader);
   }

   @Override
   protected ClassGenerator transform(ClassGenerator cg) throws Exception {
      // 动态添加字段"$$beanFactory"
      ClassEmitterTransformer transformer = new ClassEmitterTransformer() {
         @Override
         public void end_class() {
            declare_field(Constants.ACC_PUBLIC, BEAN_FACTORY_FIELD, Type.getType(BeanFactory.class), null);
            super.end_class();
         }
      };
      return new TransformingClassGenerator(cg, transformer);
   }
}

 通过源码,我们可以看到,在生成子类字节码的时候,就借助CGLIB的能力,给代理类动态增加了一个属性$$beanFactory,其类型正是BeanFactory类型,这就是自动放入的。 聊透spring @Configuration配置类

  • 使用拦截器BeanFactoryAwareMethodInterceptor给beanFactory属性赋值  上述过程只是增加了属性$$beanFactory,还没有赋值呢。其实在属性填充的时候,会进行赋值。我们一起看一下是如何赋值的。
private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {
   // 给$$beanFactory字段赋值
   public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
      // 代理类中需要存在字段: $$beanFactory
      Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);
      Assert.state(field != null, "Unable to find generated BeanFactory field");
      // 通过反射API,为该字段注入spring容器beanFactory
      field.set(obj, args[0]);
      
      if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
         // 如果注解配置类的父类实现了BeanFactoryAware,传入参数BeanFactory,并执行父类方法
         return proxy.invokeSuper(obj, args);
      }
      return null;
   }
}

 通过源码,我们可以看到,直接通过反射给$$beanFactory字段赋值,那么在后续其他流程中,就可以正常使用了,so happy。

聊透spring @Configuration配置类

其实这里并不happy,原理和实现,我们都知道了,但是大家有没有疑惑,这个拦截器中直接从参数args[0]获取了beanFactory,为什么?传递进来的参数到底是什么?又是什么时候回调这个拦截器的。笔者在网上搜了不少文章,好像并没有人提及或者说清这个事情,可能确实有点难度吧,贰师兄仔细研究了下,限于篇幅和对Spring整体体系的限制,本节我们不展开,末尾写个选读吧,大家可以自行决定是否挑战一下哦。

2.3.2.2 保证@Bean工厂方法"冥等"

// 测试代码
@Configuration
public class MarkedByConfiguration {
    @Bean
    public A a(){
        return new A();
    }

    @Bean
    public B b(){
        a();
        return new B();
    }
}

 我们根据测试案例中代码b()->a()来看一下拦截器BeanMethodInterceptor是如何保证幂等的。我们还是先看源码,有个整体印象:

private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {

   public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
            MethodProxy cglibMethodProxy) throws Throwable {

      // 从增强后的注解配置类实例中,获取BeanFactory(也就是成员变量$$beanFactory)
      ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);

      // ① 判断当前调用到的方法,是否是正在执行创建的@Bean工厂方法
      if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
         // 如果当前执行的是工厂方法来实例化bean,那就执行父类中的方法
         return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
      }

      // ② 如果当前执行的方法不是工厂方法,此时就去spring容器中获取@Bean对应的bean实例
      return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
   }
   
   private boolean isCurrentlyInvokedFactoryMethod(Method method) {
       // 获取执行中的@Bean方法,在执行@Bean方法的创建时,会将当前@Bean方法放入ThreadLocal中,这里再取出来
       Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
       // 判断执行的@Bean方法,和正在回调的方法是否相同
       return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
         Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
    }

}

 这里我们只分析核心方法,第一步就是 if (isCurrentlyInvokedFactoryMethod(beanMethod))这个判断逻辑:

  1. 获取当前执行的工厂方法(@Bean方法)。在Spring执行b()工厂方法时,就会将b()放入到ThreadLocal中,标识该工厂方法正在创建中。
  2. 匹配当前正在执行的工厂方法,和当前执行的方法,是否是同一个方法。这里需要注意区别,比如在执行b()时,执行到第一行a()的调用了,进入a()方法后,当前执行的方法是a(),正在执行的@Bean工厂方式仍然是b(),两者肯定不是同一个方法,返回的就是false。

 理解了这个方法的判断逻辑,下面我们再来分析执行b()的具体流程:

  1. 执行到b方法时,将b()放入到ThreadLoacl中,标识正在创建
  2. 执行b(),由于此时b()所在的配置类,已经为代理类实例了,所以会执行拦截器的回调,且b()是@Bean修饰的工厂方法,回调过滤器会选择BeanMethodInterceptor,并执行intercept()拦截逻辑。
  3. 执行isCurrentlyInvokedFactoryMethod()判断,也就是①的代码,此时ThreadLocal中是b(),正在执行的也是b(),二者相同,条件成立,需要执行②处的代码。
  4. 开始执行父类中的方法,也就是原始的,我们自己编写的方法。注意:此时已经从拦截器的方法中跳出来了,执行的是原始的代码。根据执行的顺序,首先执行的就是a()的创建。
  5. 执行a(),由于此时a()所在的配置类,也是代理类实例,且为被@Bean标识的工厂,所以也会被BeanMethodInterceptor拦截,此时也会执行isCurrentlyInvokedFactoryMethod()判断,但是由于此时ThreadLocal中获取到的方法还是b(),而正在执行的方法是a(),二者不同,需要执行③处的代码。
  6. ③处的代码逻辑,主要是去容器中获取bean实例,如果不存在,会先创建bean,放入容器后返回。这不是我们讨论的核心问题,我们假设a()之前已经执行过了,在容器中是存在的,此时直接返回了a对象,并没有再次创建。
  7. 继续执行原始a()的第二行及后面的代码,无关我们的分析,可以忽略。

  我们在代码中加入注释,让大家加深一下印象:

private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {

   public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
            MethodProxy cglibMethodProxy) throws Throwable {

      // 从增强后的注解配置类实例中,获取BeanFactory(也就是成员变量$$beanFactory)
      ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);

      // ① 判断当前调用到的方法,是否是正在执行创建的@Bean工厂方法
      // 1.1: b()执行时,已经将y放入ThreadLocal中,这里获取到的是b,条件成立
      // 2.1 执行到b()调用a()了,a()也被代理了,也会执行到这里,此时ThreadLocal中是y,条件不成立,执行②
      if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
         // 1.2 执行父类中的方法,也就是@Bean工厂方法的逻辑,会调用a()方法呢
         return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
      }

      // ② 如果当前执行的方法不是工厂方法,此时就去spring容器中获取@Bean对应的bean实例
      // 2.2 去Spring容器中查找a对象,不存在会实例化的,这个可以返回
      return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
   }

}

聊透spring @Configuration配置类

 在上面的流程分析时。我们发现,该拦截器确实做到了不会重复创建@Bean工厂方法产生的bean,核心原理是对@Bean方法的每次调用都会拦截,然后先去容器查重,存在直接返回。正是这个操作,保证了@Bean工厂方法的幂等。

聊透spring @Configuration配置类

 至此,我们已经明白了在@Configuration配置类的加持下,为什么@Bean工厂方法可以保持幂等的秘密。希望小伙伴们能有所收获,这是对贰师兄最大的安慰哈。

3. 增强代码的调用(选读)

3.1 BeanFactoryAwareMethodInterceptor的调用

  上述在提到代理类中,动态添加的属性$$beanFactory是通过拦截器BeanFactoryAwareMethodInterceptor反射赋值的。并且在拦截器BeanMethodInterceptor直接使用了。但是没有提提及BeanFactoryAwareMethodInterceptor的被调用时机和调用参数,原因是需要小伙伴对Spring的生命周期和Bean的生命周期需要比较熟悉的理解。大家需要补充对应的知识。

关于Bean的生命周期,聊透Spring bean的生命周期有着比较详细的分析。但是对Spring的生命周期,还没有来得及分析,感兴趣的小伙伴,可以留言催更哦。

  • 注册BeanPostProcessor,触发拦截器

  在上述的分析中,我们已经知道,对于全配置类生成代理子类的逻辑在ConfigurationClassPostProcessor#postProcessBeanFactory()中,该方法除了生成代理类外,还手动添加了ImportAwareBeanPostProcessor这个Bean的后置处理器,该后置处理器的作用就是调用setBeanFactory方法(会被拦截器BeanFactoryAwareMethodInterceptor拦截,进而执行拦截器的内容),最后为属性$$beanFactory赋值

//ConfigurationClassPostProcessor.java
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
   // ...其他代码
   // 对全配置类进行增强
   enhanceConfigurationClasses(beanFactory);
   // 手动添加一个BeanPostProcessor,用来设置setBeanFactory
   beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}


private static class ImportAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
    @Override
    public PropertyValues postProcessProperties(@Nullable PropertyValues pvs, Object bean, String beanName) {
        // 调用setBeanFactory()
        if (bean instanceof EnhancedConfiguration) {
            ((EnhancedConfiguration) bean).setBeanFactory(this.beanFactory);
        }
        return pvs;
    }
}

  我们可以看到,该后置处理器postProcessProperties()的逻辑就是调用setBeanFactory(),并且只调用类型是EnhancedConfiguration子类的哦。小伙伴们还记不记得,Spring为全配置类生成的代理类,手动加了实现EnhancedConfiguration接口的哦,这不就对上了嘛,你说巧不巧😁。

  还有小伙伴需要注意,该后置处理器一旦调用,执行到setBeanFactory()时,就会被BeanFactoryAwareMethodInterceptor拦截,此时传递的参数是this.beanFactory,也就是Spring的bean容器,所以在拦截器直接args[0]反射赋值,才是没有问题的。

  • ImportAwareBeanPostProcessor后置处理器的调用   现在我们已经知道通过后置处理器ImportAwareBeanPostProcessor进行setBeanFactory()的回调,也知道传递的参数是Spring的bean容器。那么问题又来了,这个后置处理器是什么时候调用,毕竟他调用了,拦截器才能执行,属性$$beanFactory才能被赋值啊。

 好烦啊,有没有,没办法,Spring的代码就是这样,功能实现的链路很长,也很分散,一环扣一环,想串联起来很难,这也是笔者写这个系列文章的初衷,想给大家整合起来,明白来龙去脉,这样一来文章篇幅势必很长,没办法的,大家忍耐一下。好了,不啰嗦了,我们继续。

  仔细看笔者聊透Spring bean的生命周期这篇文章的小伙伴可能有印象,在属性填充这一步,回调的后置处理器,有ImportAwareBeanPostProcessor,我们在一起看一下: 聊透spring @Configuration配置类

  OK,现在这个这个链路也清晰了,我们一起总结一下:

聊透spring @Configuration配置类

3.2 BeanMethodInterceptor的调用

  关于@Bean工厂方法的调用时机,其实是在bean的生命周期的初始化bean阶段,不清楚的小伙伴自行查看吧。啰嗦不动了。

  由于BeanFactoryAwareMethodInterceptor拦截器的调用在bean的生命周期的属性填充阶段,BeanMethodInterceptor的调用在初始化bean阶段,所以是能保证BeanFactoryAwareMethodInterceptor拦截器的触发早于BeanMethodInterceptor拦截器的,这也是BeanMethodInterceptor能够直接使用的原因。

 最后我们一起总结一下吧,希望大家多学习,多进步,最后祝大家新年快乐,发大财。

聊透spring @Configuration配置类