likes
comments
collection
share

第10节 Spring源码之 ConfigurationClassPostProcessor 处理器

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

ConfigurationClassPostProcessor可以说是Spring中重要的处理器之一。为什么重要,从两个问题开始入手思考?

  1. Spring 中是如何识别 @Component @Repository @Service @Controller @Configuration注解标识的类?
  2. Spring 中是如何解析 @Component @Repository @Service @Controller @Configuration注解标识的类?

本篇笔记将围绕着两个问题展开讨论。首先,了解一下 Spring 注解开发的简单使用!

  • application-context.xml 配置包扫描路径
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-2.0.xsd
            http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-2.5.xsd">

   <context:component-scan base-package="com.sff.demo"/>
</beans>
  • 编写注解配置类
@Component
public class TestComponentAnno {
}

@Configuration
public class TestConfiguration {
}

@Controller
public class TestControllerAnno {
}

@Repository
public class TestRepositoryAnno {
}

@Service
public class TestServiceAnno {
}

这里主要编写 @Component @Repository @Service @Controller @Configuration 标识的类。这里要重点说明一下 @Component 注解和 @Repository @Service @Controller @Configuration 注解的关系

注解含义
@Component标识Spring中最普通的组件,可以被注入到Spring容器中进行管理
@Repository作用于持久层
@Service作用于业务逻辑层
@Controller作用于表现层
@Configuration标识当前类是配置类

(1)@Repository @Service @Controller @Configuration@Component 注解的扩展,被用于特定的业务场景;base-package 指定包路径下被以上注解修饰的类都能够被 Spring 容器自动识别

(2)@Component 注解的功能就是将标识的类自动注入到 Spring 容器中, @Repository @Service @Controller @Configuration 都是被 @Component 注解修饰的注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
   @AliasFor(annotation = Component.class)
   String value() default "";

}
  • 编写注解测试类
public class TestAnnoMain {

   public static void main(String[] args) {
      testComponent();
   }

   public static void testComponent() {
      ClassPathXmlApplicationContext cx = new ClassPathXmlApplicationContext("application-context.xml");

      Teacher user = (Teacher) cx.getBean("teacher");
      System.out.println(user);
   }
}

运行之后,我们发现beanFactory中已经有了以上注解标识的beanName了!!! 第10节 Spring源码之 ConfigurationClassPostProcessor 处理器

一、 Spring 中是如何识别 @Component @Repository @Service @Controller @Configuration注解标识的类?

所谓识别就是在 Spring 容器启动后,如何找到由这些注解标识的类

Spring 容器扫描到相关配置类的依据就是 <context:component-scan base-package=""/> 标签,根据标签配置的 base-package去扫描对应包路径下的类,然后一一校验

结论: 解析 <context:component-scan>标签时识别 @Component @Repository @Service @Controller @Configuration 注解标识的类

第10节 Spring源码之 ConfigurationClassPostProcessor 处理器

  • <context> 对应的命名空间解析器是 ContextNamespaceHandler

第10节 Spring源码之 ConfigurationClassPostProcessor 处理器

所以标签正在的解析逻辑是在 ComponentScanBeanDefinitionParser 中处理的

  • ComponentScanBeanDefinitionParserparse方法执行解析逻辑

第10节 Spring源码之 ConfigurationClassPostProcessor 处理器 核心逻辑就是通过创建 ClassPathBeanDefinitionScanner对象,然后调用doScan方法来扫描获取对应注解标识的类的BeanDefinitionHolder,然后向容器 beanFactory 中注册 BeanDefinition

