写点什么

【原创】Spring Boot 终极篇《下》

用户头像
田维常
关注
发布于: 2020 年 11 月 03 日

关注Java后端技术全栈”**



回复“面试”获取全套面试资料



在前面文章中,我们聊过SpringBoot是如何解决依赖配置,以及如何实现自动装配的。今天我们继续来聊Springboot的启动流程。



SpringBoot项目是如何启动的?



@SpringBootApplication
public class BlogApplication {
    public static void main(String[] args) {
        SpringApplication.run(BlogApplication.class, args);
    }
}




这就是我们项目中所谓的启动类。上一篇文章【原创】Spring Boot终极篇《上》我们主要是聊了注解@SpringBootApplication原理以及是如何实现自动装配的。在这里启动类里还有很重要就是main方法里的:



SpringApplication.run(BlogApplication.class, args);




不妨我们进去看看这个run方法里有些什么东东:



public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class[]{primarySource}, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return (new SpringApplication(primarySources)).run(args);
}




从上面的源码中,我们可以看到这个 run 方法,实际上分两步:



  1. 创建一个 SpringApplication 实体

  2. 执行 run 方法。



SpringApplication 实例化



我们点进去看下,发现其实就是一个初始化的过程。





说一下几个相对重要的配置:



//项目启动类 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 方法初始化启动



我们接下来看下 run 方法。这个 run 方法执行的步骤比较多。下面是源码及部分注释:



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;
    }




这里我们可以将主要执行流程列出来:



1、获取并启动监听器



2、根据监听器和参数来创建运行环境



3、准备 Banner 打印器



4、创建 Spring 容器



5、Spring 容器前置处理



6、刷新容器



7、Spring 容器后置处理



8、发出结束执行的事件通知



9、返回容器



在 run 方法中,在最开始的时候,会启动一个 StopWatch 对象,用来记录 run 方法执行时长,这个我们了解就行,然后是一些初始化,接下啦核心一步就是创建并启动监听器啦。创建好监听器后,就需要创建运行环境。prepareEnvironment () .主要初始化加载外部化配置资源到 environment,包括命令行参数、servletConfigInitParams、application.yml(.yaml/.xml/.properties) 等;初始化日志系统。



private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (!this.isCustomEnvironment) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }




接下来打印 banner



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




再接下来创建 spring 容器



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




创建好 spring 容器后,开始 spring 容器的前置处理



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




前置处理做了很多操作:主要是将启动类注入到容器中,包含环境,上下文,bean 等等。



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);
        //这块会对整个上下文进行一个预处理,比如触发监听器的响应事件、加载资源、设置上下文环境等等
    }




前置处理 执行完之后,就会刷新容器



private void refreshContext(ConfigurableApplicationContext context) {
        this.refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            } catch (AccessControlException var3) {
                ;
            }
        }
    }



主要是对 IOC 容器的初始化。然后就是 spring 容器的后置处理。



afterRefresh(context, applicationArguments);




我们可以重写这个方法,实现我们自定义的需求。



接下来就是发出结束执行的事件通知:



listeners.started(context);




public void started(ConfigurableApplicationContext context) {
        context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
 }



最后就是执行 Runners:



callRunners(context, applicationArguments);




private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        Iterator var4 = (new LinkedHashSet(runners)).iterator();
        while(var4.hasNext()) {
            Object runner = var4.next();
            if (runner instanceof ApplicationRunner) {
                this.callRunner((ApplicationRunner)runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                this.callRunner((CommandLineRunner)runner, args);
            }
        }
    }



用于调用项目中自定义的执行器 XxxRunner 类,使得在项目启动完成后立即执行一些特定程序。Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。Spring Boot 提供了 ApplicationRunner 和 CommandLineRunner 两种服务接口。



执行完之后,就是返回上下文容器了。





启动流程的总结



以上便是Spring Boot的整个启动流程。好啦,最后来总结一下,整个Spring Boot的启动流程,分为以下几个步骤:



  • 加载并且启动监听器

  • 创建项目运行环境,加载配置

  • 初始化 Spring 容器

  • 执行 Spring 容器前置处理器

  • 刷新 Spring 容器

  • 执行 Spring 后置处理器

  • 发布事件

  • 执行自定义执行器

  • 返回容器



看源码时候,咱们尽量看重点,注重整体设计思路的把握。



推荐阅读



笔试题:代码如何实现“百钱买百鸡”?



【原创】90%的人都不会做的一道笔试题



《Redis入门指南》.pdf





发布于: 2020 年 11 月 03 日阅读数: 22
用户头像

田维常

关注

关注公众号:Java后端技术全栈,领500G资料 2020.10.24 加入

关注公众号:Java后端技术全栈,领500G资料

评论

发布
暂无评论
【原创】Spring Boot终极篇《下》