新年新干货!SpringBoot启动流程原理+自动装配原理讲解,建议收藏!

前言

SpringBoot 设计的目的是为了让你尽可能快的跑起来 Spring 应用程序并且尽可能减少你的配置文件。SpringBoot相对Spring的优点主要有两个:
1.起步依赖-会将很多jar包按照功能合并成stater整体进行版本管理和引用,解决Spring集成其他框架时jar版本管理问题
2.自动装配-引入相关的jar包后SpringBoot会自动注册一些比较关键的bean,并进行默认配置,不用我们进行特殊配置,解决Spring重量级XML配置问题。比如整合Mybatis时的SqlSessionFactory

注:其中起步依赖主要是解决版本控制问题,主要设计在于POM文件,这里主要探究第二优点自动装配。

SpringBoot启动依靠的是带有main方法的启动类,启动类的内容可以分为两个部分一个是启动类上@SpringBootApplication这个注解;第二部分是main方法里的SpringApplication.run(启动类.class,args)方法。下面主要就是分析一下这两部分分别是什么作用?完成了什么功能?怎样实现的自动装配?以及SpringBoot的启动流程分析。


@SpringBootApplication注解剖析

@SpringBootApplication是个组合注解包含四个元注解和@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan组成,下面逐个分析。


@SpringBootConfiguration

@SpringBootConfiguration也是一个组合注解由元注解和@Configuration构成,@Configuration是@Component的一个衍生注解主要作用是标记当前类是个配置类。


在这里插入图片描述

@EnableAutoConfiguration

@EnableAutoConfiguration也是一个组合注解由元注解和@AutoConfigurationPackage、@Import注解构成,Spring中有很多Enable开头的注解,其作用大都是借助@Import来收集并注册特定场景相关的bean。@EnableAutoConfiguration的主要作用就是借助@Import来收集并注册所有符合自动装配条件的bean。


@AutoConfigurationPackage

注:很多人以为@SpringBootApplication可以扫描启动类当前包及其子包下面的类是由此注解完成的,是错误的

@AutoConfigurationPackage由元注解和@Import注解组成


在这里插入图片描述

@Import注解导入了AutoConfigurationPackages.Registrar.class实现了ImportBeanDefinitionRegistrar接口会调用registerBeanDefinitions方法

进入AutoConfigurationPackages#register,这里主要为Spring容器里注入了BasePackages的BeanDefinition目的是讲启动类的包路径传入容器,官网解释在后面整合jpa时会用到,这里暂不做探究。

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        // 如果已经存在该 BEAN ,则修改其包(package)属性
        // BEAN 就是 AutoConfigurationPackages,用于存储自动配置包以供稍后引用
        if (registry.containsBeanDefinition(BEAN)) {
            BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
            ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
            // 将构造函数的第一个参数设置为包名列表
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        // 如果不存在该 BEAN ,则创建一个 Bean ,并进行注册
        } else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(BasePackages.class);
            // 将beanClass设置为BasePackages
            beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
            // 将构造函数的第一个参数设置为包名列表,也就是BasePackages的构造函数
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            // 注册beanDefinition
            registry.registerBeanDefinition(BEAN, beanDefinition);
        }
    }

@Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector类是SpringBoot实现自动装配的关键。AutoConfigurationImportSelector实现了DeferredImportSelector接口会调用process和selectImports方法(在何处调用会在后面2.2.3讲到),其中selectImports方法会返回一个数组,数组中的类都会注册到Spring容器中

AutoConfigurationImportSelector.AutoConfigurationGroup.class
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            // 断言
            Assert.state(
                    deferredImportSelector instanceof AutoConfigurationImportSelector,
                    () -> String.format("Only %s implementations are supported, got %s",
                            AutoConfigurationImportSelector.class.getSimpleName(),
                            deferredImportSelector.getClass().getName()));
            // 获得 AutoConfigurationEntry 对象
            // 核心方法:获取并过滤全部自动装配的类
            AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
                    .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
            // 添加到 autoConfigurationEntries 中
            this.autoConfigurationEntries.add(autoConfigurationEntry);
            // 添加到 entries 中
            for (String importClassName : autoConfigurationEntry.getConfigurations()) {
                this.entries.putIfAbsent(importClassName, annotationMetadata);
            }
        }