1. ClassPathBeanDefinitionScanner 的 doScan 方法

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   for (String basePackage : basePackages) {
      
      // 扫描basePackage,将符合要求的bean定义全部找出来
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      
      for (BeanDefinition candidate : candidates) {
         // 解析 @Scope注解,包括 scopeName 和 proxyMode
         ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
         candidate.setScope(scopeMetadata.getScopeName());

         // 使用beanName生成器来生成beanName
         String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
         if (candidate instanceof AbstractBeanDefinition) {
            // 处理beanDefinition对象,例如,此bean是否可以自动装配到其他bean中
            postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
         }

         if (candidate instanceof AnnotatedBeanDefinition) {
            // 处理定义在目标类上的通用注解,包括@Lazy,@Primary,@DependsOn,@Role,@Description
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
         }

         // 检查beanName是否已经注册过,如果注册过,检查是否兼容
         if (checkCandidate(beanName, candidate)) {

            // 将当前遍历bean的bean定义和beanName封装成BeanDefinitionHolder
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            // 根据proxyMode的值,选择是否创建作用域代理
            definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);

            // 注册 beanDefinition
            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}

2. doScan 中的 registreBeanDefinition 方法

该方法向 beanFactory 中注册一些后置处理器,比如:ConfigurationClassPostProcessor等,这点很重要!!! 第10节 Spring源码之 ConfigurationClassPostProcessor 处理器

注册之后我们可以看到 beanDefinitionMap 属性中有很多Spring内置的后置处理器,比如 ConfigurationClassPostProcessorAutowiredAnnotationBeanProcessor

第10节 Spring源码之 ConfigurationClassPostProcessor 处理器

  • org.springframework.context.annotation.internalConfigurationAnnotationProcessor:用于处理 @Configuration 注解的后置处理器的bean
  • org.springframework.context.annotation.internalAutowiredAnnotationProcessor:于处理@Autowired、@Value、@Inject、@Lookup注解的后置处理器bean
  • org.springframework.context.annotation.internalCommonAnnotationProcessor:用于处理JSR-250注解,例如 @Resource、@PostConstruct、@PreDestroy 的后置处理器bean
  • org.springframework.context.annotation.internalPersistenceAnnotationProcessor:用于处理JPA注解的后置处理器bean
  • org.springframework.context.event.internalEventListenerProcessor:用于处理 @EventListener 注解的后置处理器的bean

该注册Spring内置的后置处理器的逻辑封装在 AnnotationConfigUtilsregisterAnnotationConfigProcessors方法中

二、 Spring 中是如何解析 @Component @Repository @Service @Controller @Configuration注解标识的类?

通过第一个问题,明白了Spring容器是如何将@Component @Repository @Service @Controller @Configuration注解标识的类如何转化成 BeanDefinition的。同时也会向Spring注册一些默认的后置处理器,比如 ConfigurationClassPostProcessor,该处理器就是用来解析 @Component @Repository @Service @Controller @Configuration 这些注解的。

注意:

  • 第一个问题的引入是通过在 application.xml 中配置 <context:component-scan base-package=""/> ,然后通过 ClassPathXmlApplicationContext 来启动容器,所以会有解析标签的逻辑
  • 如果我们完全基于注解开发,是不会有走标签解析逻辑,而解析注解的逻辑还在ConfigurationClassPostProcessor中完成的。
public class TestAnnoMain {
   public static void main(String[] args) {
      testComponentScan();
   }

   public static void testComponentScan() {
      ApplicationContext ac = new AnnotationConfigApplicationContext(BeanConfig.class);
      Teacher user = (Teacher) ac.getBean("teacher");
      System.out.println(user);
   }
}

@Configuration
@ComponentScan(basePackages = "com.sff.demo")
@ImportResource(locations = "classpath:application-context.xml")
public class BeanConfig {
}

1. 注解解析时机

第10节 Spring源码之 ConfigurationClassPostProcessor 处理器

第9节 Spring源码之 invokeBeanFactoryPostProcessors 方法,调用 invokeBeanFactoryPostProcessors 方法的作用就是:注册 BeanFactoryPostProcessor 处理器,实例化并调用 BeanFactoryPostProcessor 处理器的实现。所以触发 ConfigurationClassPostProcessor处理器的执行是在调用 invokeBeanFactoryPostProcessors 方法中

/**
 * 实例化并调用 BeanFactoryPostProcessor 处理器
 */
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
   // 实例化并调用 BeanFactoryPostProcessor 处理器核心逻辑方法
   PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
   if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
      beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
      beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
   }
}

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors 方法主要执行 BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor 后置处理器的方法,即

@FunctionalInterface
public interface BeanFactoryPostProcessor {
   void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
   void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
  • BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor 的子类。
  • BeanFactoryPostProcessor 主要是对 BeanFactory 的操作;而 BeanDefinitionRegistryPostProcessor 主要对 BeanDefinition 的注册操作,在Spring中对注册可以理解为增删改查操作。
  • PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors 中优先执行postProcessBeanDefinitionRegistry方法,然后执行 postProcessBeanFactory 方法。

结论: @Component @Repository @Service @Controller @Configuration注解的解析是在ConfigurationClassPostProcessor 处理器的 postProcessBeanDefinitionRegistry方法中完成的

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
      PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
       // 根据对应的 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);

