从源码角度深入理解@Configuration注解

@Configuration注解的作用

在Spring早期需要开发者使用到XML配置来配合开发非常的麻烦,在Spring3.0后引进了@Configuration注解,可以被AnnotationConfigApplicationContext和AnnotationConfigWebApplicationContext两个容器给扫描到,就可以实现基于注解来开发。相信各位读者在使用Spring来开发项目时,避免不了使用@Configuration注解标志位配置类,结合@ComponentScan和@MapperScan两个注解来扫描其他类注册到IoC容器中,也可以结合@Bean注解来使用,标有@Bean注解的方法的返回值将注入到IoC容器中,所以@Configuration注解功能是非常的重要,所以此帖从源码来讲解@Configuration注解底层。

@Configuration注解的使用案例

@SpringBootApplication
public class Application {
    public static void main(String[] args) {

        ConfigurableApplicationContext applicationContext = SpringApplication.run(Application.class, args);

        MyConfig config = (MyConfig)applicationContext.getBean("myConfig");
        Cat cat1 = config.cat();
        Cat cat2 = config.cat();
        System.out.println(cat1 == cat2);

        Cat cat = (Cat)applicationContext.getBean("cat");
        System.out.println(cat==cat1);
    }
}

/**
 * @author liha
 * @version 1.0
 * @date 2022/3/22 10:04
 * @description
 */
@Configuration
public class MyConfig {

    @Bean
    public Cat cat(){
        return new Cat();
    }

    @Bean
    public Dog dog(){
        return new Dog();
    }
}

public class Cat {
}

public class Dog {
}


 可以看到控制台输出的结果,证明@Bean将Cat类给注入到IoC容器中,并且是单例的。

源码解读@Configuration注解

源码前的推理:

  1. 标有@Configuration注解类中方法标有@Bean注解方法的返回值就可以注入IoC容器,并且还是单例的,所以肯定是经过某种方式增强方法了,并且还是对类的增强,所以肯定是使用到Cglib动态代理技术。
  2. 从上面的案例我们知道标有@Configuration的类和@Bean注解的方法返回对象已经存在于容器中,所以我们现在可以推测是解析@Configuration注解类的时候把@Bean方法给解析成一个BeanDefinition然后通过BeanFactory给创建。还有一种可能就是BeanFactory创建标有@Configuration注解类的时候执行了标有@Bean的方法,把返回值给注入到IoC容器中。
  3. 如果满足2号条件的话,标有@Configuration注解的类也会被注入到IoC容器中。
  4. 还有很多可以推理的点笔者暂时考虑不到,读者可以自行思考,然后源码证实!

 那么我们的入口在哪里呢?当琢磨不透的时候就可以看到源码类上的注释,可以看到有一个ConfigurationClassPostProcessor类。

从源码角度深入理解@Configuration注解_第1张图片

 这里注释讲解的特别清楚,就是此类用于引导@Configuration的处理,并且可以看到实现了BeanDefinitionRegistryPostProcessor接口,此接口就是重点了。

从源码角度深入理解@Configuration注解_第2张图片

从源码角度深入理解@Configuration注解_第3张图片

 可以清楚的看到继承关系,所以需要重写这两个接口,所以我们推测这两个接口应该是用来解析@Configuration注解类,不过在具体查看实现体之前,我觉得有必要带各位读者来了解一下啥时候回调这接口。

我们知道整个Spring项目启动就是刷新上下文,所以肯定是在refresh()方法中,而从接口和方法名我们可以知道是关于BeanFactory工厂的一些后置操作。所以就可以推断出具体的在哪里回调。

从源码角度深入理解@Configuration注解_第4张图片

从源码角度深入理解@Configuration注解_第5张图片

postProcessBeanDefinitionRegistry具体实现