AutoConfigurationImportSelector.AutoConfigurationGroup.class
public Iterable<Entry> selectImports() {
            // 如果为空,则返回空数组
            if (this.autoConfigurationEntries.isEmpty()) {
                return Collections.emptyList();
            }
            // 获得 allExclusions
            Set<String> allExclusions = this.autoConfigurationEntries.stream()
                    .map(AutoConfigurationEntry::getExclusions)
                    .flatMap(Collection::stream).collect(Collectors.toSet());
            // 获得 processedConfigurations
            Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
                    .map(AutoConfigurationEntry::getConfigurations)
                    .flatMap(Collection::stream)
                    .collect(Collectors.toCollection(LinkedHashSet::new));
            // 从 processedConfigurations 中,移除排除的
            processedConfigurations.removeAll(allExclusions);
            // 处理,返回结果
            return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()) // 排序
                        .stream()
                        .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)) // 创建 Entry 对象
                        .collect(Collectors.toList()); // 转换成 List
        }

由源码可以看到selectImports只是对process中封装到autoConfigurationEntries的结果进行分组排序等处理后返回,下面主要看到process中的getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);方法

AutoConfigurationImportSelector.class
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        // 1\. 判断是否开启注解。如未开启,返回空串
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        // 2\. 获得注解的属性
        AnnotationAttributes attributes = getAttributes(annotationMetadata);

        // 3\. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
        // spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
        // 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
        // 将这些值作为自动配置类导入到容器中,自动配置类就生效了
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

        // 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
        configurations = removeDuplicates(configurations);
        // 4\. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
        // 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
        //找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        // 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
        checkExcludedClasses(configurations, exclusions);
        // 4.2 从 configurations 中,移除所有不希望自动配置的配置类
        configurations.removeAll(exclusions);

        // 5\. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类

        //@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
        //@ConditionalOnMissingClass : classpath中不存在该类时起效
        //@ConditionalOnBean : DI容器中存在该类型Bean时起效
        //@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
        //@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
        //@ConditionalOnExpression : SpEL表达式结果为true时
        //@ConditionalOnProperty : 参数设置或者值一致时起效
        //@ConditionalOnResource : 指定的文件存在时起效
        //@ConditionalOnJndi : 指定的JNDI存在时起效
        //@ConditionalOnJava : 指定的Java版本存在时起效
        //@ConditionalOnWebApplication : Web应用环境下起效
        //@ConditionalOnNotWebApplication : 非Web应用环境下起效

        //要判断@Conditional是否满足
        // 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
        configurations = filter(configurations, autoConfigurationMetadata);

        // 6\. 将自动配置导入事件通知监听器
        //当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
        // 并触发fireAutoConfigurationImportEvents事件。
        fireAutoConfigurationImportEvents(configurations, exclusions);
        // 7\. 创建 AutoConfigurationEntry 对象
        return new AutoConfigurationEntry(configurations, exclusions);
    }

@ComponentScan

这个注解才是@SpringBootApplication会默认扫描启动类所在包以及子包路径下全部类的原因

SpringApplication.run(启动类.class,args)方法剖析

SpringApplication#run主要完成的事件可以分成两部分1.实例化SpringApplication对象2. run(args):调用run方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
       //SpringApplication的启动由两部分组成:
        //1\. 实例化SpringApplication对象
        //2\. run(args):调用run方法
        return new SpringApplication(primarySources).run(args);
    }

实例化SpringApplication对象

在实例化SpringApplication中设置的初始化器和监听器都是在/META-INF/spring.factories 中获取的

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {

        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");

        //项目启动类 SpringbootDemoApplication.class设置为属性存储起来
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

        //设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
        this.webApplicationType = WebApplicationType.deduceFromClasspath();

        // 设置初始化器(Initializer),最后会调用这些初始化器
        //所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

        // 设置监听器(Listener)
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

        // 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
        this.mainApplicationClass = deduceMainApplicationClass();
    }

run(args):调用run方法

这里一个分为九步,最核心的是3、4、5下面会逐一介绍:

  • 获取并启动监听器,监听器也是在spring.factories中获取的。
  • 项目运行环境Environment的预配置
  • 创建Spring容器
  • Spring容器前置处理,这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
  • 刷新容器
  • Spring容器后置处理,扩展接口,设计模式中的模板方法,默认为空实现。
  • 向监听器发出结束执行的事件通知
  • 执行Runners
  • 向监听器发布应用上下文就绪事件
