微服务 SpringBoot 2.0(四):启动剖析之SpringApplication.run

我以为只是运行了个main方法,可却启动了服务 —— 知码学院

引言

前一章我们讲解了启动原理的@SpringBootApplication部分,仔细跟着看代码还是很好理解,若需观看@SpringBootApplication注解部分请点击,接下来这章主要讲解run方法运行部分,run方法运行这里很枯燥乏味,所以请带好你的精气神,准备好水,因为实在是太干了。

运行启动

工具

  • SpringBoot版本:2.0.4
  • 开发工具:IDEA 2018
  • Maven:3.3 9
  • JDK:1.8

我们继续看这段启动代码

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

SpringApplication.run分析

分析该方法主要分两部分,一是SpringApplication实例化,二是run方法的执行。

#该方法返回一个ConfigurableApplicationContext对象
SpringApplication.run(MySpringConfigurationApp.class, args);//参数1-应用入口的类,参数2-命令行参数
分析点一、SpringApplication实例化

在SpringApplication实例初始化的时候,它会做 4 件有意义的事情:

  1. 推断应用类型是Standard还是Web
    可能会出现三种结果REACTIVENONESERVLET

     this.webApplicationType = this.deduceWebApplicationType();//该行代码位于构造器中
    
  2. 查找并加载所有可用初始化器,设置到initializers属性中

     this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));//spring.factories处读取
    
  3. 找出所有的应用程序监听器,设置到listeners属性中。

     this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));//该行代码位于构造器中
    
  4. 推断并设置main方法的定义类,意思就是找出运行的主类。

     this.mainApplicationClass = this.deduceMainApplicationClass();//该行代码位于构造器中
    

至此,对于SpringApplication实例的初始化过程就结束了,接下来进入方法调用环节。


分析点二、SpringApplication.run方法调用

接下来的过程比较枯燥无味,如果您跟着仔细看完,你一定有所收获。
执行流程图源文件:https://www.processon.com/view/link/5b825917e4b0d4d65be7066a PS 画图好累

执行流程图

流程图对应的代码如下

public ConfigurableApplicationContext run(String... args) {
        //计时工具
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();

        // step1  设置java.awt.headless系统属性为true - 没有图形化界面
        this.configureHeadlessProperty();

        //step2  初始化监听器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);

        //step3  启动已准备好的监听器,发布应用启动事件
        listeners.starting();

        Collection exceptionReporters;
        try {

            //step4  根据SpringApplicationRunListeners以及参数来装配环境,如java -jar xx.jar --server.port=8000
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);

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

            //step6  创建Spring ApplicationContext()上下文
            context = this.createApplicationContext();

            //step7  准备异常报告器
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, 
             new Class[]{ConfigurableApplicationContext.class}, context);

            //step8  装配Context,上下文前置处理
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

            //step9  Spring上下文刷新处理
            this.refreshContext(context);

            //step10  Spring上下文后置结束处理 
            this.afterRefresh(context, applicationArguments);

            // 停止计时器监控
            stopWatch.stop();

            //输出日志记录执行主类名、时间信息
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            //step11  发布应用上下文启动完成事件
            listeners.started(context);

            //step12  执行所有 Runner 运行器
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            //step13  发布应用上下文就绪事件
            listeners.running(context);

            //step14  返回应用上下文
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }
代码解析
step1 java.awt.headless系统属性设置
this.configureHeadlessProperty();

Headless模式是系统的一种配置模式。设置该默认值为:true,Java.awt.headless = true 有什么作用?

对于一个 Java 服务器来说经常要处理一些图形元素,例如地图的创建或者图形和图表等。这些API基本上总是需要运行一个X-server以便能使用AWT(Abstract Window Toolkit,抽象窗口工具集)。然而运行一个不必要的 X-server 并不是一种好的管理方式。有时你甚至不能运行 X-server,因此最好的方案是运行 headless 服务器,来进行简单的图像处理。

方法声明如下:
private void configureHeadlessProperty() {
        System.setProperty("java.awt.headless", System.getProperty("java.awt.headless", Boolean.toString(this.headless)));
}
step2 初始化监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);

该方法最终落到了loadSpringFactories方法上,加载了META-INF/spring.factories这个配置文件下的所有资源,并放入缓存,然后再获取了org.springframework.context.ApplicationListener定义的资源列表。这些也就是SpringBoot的自动装配资源,然后获取到后存放到list中,并调用createSpringFactoriesInstances函数创建了SpringFactories的实例。

