Spring源码分析——Configuration配置类解析流程

示例工程

引入Maven依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.5</version>
    </dependency>
</dependencies>

在项目中新建一个byx.test包,然后添加以下三个类:

public class A {
}

public class B {
}

@Configuration
public class MyConfig {
    @Bean
    public A a() {
        return new A();
    }

    @Bean
    public B b() {
        return new B();
    }
}

再添加一个Main类作为启动类:

public class Main {
    public static void main(String[] args) {
        // 初始化容器
        ApplicationContext ctx = new AnnotationConfigApplicationContext("byx.test");
        
        // 输出容器中所有bean的name
        for (String name : ctx.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }
}

运行main方法,控制台输出如下:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
myConfig
a
b

可以看到,容器中一共有7个bean。前面4个org开头的是Spring内部的组件,myConfig是我们定义的配置类MyConfigabMyConfig使用Bean注解导入的bean。

下面就来探究一下Spring是如何处理配置类的。

ConfigurationClassPostProcessor执行流程

配置类处理的入口是在ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法,实现如下:

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
        PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware {
    ...
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        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);
    }
    ...
}

ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor,根据Spring的生命周期,里面的postProcessBeanDefinitionRegistry方法会在容器初始化时被回调。

postProcessBeanDefinitionRegistry方法中,真正的逻辑是在最后一行processConfigBeanDefinitions(registry)调用。这个方法十分复杂,它包含了完整的配置类解析逻辑,下面来一点点分析。

// 用来保存所有配置类的定义
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();

这里首先创建了一个configCandidates列表,用来保存容器中的所有配置类,即所有被Configuration注解标注的类。由于Configuration注解被Component注解修饰,所以所有的配置类在AnnotationConfigApplicationContext初始化过程中就已经被注册到容器中了。

// 当前容器中所有bean的name
String[] candidateNames = registry.getBeanDefinitionNames();

// 遍历容器中的所有bean
// 如果满足配置类的条件(ConfigurationClassUtils.checkConfigurationClassCandidate返回true),则加入configCandidates
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));
    }
}

上面的这个for循环用来遍历当前容器中的所有bean,如果满足配置类的条件,则加入到configCandidates中。那么是如何判断一个bean是不是配置类呢?从代码中可以看到,是通过调用ConfigurationClassUtils.checkConfigurationClassCandidate方法来判断的。

ConfigurationClassUtils.checkConfigurationClassCandidate方法实现如下:

public static boolean checkConfigurationClassCandidate(
        BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
    ...

    AnnotationMetadata metadata;
    // 获取beanDef的注解元数据
    ...

    // 获取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);
    }
    // 轻量级配置类:不为配置类生成代理
    else if (config != null || isConfigurationCandidate(metadata)) {
        beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
    }
    else {
        return false;
    }

    // 能执行到这里说明是一个配置类,下面判断是否需要进行顺序处理
    Integer order = getOrder(metadata);
    if (order != null) {
        beanDef.setAttribute(ORDER_ATTRIBUTE, order);
    }

    return true;
}

public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
    // 忽略接口类型
    if (metadata.isInterface()) {
        return false;
    }

    // 是否被若干种指定注解之一标注?
    // candidateIndicators中包含了Component、ComponentScan、Import、ImportResource这几个注解
    for (String indicator : candidateIndicators) {
        if (metadata.isAnnotated(indicator)) {
            return true;
        }
    }

    // 看看是否有被Bean注解标注的方法
    return hasBeanMethods(metadata);
}

ConfigurationClassUtils.checkConfigurationClassCandidate方法的逻辑比较简单,核心就是判断类上面有没有标注Configuration注解,同时还包含重量级配置类和轻量级配置类的处理。

回到ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry方法。获取了所有配置类后,接下来进行一些额外处理:

// 如果不存在配置类,则提前返回
if (configCandidates.isEmpty()) {
    return;
}

// 对所有配置类进行排序
configCandidates.sort((bd1, bd2) -> {
    int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
    int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
    return Integer.compare(i1, i2);
});

在阅读下面的代码之前,先来介绍一下Spring中与配置类相关的几个重要组件:

  • ConfigurationClassParser:将配置类的BeanDefinitionHolder转换成ConfigurationClass
  • ConfigurationClass:Spring内部用来封装配置类相关信息的包装类,包括对所有Bean方法的封装
  • ConfigurationClassBeanDefinitionReader:用来注册配置类内部被Bean注解标注的方法声明的bean,内部包括对条件装配以及各种导入的的处理

下面是配置类处理的核心代码:

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

// candidates保存当前待处理的配置类
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);

// alreadyParsed保存已处理的配置类
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());

do {
    // 使用parser将candidates中的配置类definition转换成ConfigurationClass
    StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
    parser.parse(candidates);
    parser.validate();
    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());

    configClasses.removeAll(alreadyParsed);

    // 创建ConfigurationClassBeanDefinitionReader
    if (this.reader == null) {
        this.reader = new ConfigurationClassBeanDefinitionReader(
                registry, this.sourceExtractor, this.resourceLoader, this.environment,
                this.importBeanNameGenerator, parser.getImportRegistry());
    }

    // 使用reader注册配置类中定义的组件
    this.reader.loadBeanDefinitions(configClasses);

    alreadyParsed.addAll(configClasses);
    processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

    candidates.clear();
    
    // 解析新增的配置类
    if (registry.getBeanDefinitionCount() > candidateNames.length) {
        String[] newCandidateNames = registry.getBeanDefinitionNames();
        Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
        Set<String> 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());

上面的代码就是用了前面提到的三个关键组件来对配置类进行解析的,首先调用ConfigurationClassParserparse方法将配置类的BeanDefinitionHolder转换成ConfigurationClass,然后传入ConfigurationClassBeanDefinitionReaderloadBeanDefinitions方法执行真正的注册操作。如果使用调试器可以发现,当执行完this.reader.loadBeanDefinitions(configClasses)这行代码后,当前容器的beanDefinitionMap的大小增加了。

上面的代码还包含了一个do-while循环,这个循环用来一遍又一遍地解析新增的配置类,因为一个配置类可能会用Bean注解导入另一些配置类,这些新增的配置类会在下一轮循环被解析,直到没有新增的配置类。

到这里,配置类处理的大致流程就分析完了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容