public ConfigurableApplicationContext run(String... args) {
        // 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 初始化应用上下文和异常报告集合
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        // 配置 headless 属性
        configureHeadlessProperty();

        //   (1)获取并启动监听器
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            // 创建  ApplicationArguments 对象 初始化默认应用参数类
            // args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

            //(2)项目运行环境Environment的预配置
            // 创建并配置当前SpringBoot应用将要使用的Environment
            // 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

            configureIgnoreBeanInfo(environment);
            // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
            Banner printedBanner = printBanner(environment);

            // (3)创建Spring容器
            context = createApplicationContext();
            // 获得异常报告器 SpringBootExceptionReporter 数组
            //这一步的逻辑和实例化初始化器和监听器的一样,
            // 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);

            // (4)Spring容器前置处理
            //这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);

            // (5):刷新容器
            refreshContext(context);

            // (6):Spring容器后置处理
            //扩展接口,设计模式中的模板方法,默认为空实现。
            // 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
            afterRefresh(context, applicationArguments);
            // 停止 StopWatch 统计时长
            stopWatch.stop();
            // 打印 Spring Boot 启动的时长日志。
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // (7)发出结束执行的事件通知
            listeners.started(context);

            // (8):执行Runners
            //用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
            //Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
            //Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
            // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        //   (9)发布应用上下文就绪事件
        //表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
        // 这样整个Spring Boot项目就正式启动完成了。
        try {
            listeners.running(context);
        } catch (Throwable ex) {
            // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
         //返回容器
        return context;
    }

run(args)方法——第三步之创建Spring应用上下文

这里根据实例SpringApplication时获取的应用类型来创建不同的应用上下文对象

SpringApplication.class
protected ConfigurableApplicationContext createApplicationContext() {
        // 根据 webApplicationType 类型,获得 ApplicationContext 类型
        // 这里创建容器的类型 还是根据webApplicationType进行判断的,
        // 该类型为SERVLET类型,所以会通过反射装载对应的字节码,
        // 也就是AnnotationConfigServletWebServerApplicationContext

        // 先判断有没有指定的实现类
        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);
            }
        }
        // 创建 ApplicationContext 对象
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

run(args)方法——第四步之Spring应用上下文前置处理

这块会对整个上下文进行一个预处理,比如触发监听器的响应事件、加载资源、设置上下文环境等等

SpringApplication.class
private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        //设置容器环境,包括各种变量
        context.setEnvironment(environment);

        //设置上下文的 bean 生成器和资源加载器
        postProcessApplicationContext(context);

        //执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)
        applyInitializers(context);

        //触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法
        listeners.contextPrepared(context);

        //记录启动日志
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        // Add boot specific singleton beans
        //注册启动参数bean,这里将容器指定的参数封装成bean,注入容器
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        // Load the sources
        // 加载所有资源
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //加载我们的启动类,将启动类注入容器,为后续开启自动化配置奠定基础
        load(context, sources.toArray(new Object[0]));

        //触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
        listeners.contextLoaded(context);

    }

在前置处理中最核心的一步是加载我们的启动类,将启动类注入容器,为后续开启自动化配置奠定基础load(context, sources.toArray(new Object[0]));

BeanDefinitionLoader.class
private int load(Object source) {
        Assert.notNull(source, "Source must not be null");
        // 如果是 Class 类型,则使用 AnnotatedBeanDefinitionReader 执行加载
        if (source instanceof Class<?>) {
            return load((Class<?>) source);
        }
        // 如果是 Resource 类型,则使用 XmlBeanDefinitionReader 执行加载
        if (source instanceof Resource) {
            return load((Resource) source);
        }
        // 如果是 Package 类型,则使用 ClassPathBeanDefinitionScanner 执行加载
        if (source instanceof Package) {
            return load((Package) source);
        }
        // 如果是 CharSequence 类型,则各种尝试去加载
        if (source instanceof CharSequence) {
            return load((CharSequence) source);
        }
        // 无法处理的类型,抛出 IllegalArgumentException 异常
        throw new IllegalArgumentException("Invalid source type " + source.getClass());
    }

run(args)方法——第五步之刷新容器