       // 处理配置类的 BeanDefinition 信息
       processConfigBeanDefinitions(registry);
    }
}

2. processConfigBeanDefinitions(registry) 方法

该方法的逻辑非常复杂,处理了Spring注解的解析逻辑,如果理解了该方法逻辑,那么对 SpringBoot自动装配原理也就清楚了!!!

  • 该方法的核心逻辑如下代码块,利用ConfigurationClassParserparse方法完成@Component、@Import、@ImportResource、@ComponentScan、@ComponentScans、@Bean的解析,即完成 BeanDefinition的转换,并向Spring容器注入BeanDefinition
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();

    // 省略代码 ....
    
	// Parse each @Configuration class
	ConfigurationClassParser parser = new ConfigurationClassParser(
			this.metadataReaderFactory, this.problemReporter, this.environment,
			this.resourceLoader, this.componentScanBeanNameGenerator, registry);
	// 存放 BeanDefinitionHolder 对象
	Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
	// 存放扫描包下的所有 bean
	Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
	do {
		// 解析带有 @Component、@Import、@ImportResource、@ComponentScan、@ComponentScans、@Bean 的 BeanDefinition
		parser.parse(candidates);
		// 将解析完的 Configuration 配置类进行校验,(1) 配置类不能是final;(2) @Bean修饰的方法必须可以重写以支持CGLIB
		parser.validate();
		// 获取所有的bean,包括扫描的bean对象、@Import导入的bean对象
		Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
		configClasses.removeAll(alreadyParsed);
		// Read the model and create bean definitions based on its content
		if (this.reader == null) {
			this.reader = new ConfigurationClassBeanDefinitionReader(
					registry, this.sourceExtractor, this.resourceLoader, this.environment,
					this.importBeanNameGenerator, parser.getImportRegistry());
		}
                
		// 核心方法,将完全填充好的ConfigurationClass实例转化为BeanDefinition注册入IOC容器
		this.reader.loadBeanDefinitions(configClasses);
                
		alreadyParsed.addAll(configClasses);
		candidates.clear();
		
        // 省略代码 ....
	} while (!candidates.isEmpty());
	
}
  • ConfigurationClassParserparse 方法
public void parse(Set<BeanDefinitionHolder> configCandidates) {
   this.deferredImportSelectors = new LinkedList<>();

   for (BeanDefinitionHolder holder : configCandidates) {
      BeanDefinition bd = holder.getBeanDefinition();
      // 根据 BeanDefinition 类型的不同,调用 parse 不同的重载方法,实际上最终都是调用 processConfigurationClass 方法
         if (bd instanceof AnnotatedBeanDefinition) {

            // 根据注解元数据和beanName解析配置文件,有注解元数据
            parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
         } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {

            // 根据Class和beanName解析配置文件,有Class对象
            parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
         } else {

            // 根据className和beanName解析配置文件,读取元数据
            parse(bd.getBeanClassName(), holder.getBeanName());
         }
   }

   /**
    * 执行找到的DeferredImportSelector
    * (1) ImportSelector 和 @Import注解 有同的效果,但是实现了 ImportSelector 的类可以条件性的决定导入某些配置
    * (2) DeferredImportSelector的执行时机是在所有其他的配置类被处理后才进行处理
    */
   processDeferredImportSelectors();
}

