第10节 Spring源码之 ConfigurationClassPostProcessor 处理器

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

  • Spring 中是如何识别
    @Component @Repository @Service @Controller @Configuration注解标识的类?
  • Spring 中是如何解析
    @Component @Repository @Service @Controller @Configuration注解标识的类?
  • 本篇笔记将围绕着两个问题展开讨论。首先,了解一下 Spring 注解开发的简单使用!

    • application-context.xml 配置包扫描路径
    • 编写注解配置类
    @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了!!!
    image.png

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

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

    Spring 容器扫描到相关配置类的依据就是
    标签,根据标签配置的 base-package去扫描对应包路径下的类,然后一一校验

    结论: 解析 标签时识别 @Component @Repository @Service @Controller @Configuration 注解标识的类

    • 关于Spring是如何解析标签,参考笔记:第7节 Spring源码之 obtainFreshBeanFactory 方法
    • 标签时是Spring自定义标签,所以走自定义标签解析逻辑

    image.png

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

    image.png

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

    • ComponentScanBeanDefinitionParserparse方法执行解析逻辑

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

    1. ClassPathBeanDefinitionScanner 的 doScan 方法

    protected Set doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set beanDefinitions = new LinkedHashSet();
    for (String basePackage : basePackages) {
    // 扫描basePackage,将符合要求的bean定义全部找出来
    Set 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等,这点很重要!!!
    image.png

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

    image.png

    • 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 中配置 ,然后通过 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. 注解解析时机

    image.png

    第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 configCandidates = new ArrayList();
    // 省略代码 ....
    // Parse each @Configuration class
    ConfigurationClassParser parser = new ConfigurationClassParser(
    this.metadataReaderFactory, this.problemReporter, this.environment,
    this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    // 存放 BeanDefinitionHolder 对象
    Set candidates = new LinkedHashSet(configCandidates);
    // 存放扫描包下的所有 bean
    Set 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 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 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 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 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