本章节咱们来探索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配置类