parse 方法内部又调用了私有的 parse 方法,核心调用链路如下: parse -> processConfigurationClass -> doProcessConfigurationClass,所重点掌握 doProcessConfigurationClass 方法的逻辑,即可了解Spring注解解析的过程,doProcessConfigurationClass 方法逻辑步骤如下:

  • 解析 @Configuration 注解标记的类

  • 解析 @PropertySource 注解标记的类,目的就是加载 properties 配置文件

  • 解析 @ComponentScan 或者 @ComponentScans 注解标记的类,主要是扫描basePackage=com.test属性对应包下 @Bean @Component @Repository @Service @Controller @Configuration 注解标识的类,识别后转化成BeanDefinition

  • 解析 @Import 注解标记的类

  • 解析 @ImportResource 注解标记的类

  • 解析 @Bean 注解标记的方法

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {

   // Recursively process any member (nested) classes first
   // 解析有 @Configuration 注解的类,该注解继承 @Component;递归解析配置类中的内部配置类
   processMemberClasses(configClass, sourceClass);

   // Process any @PropertySource annotations
   // 如果配置类上加了 @PropertySource 注解,那么就解析加载 properties 文件,并将属性添加到 Spring 上下文中
   for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) {
      if (this.environment instanceof ConfigurableEnvironment) {
         processPropertySource(propertySource);
      } else {
         logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment");
      }
   }

   /**
    * Process any @ComponentScan annotations
    * 1. 处理 @ComponentScan 或者 @ComponentScans 注解
    * 2. 将自定义的bean加载到IOC容器,因为扫描到的类可能也添加了 @ComponentScan@ComponentScans 注解,所以需进行递归解析
    * 3. 该步骤逻辑只针对通过配置类配包扫描路径时才会在后置处理中将 basePackage 扫描到的类注册到到容器中
    *
    * @Configuration
    * @ComponentScan(basePackages = "com.sff.demo")
    * public class BeanConfig {
    * }
    *
    * 启动类应该使用 ApplicationContext ac = new AnnotationConfigApplicationContext(BeanConfig.class)
    */
   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) {

         /**
          * The config class is annotated with @ComponentScan -> perform the scan immediately
          * 1. 解析 @ComponentScan@ComponentScans 的扫描包所包含的类,此时的 componentScan 对象就是 @ComponentScan 所有属性的封装
          * 2. 比如 basePackages = com.test, 那么在这一步会扫描出这个包及子包下有
          *    @Bean @Component @Repository @Service @Controller @Configuration 注解标记的类,然后将其解析成 BeanDefinition
          */
         Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

         /**
          * Check the set of scanned definitions for any further config classes and parse recursively if needed
          * 1. 通过 componentScanParser.parse 方法扫描到了 basePackages = com.test 包下所有 @Bean @Component @Repository @Service  @Controller @Configuration 注解标识的类
          * 2. 然后对这些 BeanDefinition 进行递归解析,因为它们可能会再添加 @ComponentScan@ComponentScans注解
          */
         for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
            if (bdCand == null) {
               bdCand = holder.getBeanDefinition();
            }
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
               // 通过递归方法进行解析
               parse(bdCand.getBeanClassName(), holder.getBeanName());
            }
         }
      }
   }

   // Process any @Import annotations
   // 解析 @Import 注解
   processImports(configClass, sourceClass, getImports(sourceClass), true);

   // Process any @ImportResource annotations
   // 处理 @ImportResource注解,导入 Spring 的配置文件;@ImportResource(locations = {"classpath:beans.xml"})
   AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
   if (importResource != null) {
      String[] resources = importResource.getStringArray("locations");
      Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
      for (String resource : resources) {
         String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
         configClass.addImportedResource(resolvedResource, readerClass);
      }
   }

   // Process individual @Bean methods
   // 处理加了 @Bean 注解的方法,将 @Bean方法 转化为 BeanMethod 对象,保存再集合中
   Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
   for (MethodMetadata methodMetadata : beanMethods) {
      configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
   }

   // Process default methods on interfaces
   // 处理接口的默认方法实现,从jdk8开始,接口中的方法可以有自己的默认实现,因此如果这个接口的方法加了@Bean注解,也需要被解析
   processInterfaces(configClass, sourceClass);

   // Process superclass, if any
   // 解析父类,如果被解析的配置类继承了某个类,那么配置类的父类也会被进行解析
   if (sourceClass.getMetadata().hasSuperClass()) {
      String superclass = sourceClass.getMetadata().getSuperClassName();
      if (superclass != null && !superclass.startsWith("java") && !this.knownSuperclasses.containsKey(superclass)) {
         this.knownSuperclasses.put(superclass, configClass);
         // Superclass found, return its annotation metadata and recurse
         return sourceClass.getSuperClass();
      }
   }
   // No superclass -> processing is complete
   return null;
}

三、 写在最后

  • 本篇笔记涉及的逻辑比较复杂,通过文字难以清楚表达
  • 自己多动手,多翻译源码