// 我们知道Spring中就喜欢接口回调来实现一些高扩张,所以这接口也不列外,具体回调地点后面会说
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {

   // 根据HashCode码来验证是否处理过了
   int registryId = System.identityHashCode(registry);
   if (this.registriesPostProcessed.contains(registryId)) {
      throw new IllegalStateException(
            "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
   }
   if (this.factoriesPostProcessed.contains(registryId)) {
      throw new IllegalStateException(
            "postProcessBeanFactory already called on this post-processor against " + registry);
   }
   this.registriesPostProcessed.add(registryId);

   // 具体的实现看下面的代码块
   processConfigBeanDefinitions(registry);
}

 上面代码块跳转到这里,processConfigBeanDefinitions()方法的具体实现

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
   List configCandidates = new ArrayList<>();
   String[] candidateNames = registry.getBeanDefinitionNames();

   for (String beanName : candidateNames) {
      BeanDefinition beanDef = registry.getBeanDefinition(beanName);
      if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
         if (logger.isDebugEnabled()) {
            logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
         }
      }
      else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
         configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
      }
   }

   // Return immediately if no @Configuration classes were found
   if (configCandidates.isEmpty()) {
      return;
   }

   // Sort by previously determined @Order value, if applicable
   configCandidates.sort((bd1, bd2) -> {
      int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
      int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
      return Integer.compare(i1, i2);
   });

   application context
   SingletonBeanRegistry sbr = null;
   if (registry instanceof SingletonBeanRegistry) {
      sbr = (SingletonBeanRegistry) registry;
      if (!this.localBeanNameGeneratorSet) {
         BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
               AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
         if (generator != null) {
            this.componentScanBeanNameGenerator = generator;
            this.importBeanNameGenerator = generator;
         }
      }
   }

   if (this.environment == null) {
      this.environment = new StandardEnvironment();
   }


   ConfigurationClassParser parser = new ConfigurationClassParser(
         this.metadataReaderFactory, this.problemReporter, this.environment,
         this.resourceLoader, this.componentScanBeanNameGenerator, registry);

   Set candidates = new LinkedHashSet<>(configCandidates);
   Set alreadyParsed = new HashSet<>(configCandidates.size());
   do {
      parser.parse(candidates);
      parser.validate();

      Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
      configClasses.removeAll(alreadyParsed);

      
      if (this.reader == null) {
         this.reader = new ConfigurationClassBeanDefinitionReader(
               registry, this.sourceExtractor, this.resourceLoader, this.environment,
               this.importBeanNameGenerator, parser.getImportRegistry());
      }
      this.reader.loadBeanDefinitions(configClasses);
      alreadyParsed.addAll(configClasses);

      candidates.clear();
      if (registry.getBeanDefinitionCount() > candidateNames.length) {
         String[] newCandidateNames = registry.getBeanDefinitionNames();
         Set oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
         Set alreadyParsedClasses = new HashSet<>();
         for (ConfigurationClass configurationClass : alreadyParsed) {
            alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
         }
         for (String candidateName : newCandidateNames) {
            if (!oldCandidateNames.contains(candidateName)) {
               BeanDefinition bd = registry.getBeanDefinition(candidateName);
               if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                     !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                  candidates.add(new BeanDefinitionHolder(bd, candidateName));
               }
            }
         }
         candidateNames = newCandidateNames;
      }
   }
   while (!candidates.isEmpty());

   if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
      sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
   }

   if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {

      ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
   }
}

可以看到代码确实特别特别的长,不过没关系,因为逻辑并不是很复杂,大概的一个流程如下:

  1. 获取到暂有的BeanDefinition,对于Spring boot来说启动类一开始就会被解析成BeanDefinition,并且Spring boot启动类的注解中包含了@Configuration注解。
  2. 对所有的BeanDefinition进行筛选,看是否有@Configuration注解。
  3. 将筛选后的BeanDefinition给进行排序
  4. 创建BeanName的生成器
  5. 从启动类中递归解析到其他的配置类封装成ConfigurationClass类型(Spring boot的自动装配就在这里获取来解析,当然我们自定义的配置类也获取到了)
  6. 获取到ConfigurationClass后解析成BeanDefinition准备后面的BeanFactory装载Bean

注:想具体了解的可以parse()方法打断点追一追

 postProcessBeanFactory具体实现

把所有标有@Configuration注解的BeanDefinition给获取到以后就要开始选择性动态代理了,因为标有@Configuration注解可以设置为不代理,比如Spring boot自动装配的绝大部分都是不启动代理的,因为只运行一次没必要代理浪费性能,而我们自定义的配置类就需要动态代理。

这个方法是实现BeanFactoryPostProcessor重写的方法,回调时机在上面方法之后,所以也就是先获取到BeanDefinition再回调这里来Cglib选择性代理。不懂Cglib的点击下面链接可以看笔者的。

从源码角度分析Cglib动态代理icon-default.png?t=M276https://blog.csdn.net/qq_43799161/article/details/123604338?spm=1001.2014.3001.5501

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {

        // 这里老规矩,通过HASHCODE来判断是否运行过
		int factoryId = System.identityHashCode(beanFactory);
		if (this.factoriesPostProcessed.contains(factoryId)) {
			throw new IllegalStateException(
					"postProcessBeanFactory already called on this post-processor against " + beanFactory);
		}
		this.factoriesPostProcessed.add(factoryId);

        // 这里是没回调过上面的方法,因为需要BeanDefinition才能开始代理,所以这里手动回调一次。
		if (!this.registriesPostProcessed.contains(factoryId)) {
			// BeanDefinitionRegistryPostProcessor hook apparently not supported...
			// Simply call processConfigurationClasses lazily at this point then.
			processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
		}

        // Cglib动态代理逻辑,详情看下面逻辑
		enhanceConfigurationClasses(beanFactory);
		beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
	}