这里刷新容器最终调用的是AbstractApplication#refresh方法

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            // 第一步 刷新前的预处理
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            // 第二步 1.创建BeanFactory实例,默认实现是DefaultListableBeanFactory
            //       2.解析XML中的<bean>为BeanDefition 并注册到 BeanDefitionRegistry
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            // 第三步 BeanFactory的预准备⼯作(BeanFactory进⾏⼀些设置,⽐如context的类加载器等)
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                // 第四步 BeanFactory准备工作完成后的后置处理工作,钩子方法,等子类重写
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                // 第五步 实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean
                // 提前初始化工厂后置处理器bean,并调用postProcessBeanFactory方法
                //其中BeanFactoryPostProcessor比较重要的一个ConfigurationClassPostProcessor在这里调用,
                //用来遍历BeanDefinitionRegistry中现有的BeanDefinition解析@Import、@Configuration
                // 、@ComponentScan等注解将注解覆盖到的类也注册到BeanDefinitionRegistry中
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                // 第六步 注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                // 第七步 初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
                initMessageSource();

                // Initialize event multicaster for this context.
                // 第八步 初始化事件派发器
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                // 第九步 ⼦类重写这个⽅法,在容器刷新的时候可以⾃定义逻辑,钩子方法
                onRefresh();

                // Check for listener beans and register them.
                // 第十步 注册应⽤的监听器。就是注册实现了ApplicationListener接⼝的监听器bean
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                // 第十一步 初始化所有剩下的⾮懒加载的单例bean
                //1).初始化创建⾮懒加载⽅式的单例Bean实例(未设置属性)
                //2).填充属性
                //3) .如果bean实现了Aware相关接口,则调用Aware接口的实现方法
                //4) .调用BeanPostProcessor处理器的前置方法
                //5).初始化⽅法调⽤(⽐如调⽤afterPropertiesSet⽅法、init-method⽅法)
                //6).调⽤BeanPostProcessor(后置处理器)对实例bean进⾏后置处
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                // 第十二步 完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事件 (ContextRefreshedEvent)
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

这也是Spring容器启动的经典方法这里就不每个步骤逐一过了,只重点关注和SpringBoot自动装配相关的步骤——第五步 实例化并调⽤实现了BeanFactoryPostProcessor接⼝的Bean,就是在这一步解析的@SpringBootApplication这个组合注解,BeanFactoryPostProcessor比较重要的一个ConfigurationClassPostProcessor在这里调用,用来遍历BeanDefinitionRegistry中现有的BeanDefinition解析@Import、@Configuration 、@ComponentScan等注解将注解覆盖到的类也注册到BeanDefinitionRegistry中。

a.进入ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry

ConfigurationClassPostProcessor.class
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);
    }

b.进入ConfigurationClassPostProcessor#processConfigBeanDefinitions,这里遍历BeanDefinitionRegistry现有的全部类不包含@Configuration的类不会进行解析,这也是为什么配置类需要加@Configuration的原因

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
        String[] candidateNames = registry.getBeanDefinitionNames();

        for (String beanName : candidateNames) {
            BeanDefinition beanDef = registry.getBeanDefinition(beanName);
            if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
                    ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
                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));
            }
        }

        //如果不存在@Configuration直接return
        // Return immediately if no @Configuration classes were found
        if (configCandidates.isEmpty()) {
            return;
        }

        // Sort by previously determined @Order value, if applicable
        configCandidates.sort((bd1, bd2) -> {
            int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
            int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
            return Integer.compare(i1, i2);
        });

        // Detect any custom bean name generation strategy supplied through the enclosing application context
        SingletonBeanRegistry sbr = null;
        if (registry instanceof SingletonBeanRegistry) {
            sbr = (SingletonBeanRegistry) registry;
            if (!this.localBeanNameGeneratorSet) {
                BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
                if (generator != null) {
                    this.componentScanBeanNameGenerator = generator;
                    this.importBeanNameGenerator = generator;
                }
            }
        }

        if (this.environment == null) {
            this.environment = new StandardEnvironment();
        }

        // Parse each @Configuration class
        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 {
            // 核心解析方法
            parser.parse(candidates);
            parser.validate();

            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());
            }
            this.reader.loadBeanDefinitions(configClasses);
            alreadyParsed.addAll(configClasses);

            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());

        // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
        if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
            sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
        }

        if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
            // Clear cache in externally provided MetadataReaderFactory; this is a no-op
            // for a shared cache since it'll be cleared by the ApplicationContext.
            ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
        }
    }

c.进入ConfigurationClassParser#parse

