码迷,mamicode.com
首页 > 编程语言 > 详细

@Configuration在SpringBoot中是如何被处理的?

时间:2020-12-18 13:10:13      阅读:4      评论:0      收藏:0      [点我收藏+]

标签:vat   rem   reader   getbean   nmon   data   null   ada   试题   

技术图片

从SpringApplication开始

一般情况下启动SpringBoot都是新建一个类包含main方法,然后使用SpringApplication.run来启动程序:

@SpringBootApplication
public class AutoConfigApplication {

    public static void main(String[] args){
        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(AutoConfigApplication.class,args);
    }
}

SpringApplication.run接收两个参数分别为:primarySource、运行参数(args),上面的代码使用AutoConfigApplication.class作为primarySource。SpringApplication还有一个实例方法也叫run,SpringBoot的大部分启动都由实例run方法来完成的,其中构造ApplicationContext由createApplicationContext方法完成:

protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

createApplicationContext根据this.webApplicationType来构造ApplicationContext,不同的环境都会使用不同的实例,但本文非web环境所有构造的时候会使用AnnotationConfigApplicationContext类。创建AnnotationConfigApplicationContext的时候会调用默认构造方法:

public AnnotationConfigApplicationContext() {
    this.reader = new AnnotatedBeanDefinitionReader(this);
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

AnnotationConfigApplicationContext默认构造函数创建两个对象:

  • reader(AnnotatedBeanDefinitionReader):用于手动注册bean
  • scanner(ClassPathBeanDefinitionScanner): 用于扫描Component、Repository、Service等注解
    AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner会注册一些注解处理器,注册的方式都是使用AnnotationConfigUtils的registerAnnotationConfigProcessors方法:
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
            BeanDefinitionRegistry registry, @Nullable Object source) {

        ...

        if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
        ...
        return beanDefs;
    }

最终AnnotationConfigApplicationContext构造方法执行完成后ApplicationContext会有以下BeanDefinition:

技术图片
构造完ApplicationContext后SpringApplicaiton紧接着会加载primarySource,上面提到 过primarySource是在运行的时候传递进来的(AutoConfigApplication.class),加载过程中不贴代码了,只要知道最终ApplicaitonContext中会多一个AutoConfigApplication的BeanDefinition:

技术图片

小结

总的来说SpringApplicaiton主要干了这些事:

  • 创建AnnotationConfigApplicationContext
    加载一些处理注解的后处理器如:ConfigurationClassPostProcessor
    将primarySource加载进ApplicationContext
    最重要的一点是,现在是有一个AnnotationConfigApplicationContext里面包含了primarySource(AutoConfigApplication)以及ConfigurationClassPostProcessor。打个断点在ApplicaitonContext刷新之前打印下context中的bean的名称,可以确定这样说没毛病!

技术图片

@Configuration啥时候被解析?

虽说有了primarySource和ConfigurationClassPostProcessor后处理器,还是需要有个执行的入口。ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor的实现类,BeanDefinitionRegistryPostProcessor会在ApplicationContext的refresh操作时被处理:

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            ...
            invokeBeanFactoryPostProcessors(beanFactory);
            ...
        }
}

public static void invokeBeanFactoryPostProcessors(
            ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

        ...
        //找出所有类型为BeanDefinitionRegistryPostProcessor的bean的名称
        String[] postProcessorNames =
                    beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
        for (String ppName : postProcessorNames) {
            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                processedBeans.add(ppName);
            }
        }
        sortPostProcessors(currentRegistryProcessors, beanFactory);
        registryProcessors.addAll(currentRegistryProcessors);
        //执行BeanDefinitionRegistryPostProcessor
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
        ...
}

private static void invokeBeanDefinitionRegistryPostProcessors(
        Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {

    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        //调用postProcessBeanDefinitionRegistry方法
        postProcessor.postProcessBeanDefinitionRegistry(registry);
    }
}    

invokeBeanDefinitionRegistryPostProcessors会调用BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,通过断点调试工具确认下ConfigurationClassPostProcessor有没有在这一步被处理:

技术图片
调试输出postProcessors集合里面有一个了ConfigurationClassPostProcessor元素,说明了ConfigurationClassPostProcessor的执行入口没有问题。

ConfigurationClassPostProcessor处理器

ConfigurationClassPostProcessor首先会判断在ApplicationContext中的bean是否被@Configuration注解标记,然后使用ConfigurationClassParser来解析@Configuration,ConfigurationClassPostProcessor的解析@Configuration的大致流程:

  1. 使用ConfigurationClassUtils.checkConfigurationClassCandidate检查BeanDefinition是否@Configuration注解标记
  2. 对@Configuration进行排序
  3. 使用ConfigurationClassParser解析@Configuration注解的信息
  4. 使用ConfigurationClassBeanDefinitionReader解析BeanDefinition
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();

        //获取所有BeanDefinitio名称
        String[] candidateNames = registry.getBeanDefinitionNames();

        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            //如果是full、lite则说明已经处理过的类
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                    ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
                }
            }
            //检查BeanDefinition是否有@Configuration注解
            else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }

        //如果没有找到@Configuration标记的类,则返回不作处理也
        if (configCandidates.isEmpty()) {
            return;
        }

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

        ...

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

        Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
        Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
        do {
            //解析@Configuration class
            parser.parse(candidates);
            parser.validate();

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

            //读取BeanDefinition
            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();
            ...
        }
        while (!candidates.isEmpty());
        ...
    }

最后还是通过调试工具看一下示例中的的启动类AutoConfigApplication没有被处理:

技术图片
图上显示configCandidates中有一个名称为autoConfigApplication的BeanDefinition的元素,说明AutoConfigApplication会被当作配置类解析,但是AutoConfigApplication并没有使用@Configuration注解,为什么还会被当做配置类呢?其实@Configuration在@SpringBootApplication注解中:

技术图片
红色背景列出来的就是@Configuration注解,它是@SpringBootConfiguration的元注解。

推荐阅读:

  • ”12306“秒杀系统的设计艺术
  • 技术总监的反思录,我是如何失去团队掌控的?
  • 假如有人今天把支付宝的存储服务器炸了,支付宝里的钱是不是就没了。。。
  • 再也不用担心被虐啦,高频率JVM面试题,都在这里!
  • 饿了么千万级交易系统的重构设计思路
  • 支付系统高可用架构设计实战,可用性高达99.999!
  • 大型互联网公司分布式ID方案总结

-END-
技术图片
架构文摘
ArchDigest
架构知识丨大型网站丨大数据丨机器学习
技术图片

@Configuration在SpringBoot中是如何被处理的?

标签:vat   rem   reader   getbean   nmon   data   null   ada   试题   

原文地址:https://blog.51cto.com/15054050/2563616

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!