上面方法跳转到这里,enhanceConfigurationClasses()方法详情,也就是Cglib代理的详情

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
   Map configBeanDefs = new LinkedHashMap<>();
   for (String beanName : beanFactory.getBeanDefinitionNames()) {
      BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
      Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
      MethodMetadata methodMetadata = null;
      if (beanDef instanceof AnnotatedBeanDefinition) {
         methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();
      }
      if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
         // Configuration class (full or lite) or a configuration-derived @Bean method
         // -> resolve bean class at this point...
         AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
         if (!abd.hasBeanClass()) {
            try {
               abd.resolveBeanClass(this.beanClassLoader);
            }
            catch (Throwable ex) {
               throw new IllegalStateException(
                     "Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
            }
         }
      }
      if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
         if (!(beanDef instanceof AbstractBeanDefinition)) {
            throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
                  beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
         }
         else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
            logger.info("Cannot enhance @Configuration bean definition '" + beanName +
                  "' since its singleton instance has been created too early. The typical cause " +
                  "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
                  "return type: Consider declaring such methods as 'static'.");
         }
         configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
      }
   }
   if (configBeanDefs.isEmpty()) {
      // nothing to enhance -> return immediately
      return;
   }

   ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
   for (Map.Entry entry : configBeanDefs.entrySet()) {
      AbstractBeanDefinition beanDef = entry.getValue();
      // If a @Configuration class gets proxied, always proxy the target class
      beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
      // Set enhanced subclass of the user-specified bean class
      Class configClass = beanDef.getBeanClass();
      Class enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
      if (configClass != enhancedClass) {
         if (logger.isTraceEnabled()) {
            logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
                  "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
         }
         beanDef.setBeanClass(enhancedClass);
      }
   }
}

代码量确实也挺大的,这里老规矩帮读者写一下执行流程,想追特别特别详细的可以自行Debug

  1. 获取到所有的BeanDefinition开始遍历
  2. 筛选是不是@Configuration,也就是筛选是否之前是ConfigurationClass
  3. 再筛选是否需要代理,通过full和lite来筛选,而@Configuration的proxyBeanMethods为false时候是lite模式就不会产生代理
  4. 准备好动态代理的环境
  5. 遍历获取到需要Cglib动态代理的BeanDefinition
  6. 开始动态代理
  7. 代理完毕后将BeanDefinition的beanClass类型改为代理后的Class类型,这样BeanFactory产出的就是动态代理类型的。

所以我们接下来就仔仔细细讲解动态代理的过程。

从源码角度深入理解@Configuration注解_第6张图片

public Class enhance(Class configClass, @Nullable ClassLoader classLoader) {

   // 已经增强过就直接返回
   if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
      if (logger.isDebugEnabled()) {
         logger.debug(String.format("Ignoring request to enhance %s as it has " +
               "already been enhanced. This usually indicates that more than one " +
               "ConfigurationClassPostProcessor has been registered (e.g. via " +
               "). This is harmless, but you may " +
               "want check your configuration and remove one CCPP if possible",
               configClass.getName()));
      }
      return configClass;
   }

   // 增强的具体的逻辑,也就是查询缓存,缓存中没有就创建,注意这里并不是实例化,只会返回代理的Class
   Class enhancedClass = createClass(newEnhancer(configClass, classLoader));
   if (logger.isTraceEnabled()) {
      logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
            configClass.getName(), enhancedClass.getName()));
   }
   return enhancedClass;
}

因为Cglib底层ASM技术来操作字节码,这需要对Java字节码技术非常熟悉,所以我们并不关系他是如果一行一行生成的代理字节码文件,我们关心的是生成后的字节码文件,所以我们通过某种方式获取到字节码反编译文件来反推。

从源码角度深入理解@Configuration注解_第7张图片

 运行一下项目就会生成在指定的target包下

从源码角度深入理解@Configuration注解_第8张图片

 我们可以很清楚的看到cat和dog两个方法都生成了两个不同的方法,相信各位读者有Cglib的基础这里就不对反编译字节码过多的讲解。

我们知道Cglib走代理方法是回调到自定义的拦截方法上,所以我们是不是应该更关注Spring对拦截方法的处理呢? 

从源码角度深入理解@Configuration注解_第9张图片

提出疑问:

  1. 拦截方法怎么放到代理类中的?
  2. 不同的拦截器在何时被触发( 其实这就是我们最开始的推理的答案了)

1.怎么放到代理类中的?

回到前面的生成代理类的逻辑中

从源码角度深入理解@Configuration注解_第10张图片

从源码角度深入理解@Configuration注解_第11张图片