方法声明如下:
private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
        return new SpringApplicationRunListeners(logger, 
        this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
META-INF/spring.factories文件部分:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
step3 启动已准备好的监听器
listeners.starting();

通过step2操作拿到监听器集合了之后,再统一遍历出来后,为每个Listener都分配了一个任务线程去启动它们

方法声明如下:
public void starting() {
        this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        Iterator var4 = this.getApplicationListeners(event, type).iterator();

        while(var4.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var4.next();
            Executor executor = this.getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> {
                    this.invokeListener(listener, event);
                });
            } else {
                this.invokeListener(listener, event);
            }
        }

}
step4 装配环境参数
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

我们来看一下new DefaultApplicationArguments(args)这个构造函数,跟踪进去发现调用的SimpleCommandLineArgsParser.parse函数,从下面代码中,可以看到该方法读取了命令行参数:

方法声明如下:
public CommandLineArgs parse(String... args) {
        CommandLineArgs commandLineArgs = new CommandLineArgs();
        String[] var3 = args;
        int var4 = args.length;

        //省略读取命令行代码...
        return commandLineArgs;
    }

tip:那到这里你是否会联想到:java -jar xx.jar --server.port=8000后面的参数呢
接着我们看下一行:

ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);

通过这行代码我们可以看到spring boot把前面创建出来的listeners和命令行参数,传递到prepareEnvironment函数中来准备运行环境。来看一下prepareEnvironment函数的真面目:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, 
ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = this.getOrCreateEnvironment();
        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared((ConfigurableEnvironment)environment);
        this.bindToSpringApplication((ConfigurableEnvironment)environment);
        if (this.webApplicationType == WebApplicationType.NONE) {
            environment = (new EnvironmentConverter(this.getClassLoader())).convertToStandardEnvironmentIfNecessary(
(ConfigurableEnvironment)environment);
        }

        ConfigurationPropertySources.attach((Environment)environment);
        return (ConfigurableEnvironment)environment;
    }

在这里我们看到了环境是通过getOrCreateEnvironment创建出来的,再深挖一下getOrCreateEnvironment的源码:

private ConfigurableEnvironment getOrCreateEnvironment() {
        //如果environment 已经存在,则直接返回当前的环境。思考:究竟什么情况下回存在呢?
        if (this.environment != null) {
            return this.environment;
        } else {
            //判断webApplicationType是不是SERVLET,如果是,则创建Servlet的环境,否则创建基本环境
            return (ConfigurableEnvironment)(this.webApplicationType == WebApplicationType.SERVLET
             ? new StandardServletEnvironment() : new StandardEnvironment());
        }
    }

接着我们看下一行:

this.configureIgnoreBeanInfo(environment);
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
        //如果System中的spring.beaninfo.ignore属性为空,就把当前环境中的属性覆盖上去
        //spring.beaninfo.ignore= true 跳过搜索BeanInfo类
        if (System.getProperty("spring.beaninfo.ignore") == null) {
            Boolean ignore = (Boolean)environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
            System.setProperty("spring.beaninfo.ignore", ignore.toString());
        }

    }
step5 打印Banner
Banner printedBanner = this.printBanner(environment);

spring boot启动的时候,控制台显示的那个艺术字体图案

step6 创建Spring上下文
context = this.createApplicationContext();

spring boot是根据不同的webApplicationType的类型,来创建不同的ApplicationContext

代码声明如下:
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);
    }
step7 准备异常报告器

继续来看下一步,准备异常报告,又特么是这个getSpringFactoriesInstances

exceptionReporters = getSpringFactoriesInstances(
    SpringBootExceptionReporter.class,
    new Class[] { ConfigurableApplicationContext.class }, context);

大家可以再仔细看看META-INF/spring.factories文件

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
step8 装配Context,上下文前置处理
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);

这行代码是把上面已经创建好的对象,传递给prepareContext来准备上下文

prepareContext()声明如下:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, 
    SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        //这里传入的是 StandardServletEnvironment
        context.setEnvironment(environment);
        //设置上下文的beanNameGenerator(bean生成器)和resourceLoader(资源加载器)
        this.postProcessApplicationContext(context);
        //拿到之前实例化SpringApplication对象的时候设置的ApplicationContextInitializer,调用它们的initialize方法,对上下文做初始化
        this.applyInitializers(context);
        //调用listeners#contextPrepared,该方法是一个空实现,以后我们可以扩展的地方
        listeners.contextPrepared(context);
        //打印启动日志
        if (this.logStartupInfo) {
            this.logStartupInfo(context.getParent() == null);
            this.logStartupProfileInfo(context);
        }
        //往上下文的beanFactory中注册一个singleton的bean,bean的名字是springApplicationArguments,bean的实例是之前实例化的ApplicationArguments对象
        context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);

        //如果之前获取的printedBanner不为空
        if (printedBanner != null) {
        //往上下文的beanFactory中注册一个singleton的bean,bean的名字是springBootBanner,bean的实例就是这个printedBanner.这里默认是SpringBootBanner.
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }
        //获取所有资源
        Set<Object> sources = this.getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //调用load方法注册启动类的bean定义,也就是调用SpringApplication.run(Application.class, args);的类
        //SpringApplication的load方法内会创建BeanDefinitionLoader的对象,并调用它的load()方法
        this.load(context, sources.toArray(new Object[0]));
        //上下文已经加载,该方法先找到所有的ApplicationListener,遍历这些listener
        //如果该listener继承了ApplicationContextAware类,这一步会调用它的setApplicationContext方法,设置context
        listeners.contextLoaded(context);
    }