public void parse(Set<BeanDefinitionHolder> configCandidates) {
        for (BeanDefinitionHolder holder : configCandidates) {
            BeanDefinition bd = holder.getBeanDefinition();
            try {
                if (bd instanceof AnnotatedBeanDefinition) {
                    // 注解解析BeanDefinition核心方法
                    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
                }
                else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                    parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
                }
                else {
                    parse(bd.getBeanClassName(), holder.getBeanName());
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
            }
        }

        this.deferredImportSelectorHandler.process();
    }

d.进入ConfigurationClassParser#processConfigurationClass

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
        if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
            return;
        }

        ConfigurationClass existingClass = this.configurationClasses.get(configClass);
        if (existingClass != null) {
            if (configClass.isImported()) {
                if (existingClass.isImported()) {
                    existingClass.mergeImportedBy(configClass);
                }
                // Otherwise ignore new imported config class; existing non-imported class overrides it.
                return;
            }
            else {
                // Explicit bean definition found, probably replacing an import.
                // Let's remove the old one and go with the new one.
                this.configurationClasses.remove(configClass);
                this.knownSuperclasses.values().removeIf(configClass::equals);
            }
        }

        // Recursively process the configuration class and its superclass hierarchy.
        SourceClass sourceClass = asSourceClass(configClass);
        do {
            // 解析核心方法
            sourceClass = doProcessConfigurationClass(configClass, sourceClass);
        }
        while (sourceClass != null);

        this.configurationClasses.put(configClass, configClass);
    }

e.进入ConfigurationClassParser#doProcessConfigurationClass,在这里解析@PropertySource、@ComponentScan、@Import、@Bean、@ImportResource等注解,并将其覆盖的资源或类加载到容器上下文中,每个注解的具体解析细节这里就不深探讨了,主要梳理流程

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

        if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
            // Recursively process any member (nested) classes first
            processMemberClasses(configClass, sourceClass);
        }
        //解析@PropertySource注解
        // Process any @PropertySource annotations
        for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), PropertySources.class,
                org.springframework.context.annotation.PropertySource.class)) {
            if (this.environment instanceof ConfigurableEnvironment) {
                processPropertySource(propertySource);
            }
            else {
                logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment");
            }
        }
        // 解析@ComponentScan注解
        // Process any @ComponentScan annotations
        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
                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
                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());
                    }
                }
            }
        }
        // 解析@Import注解
        // Process any @Import annotations
        processImports(configClass, sourceClass, getImports(sourceClass), true);
        // 解析@ImportResource注解
        // Process any @ImportResource annotations
        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);
            }
        }
        // 解析@Bean注解
        // Process individual @Bean methods
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

        // Process default methods on interfaces
        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;
    }

f.此处简单过一下@Import注解的解析过程,验证一下1.2.2 @Import(AutoConfigurationImportSelector.class)中process和selectImports方法的调用进入ConfigurationClassParser#processImports

在进入ConfigurationClassParser#handle

public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
            DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(
                    configClass, importSelector);
            if (this.deferredImportSelectors == null) {
                DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
                handler.register(holder);
                // 核心方法
                handler.processGroupImports();
            }
            else {
                this.deferredImportSelectors.add(holder);
            }

        }

进入ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports

public void processGroupImports() {
            for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
                //getImports()中调用了AutoConfigurationImportSelector.AutoConfigurationGroup.class中的process和selectImports
                grouping.getImports().forEach(entry -> {
                    ConfigurationClass configurationClass = this.configurationClasses.get(
                            entry.getMetadata());
                    try {
                        processImports(configurationClass, asSourceClass(configurationClass),
                                asSourceClasses(entry.getImportClassName()), false);
                    }
                    catch (BeanDefinitionStoreException ex) {
                        throw ex;
                    }
                    catch (Throwable ex) {
                        throw new BeanDefinitionStoreException(
                                "Failed to process import candidates for configuration class [" +
                                        configurationClass.getMetadata().getClassName() + "]", ex);
                    }
                });
            }
        }

进入ConfigurationClassParser.DeferredImportSelectorGrouping#getImports方法此处调用了AutoConfigurationImportSelector.AutoConfigurationGroup的process和selectImports

最后

欢迎关注公众号:前程有光,领取一线大厂Java面试题总结+各知识点学习思维导+一份300页pdf文档的Java核心知识点总结! 这些资料的内容都是面试时面试官必问的知识点,篇章包括了很多知识点,其中包括了有基础知识、Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等。

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

推荐阅读更多精彩内容