Spring之ConfigurationClassPostProcessor解析流程

1.前言

之前的博文可能会设计到Spring的解析流程,但是一句二句说不清,只能一笔带过,今天整理一下这一块的流程,记录下来

2.解析顺序

  1. 解析内部类
  2. 解析@PropertySources @PropertySource注解
  3. 解析@ComponentScans @ComponentScan注解
  4. 解析@Import注解
  5. 解析@ImportResource注解
  6. 解析@Bean注解
  7. 解析JDK8以后的默认方法
  8. 解析父类

3.源码解析

具体方法在ConfigurationClassParser#processConfigurationClass方法中

上图有两个标注的方法,我们在这里约定一下:

  • processConfigurationClass都统称为大循环方法
  • doProcessConfigurationClass都统称为小循环方法
1.解析内部类

我们看一下,相关源码

  1. 首先判断configClass是否存在@Component注解,如果存在,则处理内部类。
  2. 判断内部类是不是配置类,如果满足条件,则加入候选者列表,然后循环执行processConfigurationClass方法

如果有不清楚什么是配置类的小伙伴,可以看我之前写的一篇博文 《Spring之什么是配置类

2.解析@PropertySources @PropertySource注解

这个注解主要是处理一些自定义的环境变量,我们举例看一下,使用方法

创建一个application.properties,添加属性ping

创建一个配置类AppConfig

package com.test.spring.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;

/**
 * 这里可以不用加@Configuration
 * AnnotationConfigApplicationContext会将构造方法注入的类,解析成bd
 */
@ComponentScan("com.test.spring")
@PropertySource("classpath:application.properties")
public class AppConfig {
}

运行main方法,查看运行结果

3.解析@ComponentScans @ComponentScan注解

详细可以看这篇博文 《Spring之为什么标记了@Component的类会被扫描成bean

 大概流程就是调用ComponentScanAnnotationParser的parse方法,这个方法会创建一个ClassPathBeanDefinitionScanner对象,这个scanner对象的构造方法的useDefaultFilters属相决定会不会注册默认Filter(@Component @Named @ManagedBean标注的类),然后将@ComponentScans @ComponentScan注解的属性解析并设置到这个scanner对象中,然后调用这个scanner对象的doScan方法,之后就查看这个scanner的Filters(自定义+默认)方法是否返回true,如果返回值为true,就会将指定资源解析成ScannedGenericBeanDefinition对象,然乎将这些bd对象返回,如果bd对象是配置类,则调用大循环方法进一步解析

4.解析@Import注解

我们来看关键方法ConfigurationClassParser#processImports

方法分成三个分支,分别处理实现ImportSelector接口,实现ImportBeanDefinitionRegistrar接口,普通配置类三种方式处理,具体处理流程,我将整理的图贴出来

对于这几种情况,我们写几个简单例子,了解具体用法

demo1

创建一个类ModelM

package com.test.spring.model;

public class ModelM {
}

创建一个ImportSelector子类实现

package com.test.spring.imports;

import com.test.spring.model.ModelM;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class FirstImport implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{ModelM.class.getName()};
    }
}

配置类加上此import

package com.test.spring.config;

import com.test.spring.imports.FirstImport;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;

/**
 * 这里可以不用加@Configuration
 * AnnotationConfigApplicationContext会将构造方法注入的类,解析成bd
 */
@ComponentScan("com.test.spring")
@PropertySource("classpath:application.properties")
@Import(FirstImport.class)
public class AppConfig {
}

运行main方法,查看运行结果

selectImports方法返回的类名 最终会被解析并实例化成bean

demo2

创建一个类ModelN

package com.test.spring.model;

public class ModelN {
}

创建一个ImportBeanDefinitionRegistrar子类实现

package com.test.spring.imports;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class SecondImport implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClassName("com.test.spring.model.ModelN");
        registry.registerBeanDefinition("modelN", beanDefinition);
    }
}

配置类加上此import

package com.test.spring.config;

import com.test.spring.imports.FirstImport;
import com.test.spring.imports.SecondImport;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;

/**
 * 这里可以不用加@Configuration
 * AnnotationConfigApplicationContext会将构造方法注入的类,解析成bd
 */
@ComponentScan("com.test.spring")
@PropertySource("classpath:application.properties")
@Import({FirstImport.class, SecondImport.class})
public class AppConfig {
}

运行main方法,查看运行结果

这种方法很类似bfpp的功能

demo3

创建类ModelO modelP

package com.test.spring.model;

public class ModelO {
}
package com.test.spring.model;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class ModelP implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{ModelO.class.getName()};
    }
}

创建一个ImportSelector子类实现

package com.test.spring.imports;

import com.test.spring.model.ModelP;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class ThirdImport implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{ModelP.class.getName()};
    }
}

配置类加上此import

package com.test.spring.config;

import com.test.spring.imports.FirstImport;
import com.test.spring.imports.SecondImport;
import com.test.spring.imports.ThirdImport;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;

/**
 * 这里可以不用加@Configuration
 * AnnotationConfigApplicationContext会将构造方法注入的类,解析成bd
 */
@ComponentScan("com.test.spring")
@PropertySource("classpath:application.properties")
@Import({FirstImport.class, SecondImport.class, ThirdImport.class})
public class AppConfig {
}

运行main方法,查看运行结果

ImportSelector子类实现如果import了一个ImportSelector子类实现,则Spring容器中只存在最后一次非ImportSelector子类实现类型的bean

这个@Import有两个注意点需要大家注意

  • 针对demo1和demo3 beanName是类的全限定名 而不是普通类名的驼峰形式
  • 如果一个@Import注入的类同时实现ImportSelector和ImportBeanDefinitionRegistrar接口,则会执行方法第一分支,即第一优先级分支
总结

@Import是Spring重要的扩展点之一,上面举的例子主要讲解用法,有兴趣的小伙伴可以扩展一下,比如说可以在import的类上加上@ComponentScan注解,去扫描一个Spring扫描不到的路径。

最近更新了一篇博文 《@Import在Spring中的应用之@EnableAsync》希望能帮助大家加深对注入流程的理解

5.解析@ImportResource注解

略 以后有机会补上

6.解析@Bean注解

这个主要就是查看资源没有@Bean标记的方法,如果存在就将结果设置到ConfigurationClass对象相关属性上

7.解析JDK8以后的默认方法

这一种我们主要举例说明一下

创建接口ModelQ

package com.test.spring.model;

import org.springframework.context.annotation.Bean;

public interface ModelQ {

    @Bean
    default InnerModelQ getInnerModelQ() {
        return new InnerModelQ();
    }

    class InnerModelQ {

    }
}

 创建实现类ModelQImpl

package com.test.spring.model;

import org.springframework.stereotype.Component;

@Component
public class ModelQImpl implements ModelQ {
}

运行main方法,查看运行结果

8.解析父类

之前我约定了大循环方法和小循环方法,主要就是想说明一点,只有这种情况可能存在非null的返回值,从而进入小循环

这种就是如果符合条件的父类也和普通的配置类一样解析,但是不会put额外的ConfigurationClass对象到相关map对象中了,即以后不会产生同Type的多个bean

9.额外说明
  • 步骤4 ImportBeanDefinitionRegistrar相关
  • 步骤5
  • 步骤6
  • 步骤7

上面四个步骤的解析结果 都是在ConfigurationClass对象设置相关属性,具体执行在ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass方法中

  • 33
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值