step9 Spring上下文刷新 refreshContext
this.refreshContext(context);

refreshContext函数中,第一行调用了refresh(context);跳转了一下,下面的代码是注册了一个应用关闭的函数钩子,如下

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

}

通过代码跟踪分析发现,其实是调用了AbstractApplicationContext中的refresh方法。
ServletWebServerApplicationContext和ReactiveWebServerApplicationContext的refresh函数中都是调用了super.refresh();

代码如下:
public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        //同步快执行刷新操作
        synchronized(this.startupShutdownMonitor) {
            //准备刷新上下文
            this.prepareRefresh();
            //获取了bean工厂以后,设置bean工厂的环境
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                //发送了一个bean工厂的处理信号,紧接着回调处理器,注册到bean工厂
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                //初始化监听器
                this.initMessageSource();
                //初始化监听管理器
                this.initApplicationEventMulticaster();
                this.onRefresh();
                //把spring容器内的listener和beanfactory的listener都添加到广播器中:
                this.registerListeners();
                //实例化BeanFactory 中已经被注册但是没被实例化的所有实例。初始化的过程中各种BeanPostProcessor已经开始生效
                this.finishBeanFactoryInitialization(beanFactory);
                //收尾,刷新产生的缓存、初始化生命周期处理器LifecycleProcessor,并调用其onRefresh()方法、发布事件、调用LiveBeansView的registerApplicationContext注册context。
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }
step10 Spring上下文后置结束处理 afterRefresh

在刷新完context后,调用了一个afterRefresh函数,这个函数前面已经说过了,是为了给ApplicationContext的子类留下的一个扩展点

this.afterRefresh(context, applicationArguments);

然后调用了listeners.started(context);,把监听器设置成了已经启动的状态。
最后调用了callRunners函数,获取所有的ApplicationRunnerCommandLineRunner然后调用他们的run方法:

step11 发布应用上下文启动完成事件

把监听器设置成了已经启动的状态

listeners.started(context);
step12 执行所有Runner运行器

调用了callRunners函数,获取所有的ApplicationRunnerCommandLineRunner然后调用他们的run方法

step13 发布应用上下文就绪事件
listeners.running(context);

触发所有 SpringApplicationRunListener监听器的 running 事件方法。

step14 返回上下文

总结

本文分析了Spring Boot启动run方法,看得真的是让人头痛,但又不得不看,主要分为以下2步进行分析

  1. SpringApplication实例的构建过程
    其中主要涉及到了初始化器(Initializer)以及监听器(Listener)这两大概念,它们都通过META-INF/spring.factories完成定义。

  2. SpringApplication实例run方法的执行过程
    其中主要有一个SpringApplicationRunListeners的概念,它作为Spring Boot容器初始化时各阶段事件的中转器,将事件派发给感兴趣的Listeners(在SpringApplication实例的构建过程中得到的)。这些阶段性事件将容器的初始化过程给构造起来,提供了比较强大的可扩展性。

如果从可扩展性的角度出发,应用开发者可以在Spring Boot容器的启动阶段,扩展哪些内容呢:

  • 初始化器(Initializer)
  • 监听器(Listener)
  • 容器刷新后置Runners(ApplicationRunner或者CommandLineRunner接口的实现类)
  • 启动期间在Console打印Banner的具体实现类

作者有话说:喜欢的话就请移步知码学院,请自备水,更多干、干、干货等着你

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,724评论 6 342
  • 入门 介绍 Spring Boot Spring Boot 使您可以轻松地创建独立的、生产级的基于 Spring ...
    Hsinwong阅读 16,846评论 2 89
  • 信仰是什么,我们到底有没有信仰? 当我们似乎解决了温饱和免除生存忧虑之后,生命本身好像成了人的累赘。因为我们的生物...
    洪坤先生阅读 332评论 0 0
  • 文:喵星电影 小动物 “你可曾真正的年轻过?” “什么意思,姐现在还是美少女(美骚年)呢!” ...... 如果你...
    喵星电影阅读 898评论 0 2