SpringBoot框架的自动装配和启动原理

本文详细解析了SpringBoot的自动配置原理,包括@EnableAutoConfiguration注解如何引导加载自动配置类,以及spring.factories文件在其中的作用。同时,深入介绍了SpringBoot的启动流程,从创建SpringApplication对象到执行run方法的各个阶段,如环境配置、容器创建与刷新等关键步骤。
1、SpringBoot自动装配的原理:
总结:
SpringBoot启动时会通过@EnableAutoConfiguration注解下的@Import注解导入的AutoConfigurationImportSelector类的selectImports()方法通过SpringFatoriesLoader.loadFactoryNames()扫描META-INF/spring.factories配置文件中的所有自动配置类, 这个spring.factories文件也是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表并对其进行加载,这些自动配置类都是以AutoConfiguration结尾命名的,实际上是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类取得全局配置文件中的属性,而XXXProperties类是通过@ConfigurationProperties注解与全局配置文件的属性进行绑定的

SpringBoot启动类上有一个@SpringBootApplication注解,这是一个复合注解,由
@SpringBootConfiguration 代表当前是一个配置类
@EnableAutoConfiguration 开启自动装配
@ComponentScan 扫描组件包
组成,而@EnableAutoConfiguration也是一个复合注解,由
@AutoConfigurationPackage 自动配置包,将MainApplication所在包下所有组件导入
@Import({AutoConfigurationImportSelector.class}) 自动装配的核心
AutoConfigurationImportSelector类下的selectImport()方法调用SpringFactoriesLoader.loadFactoryNames()方法扫描具有META-INF/spring.factories的jar包
以spring-boot-autoconfigure-xxx.jar包为例,它的spring.factories文件是一组组的key-value形式,里面存放的是这个jar包引用的具体依赖的自动配置类,这些
自动配置类又通过@EnableConfigurationProperties注解支持通过xxxProperties.class读取application.properties或application.yml文件中的属性值,如果没有
配置值,就是用默认值,这也就是所谓的约定大于配置,例如其中一个key是xxxEnableAutoConfiguration,value值是一个xxxAutoConfiguration的类名列表,例如
RabbitAutoConfiguration、RedisAutoConfiguration等,点进去其实是一个配置类,例如上述对应的是RabbitAutoConfiguration.class和RedisAutoConfiguration.class
在springboot启动时,SpringApplication.run()方法内部执行selectImports()方法,根据自动配置类的全限定名加载对应的class,将所有自动配置类加载到spring容器中

举例:spring-boot:2.2.4.RELEASE这个jar包
spring-boot:2.2.4.RELEASE--->META-INF/spring.factories--->(k/v)EnableAutoConfiguration/RabbitAutoConfiguration、RedisAutoConfiguration...
RabbitAutoConfiguration点进去是RabbitAutoConfiguration.class---->@EnableConfigurationProperties({RabbitProperties.class})
RabbitProperties.class--->@ConfigurationProperties(prefix = "spring.rabbitmq")对应application.properties/application.yml配置文件的属性值


2、SpringBoot的启动流程:
总结:
SpringBoot启动流程分为两大步:创建SpringApplication对象、执行SpringApplication对象的run方法
创建SpringApplication对象:
    配置source
    检查是否为web环境
    创建初始化构造器(自动装配操作)
    创建应用监听器
    配置应用主方法(main方法)的所在类
执行SpringApplication对象的run方法:
    创建并启动计时器,在启动结束后打印启动时间
    创建一个空的容器对象
    获取并启动监听器,后续的每个阶段都会发送对应事件到监听器
    配置环境ConfigurableEnvironment,这是springboot应用要使用的环境
    打印banner
    给前面创建的空对象context进一步的创建,根据不同的web类型,通过反射获取对应的容器对象
    给创建的容器进行
        前置处理(设置上下文环境、执行所有ApplicationContextInitializer对象的初始化方法、发送上下文准备完成事件、加载bean到上下文、发送上下文加载完成事件)
        刷新(真正进行ApplicationContext的初始化和创建bean)
        后置处理
    停止计时,发出启动结束事件,如果有异常进行处理抛出,没有异常返回创建的容器context

源码:
SpringBoot启动流程大概分为两步:return (new SpringApplication(primarySources)).run(args);分别是创建SpringApplication对象和执行run方法
一、创建SpringApplication对象:基本上是做一些必要的属性初始化和赋值操作
①配置primarySource
②根据classPath检查是否是web应用
③创建初始化容器(构造器)Initializer
④创建监听器
⑤配置应用主方法所在类(main方法所在类)

创建SpringApplication对象方法:
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        //把SpringApplication作为primarySource属性存储
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        //从classPath推断是否为web应用
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //设置初始化容器(Initializer),最终会调用这些功能
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //设置监听器(Listener)
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        //获取main方法所在的类
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }
二、执行run方法(运行spring应用程序)
①启动一个计时器,在整个容器启动完成(启动类启动完成)会打印耗时
②创建一个空的容器对象context
③获取并启动监听器SpringApplicationRunListeners,在对应的阶段发送对应的事件到监听器
    当调用run方法后立即调用,用于最早期的初始化
    环境准备好之后调用
    在启动失败之后调用...