从源码角度深入理解@Configuration注解_第12张图片

从源码角度深入理解@Configuration注解_第13张图片 

2.不同的拦截器在何时被触发?

首先我们继续看到字节码文件中,我们可以通过ctrl+f来搜索每个方法拦截器在那个方法中使用了。

从源码角度深入理解@Configuration注解_第14张图片

从源码角度深入理解@Configuration注解_第15张图片

从源码角度深入理解@Configuration注解_第16张图片

 字节码层面我们弄清楚以后,就看到Spring层面做了一些什么!

我们先看到1号拦截器,也就是对应的setBeanFactory方法,从方法名字我们知道就是把BeanFactory赋值到代理类

// 1号拦截器,也就是对应的setBeanFactory方法,从方法名字我们知道就是把BeanFactory赋值到代理类
private static class BeanFactoryAwareMethodInterceptor implements MethodInterceptor, ConditionalCallback {

   @Override
   @Nullable
   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");

      // 给代理类中的$$beanFactory字段赋值,obj是代理类,args是参数,
      // 参数也就是BeanFactory,具体哪里回调后面会讲
      field.set(obj, args[0]);
      
      // 如果非代理类,也就是原本类实现了BeanFactoryAware接口就会执行这里,将BeanFactory赋值进去
      if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
         return proxy.invokeSuper(obj, args);
      }

      // 没有实现BeanFactoryAware接口就返回null
      return null;
   }
}

再看到0号拦截器,这里也就是cat和dog方法的拦截器。

private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {

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

      ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
      String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

      if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
         String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
         if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
            beanName = scopedBeanName;
         }
      }

      if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
            factoryContainsBean(beanFactory, beanName)) {
         Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
         if (factoryBean instanceof ScopedProxyFactoryBean) {
           
         }
         else {

            return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
         }
      }

      if (isCurrentlyInvokedFactoryMethod(beanMethod)) {

         if (logger.isInfoEnabled() &&
               BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
            logger.info(String.format("@Bean method %s.%s is non-static and returns an object " +
                        "assignable to Spring's BeanFactoryPostProcessor interface. This will " +
                        "result in a failure to process annotations such as @Autowired, " +
                        "@Resource and @PostConstruct within the method's declaring " +
                        "@Configuration class. Add the 'static' modifier to this method to avoid " +
                        "these container lifecycle issues; see @Bean javadoc for complete details.",
                  beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
         }
         return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
      }

      return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
   }
}

 0号方法拦截器的流程如下:

  1. 获取到BeanFactory
  2. 根据非代理的方法和BeanFactory获取到beanName
  3. 判断bean的作用域
  4. 判断bean是否实现了FactoryBean接口,如果实现了就直接放回getObject()。
  5. 以上都没有就判断是否正在创建bean实例,如果正在创建就会执行@Bean方法的逻辑生成bean实例并返回,所以这里也就证明了最早的推理,是解析@Bean方法成BeanDefinition然后BeanFactory来生产,但是是走的标有@Configuration代理类的代理方法,因为在生成BeanDefinition的时候会标记为@Configuration类的@Bean方法,具体调用时机后面会讲到
  6. 如果不是正在创建bean,也就代表已经创建好了,就会直接查IoC容器的缓存然后返回bean,这样也就保证了单例bean。

下面是两个方法拦截器调用的位置。

0号方法拦截器回调位置。 

这里就不具体讲IoC的一个启动流程了,笔者直接上代码了。

从源码角度深入理解@Configuration注解_第17张图片

 因为在前面就生成了Dog和Cat的BeanDefinition,所以bean工厂也会创建他们的实例,但是标有一个记号FactoryBean(并不是FactoryBean接口,这里只是一个记号),这个记号的值就是@Configuration代理类,所以就会走代理方法,就回调到0号方法拦截器上。非常具体流程读者可以自行Debug。

1号拦截器回调位置。

 
  

从源码角度深入理解@Configuration注解_第18张图片 从源码角度深入理解@Configuration注解_第19张图片

 

总结:

整体来说,难度算比较大的,因为需要很多Spring的基础,后面的方法拦截器回调逻辑讲的不是特别的清楚,因为这需要IoC的基础来讲。叶子->树干->整个树。对于Spring来说就是这样的一个体现。对于此篇讲解@Configuration来说确实挺长,所以需要读者一个基本功和耐心分析。

更希望大家能够自己Debug读一些很细节的流程,如果有不懂的地方可以评论区留言。

最后创作不易,如本帖对您有一定的帮助,希望能点赞+关注+收藏。您的支持是给我最大的动力,后续会一直出各种框架的使用和源码解读~!

你可能感兴趣的:(源码解读,spring系列,spring,spring,boot,java,后端,代理模式)