④配置环境ConfigurableEnvironment,这是springboot应用要使用的环境(主要有创建环境、加载属性文件、配置监听)
            //根据不同的web类型创建不同实现的Environment对象
            ConfigurableEnvironment environment = this.getOrCreateEnvironment();
            //配置环境
            this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
            ConfigurationPropertySources.attach((Environment)environment);
            //监听器发送环境配置完成事件
            listeners.environmentPrepared((ConfigurableEnvironment)environment);
            //配置文件中spring.main属性绑定到SpringApplication对象中
            this.bindToSpringApplication((ConfigurableEnvironment)environment);
            //如果用户使用spring.main.web-application-type属性手动设置了webApplicationType
            if (!this.isCustomEnvironment) {
                //将环境对象转换成用户设置的webApplicationType相关类型,它们继承的是同一父类
                environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
            }

            ConfigurationPropertySources.attach((Environment)environment);
            return (ConfigurableEnvironment)environment;
⑤banner的配置和打印,控制台输出的banner图形
⑥给前面创建的空对象context进一步的创建,根据不同的web类型,通过反射获取对应的容器对象
    protected ConfigurableApplicationContext createApplicationContext() {
            Class<?> contextClass = this.applicationContextClass;
            if (contextClass == null) {
                try {
                    switch(this.webApplicationType) {
                    case SERVLET:
                        contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                        break;
                    case REACTIVE:
                        contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                        break;
                    default:
                        contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                    }
                } catch (ClassNotFoundException var3) {
                    throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
                }
            }

            return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
⑦给创建的容器进行前置处理、刷新、后置处理
    //前置处理:设置上下文环境、执行所有ApplicationContextInitializer对象的初始化方法、发送上下文准备完成事件、加载bean到上下文、发送上下文加载完成事件
    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
            //设置上下文环境
            context.setEnvironment(environment);
            this.postProcessApplicationContext(context);
            //执行所有ApplicationContextInitializer对象的initialize方法,这些对象是通过spring.factories文件加载,也就是自动装配那里的东西
            this.applyInitializers(context);
            //发布上下文准备完成事件到所有的监听器
            listeners.contextPrepared(context);
            .......省略一堆代码
            //加载bean到上下文
            this.load(context, sources.toArray(new Object[0]));
            //发送上下文加载完成事件
            listeners.contextLoaded(context);
        }
    //刷新容器:这里真正进行ApplicationContext的初始化和创建bean,下面是AbstractApplicationContext抽象类的刷新方法,具体的实现参考这里
    public void refresh() throws BeansException, IllegalStateException {
            Object var1 = this.startupShutdownMonitor;
            synchronized(this.startupShutdownMonitor) {
                //第一步:准备刷新容器的预备工作:设置容器的启动时间、设置活跃状态为true、设置关闭状态为false、获取Environment对象,加载属性值、准备监听器
                this.prepareRefresh();
                //第二步:创建BeanFactory对象:DefaultListableBeanFactory,加载xml文件属性值到当前工厂
                ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
                //第三步:对BeanFactory做预备工作:对各种属性进行填充
                this.prepareBeanFactory(beanFactory);
                try {
                    //第四步:允许在上下文子类中对BeanFactory进行PostProcessing:子类覆盖方法做额外处理,此处一般不做任何扩展工作
                    this.postProcessBeanFactory(beanFactory);
                    //第五步:调用各种BeanFactory处理器
                    this.invokeBeanFactoryPostProcessors(beanFactory);
                    //第六步:注册bean处理器,这里只是注册功能,真正调用是getBean方法
                    this.registerBeanPostProcessors(beanFactory);
                    //第七步:初始化MessageSource
                    this.initMessageSource();
                    //第八步:初始化事件监听多路广播器
                    this.initApplicationEventMulticaster();
                    //第九步:留给子类来初始化其他的bean
                    this.onRefresh();
                    //第十步:在注册的bean中查找listener bean,加载到消息广播器
                    this.registerListeners();
                    //第十一步:初始化剩下的单实例
                    this.finishBeanFactoryInitialization(beanFactory);
                    //第十二步:完成容器刷新,发布相应事件
                    this.finishRefresh();
                } catch (BeansException var9) {
                    if (this.logger.isWarnEnabled()) {
                        this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                    }
                    //为了防止bean资源异常占用,在异常处理时销毁前面生成的单例bean
                    this.destroyBeans();
                    this.cancelRefresh(var9);
                    throw var9;
                } finally {
                    this.resetCommonCaches();
                }
            }
        }
    //后置处理:一个空的方法
⑧停止计时,发出启动结束事件,如果有异常进行处理抛出,没有异常返回创建的容器context

run方法:
public ConfigurableApplicationContext run(String... args) {
        //计时工具
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        //创建启动上下文对象
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        //第一步:获取并启动监听器,在对应的阶段发送对应的事件到监听器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            //第二步:准备环境
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            //第三步:打印banner,启动时的打印的spring图案
            Banner printedBanner = this.printBanner(environment);
            //第四步:创建spring容器
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            //第五步:spring容器的前置处理
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //第六步:刷新容器
            this.refreshContext(context);
            //第七步:spring容器后置处理
            this.afterRefresh(context, applicationArguments);
            //启动完成后结束计时,这就是我们控制台打印的启动时间
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            //发出启动结束事件
            listeners.started(context);
            //执行runner的run方法
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            //异常处理,如果run过程发生异常,处理并抛出
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            //返回最终构建的容器对象
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雅俗共赏zyyyyyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值