Spring Boot学习笔记03--深入了解SpringBoot的启动过程

摘要

看完本文你将掌握如下知识点:

  1. SpringApplication的作用及运行过程
  2. SpringBootServletInitializer的作用及运行过程

PS:本节内容略显枯燥,如果对SpringBoot的启动过程不感兴趣,可以略过。


SpringBoot系列Spring Boot学习笔记


深入了解SpringApplication

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

这就是SpringBoot的启动入口,通过前面的学习我们大体上了解了@SpringBootApplication的作用,接下来我们来认识一下SpringApplication
SpringApplication (Spring Boot Docs 1.4.2.RELEASE API)

SpringApplication.run(SpringBootWebDemoApplication.class, args);

通过源码我们来看一下SpringApplication.run()方法的执行过程
1.调用static方法

//1
public static ConfigurableApplicationContext run(Object source, String... args) {return run(new Object[]{source}, args);}
public static ConfigurableApplicationContext run(Object[] sources, String[] args) 
{return (new SpringApplication(sources)).run(args);}

2.创建SpringApplication对象

//2
public SpringApplication(Object... sources) {
        this.bannerMode = Mode.CONSOLE; //banner的打印模式,此时是控制台模式
        this.logStartupInfo = true; //开启日志
        this.addCommandLineProperties = true;//启用CommandLineProperties
        this.headless = true;//开启headless模式支持
        this.registerShutdownHook = true;//启用注册ShutdownHook,用于在非Web应用中关闭IoC容器和资源
        this.additionalProfiles = new HashSet();
        this.initialize(sources);//初始化
    }

PS:Headless参考资料:在 Java SE 平台上使用 Headless 模式

3.初始化相关对象和属性

//3
private void initialize(Object[] sources) {
        if(sources != null && sources.length > 0) {
            this.sources.addAll(Arrays.asList(sources));
        }
        //3.1判断是否是web运行环境,如果classpath中是否含有**WEB_ENVIRONMENT_CLASSES**指定的全部类,则返回true
        this.webEnvironment = this.deduceWebEnvironment();
        //3.2找到*META-INF/spring.factories*中声明的所有ApplicationContextInitializer的实现类并将其实例化
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //3.3找到*META-INF/spring.factories*中声明的所有ApplicationListener的实现类并将其实例化
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        //3.4获得当前执行main方法的类对象,这里就是SpringBootWebDemoApplication的实例
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

3.1 判断是否是web运行环境
如果classpath中是否含有WEB_ENVIRONMENT_CLASSES指定的全部类,则返回true,用于创建指定类型的ApplicationContext对象。

//3.1
private static final String[] WEB_ENVIRONMENT_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};

3.2 大体的过程就是通过SpringFactoriesLoader检索META-INF/spring.factories,找到声明的所有ApplicationContextInitializer的实现类并将其实例化。
ApplicationContextInitializer是Spring框架中的接口,其作用可以理解为在ApplicationContext执行refresh之前,调用ApplicationContextInitializer的initialize()方法,对ApplicationContext做进一步的设置和处理。

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    void initialize(C var1);
}

spring-boot-1.4.2.RELEASE.jar中的META-INF/spring.factories包含的ApplicationContextInitializer

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer

spring-boot-autoconfigure-1.4.2.RELEASE.jar中的META-INF/spring.factories包含的ApplicationContextInitializer

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer

3.3 大体的过程就是通过SpringFactoriesLoader检索META-INF/spring.factories,找到声明的所有ApplicationListener的实现类并将其实例化。
ApplicationListener是Spring框架中的接口,就是事件监听器,其作用可以理解为在SpringApplicationRunListener发布通知事件时,由ApplicationListener负责接收。

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E var1);
}

SpringBoot只提供了一个SpringApplicationRunListener的实现类,就是EventPublishingRunListener,起作用就是在SpringBoot启动过程中,负责注册ApplicationListener监听器,在不同的时点发布不同的事件类型,如果有哪些ApplicationListener的实现类监听了这些事件,则可以接收并处理。

public interface SpringApplicationRunListener {
    //通知监听器,SpringBoot开始执行
    void started();
    //通知监听器,Environment准备完成
    void environmentPrepared(ConfigurableEnvironment var1);
    //通知监听器,ApplicationContext已经创建并初始化完成
    void contextPrepared(ConfigurableApplicationContext var1);
    //通知监听器,ApplicationContext已经完成IoC配置加载
    void contextLoaded(ConfigurableApplicationContext var1);
    //通知监听器,SpringBoot启动完成
    void finished(ConfigurableApplicationContext var1, Throwable var2);
}

spring-boot-1.4.2.RELEASE.jar中的META-INF/spring.factories包含的ApplicationListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.logging.LoggingApplicationListener

spring-boot-autoconfigure-1.4.2.RELEASE.jar中的META-INF/spring.factories包含的ApplicationListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

spring-boot-1.4.2.RELEASE.jar中的META-INF/spring.factories包含的SpringApplicationRunListener

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

3.4 获得当前执行main方法的类对象,这里就是SpringBootWebDemoApplication的实例。

4.核心方法

//4
public ConfigurableApplicationContext run(String... args) {
        //开启任务执行时间监听器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        ConfigurableApplicationContext context = null;
        Object analyzers = null;

        //设置系统属性『java.awt.headless』,为true则启用headless模式支持
        this.configureHeadlessProperty();

        //通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,
        //找到声明的所有SpringApplicationRunListener的实现类并将其实例化,
        //之后逐个调用其started()方法,广播SpringBoot要开始执行了。
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.started();

        try {
            DefaultApplicationArguments ex = new DefaultApplicationArguments(args);

            //创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),
            //并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, ex);

            //决定是否打印Banner
            Banner printedBanner = this.printBanner(environment);

            //根据webEnvironment的值来决定创建何种类型的ApplicationContext对象
            //如果是web环境,则创建org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext
            //否则创建org.springframework.context.annotation.AnnotationConfigApplicationContext
            context = this.createApplicationContext();

            //注册异常分析器
            new FailureAnalyzers(context);

            //为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,
            //并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,
            //之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成,
            //这里就包括通过**@EnableAutoConfiguration**导入的各种自动配置类。
            this.prepareContext(context, environment, listeners, ex, printedBanner);

            //初始化所有自动配置类,调用ApplicationContext的refresh()方法
            this.refreshContext(context);

            //遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
            //该过程可以理解为是SpringBoot完成ApplicationContext初始化前的最后一步工作,
            //我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
            this.afterRefresh(context, ex);

            //调用所有的SpringApplicationRunListener的finished()方法,广播SpringBoot已经完成了ApplicationContext初始化的全部过程。
            listeners.finished(context, (Throwable)null);

            //关闭任务执行时间监听器
            stopWatch.stop();
            //如果开启日志,则答应执行是时间
            if(this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            return context;
        } catch (Throwable var9) {
            //调用异常分析器打印报告,调用所有的SpringApplicationRunListener的finished()方法将异常信息发布出去
            this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
            throw new IllegalStateException(var9);
        }
    }

spring-boot-1.4.2.RELEASE.jar中的META-INF/spring.factories包含的FailureAnalyzerFailureAnalysisReporters

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer

# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

说明

  • SpringBoot的启动过程,实际上就是对ApplicationContext的初始化过程。
  • ApplicationContext创建后立刻为其设置Environmen,并由ApplicationContextInitializer对其进一步封装。
  • 通过SpringApplicationRunListener在ApplicationContext初始化过程中各个时点发布各种广播事件,并由ApplicationListener负责接收广播事件。
  • 初始化过程中完成IoC的注入,包括通过@EnableAutoConfiguration导入的各种自动配置类。
  • 初始化完成前调用ApplicationRunner和CommandLineRunner的实现类。

扩展SpringApplication

通过上面的学习,我们基本上了解了,如果要对SpringApplication进行扩展,我们可以选择如下三种方案:

  • 创建ApplicationContextInitializer的实现类
  • 创建ApplicationListener的实现类
  • 创建ApplicationRunner和CommandLineRunner的实现类

1.可以通过如下方式加载自定义的ApplicationContextInitializerApplicationListener

@SpringBootApplication
public class SpringBootWebDemoApplication {
    public static void main(String[] args) {
        //SpringApplication.run(SpringBootWebDemoApplication.class, args);
        SpringApplication springApplication = new SpringApplication(SpringBootWebDemoApplication.class);

        springApplication.addInitializers(MyApplicationContextInitializer1,MyApplicationContextInitializer2);

        springApplication.addListeners(MyApplicationListener1,MyApplicationListener2);

        springApplication.run(args);
    }
}

2.也可以在当前项目的类路径下创建META-INF/spring.factories文件,并声明相应的ApplicationContextInitializerApplicationListener

org.springframework.context.ApplicationContextInitializer=\
xxx.xxx.MyApplicationContextInitializer1,\
xxx.xxx.MyApplicationContextInitializer2

# Application Listeners
org.springframework.context.ApplicationListener=\
xxx.xxx.MyApplicationListener1,\
xxx.xxx.MyApplicationListener2

3.至于ApplicationRunner和CommandLineRunner,只需要在其实现类上加上@Component注解或者在@Configuration配置类中通过@Bean注解注入。


深入了解SpringBootServletInitializer

熟悉了SpringApplication的原理之后,我们再来了解SpringBootServletInitializer的原理就比较容易了。

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(DemoWarApplication.class);
    }
}

SpringBootServletInitializer就是一个org.springframework.web.context.WebApplicationContext,容器启动时会调用其onStartup(ServletContext servletContext)方法,接下来我么就来看一下这个方法:

public void onStartup(ServletContext servletContext) throws ServletException {
        this.logger = LogFactory.getLog(this.getClass());
        final WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
        if(rootAppContext != null) {
            servletContext.addListener(new ContextLoaderListener(rootAppContext) {
                public void contextInitialized(ServletContextEvent event) {
                }
            });
        } else {
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }

    }

这里的核心方法就是createRootApplicationContext(servletContext):

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {

        //创建SpringApplicationBuilder,并用其生产出SpringApplication对象
        SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
        builder.main(this.getClass());

        ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
        if(parent != null) {
            this.logger.info("Root context already created (using as parent).");
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
            builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
        }

        //初始化并封装SpringApplicationBuilder对象,为SpringApplication对象增加ApplicationContextInitializer和ApplicationListener做准备
        builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
        builder.listeners(new ApplicationListener[]{new ServletContextApplicationListener(servletContext)});
        //指定创建的ApplicationContext类型
        builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);

        //传递入口类,并构建SpringApplication对象
        //可以通过configure()方法对SpringBootServletInitializer进行扩展
        builder = this.configure(builder);
        SpringApplication application = builder.build();

        if(application.getSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
            application.getSources().add(this.getClass());
        }

        Assert.state(!application.getSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
        if(this.registerErrorPageFilter) {
            application.getSources().add(ErrorPageFilter.class);
        }

        //最后调用SpringApplication的run方法
        return this.run(application);
    }

说明
SpringBootServletInitializer的执行过程,简单来说就是通过SpringApplicationBuilder构建并封装SpringApplication对象,并最终调用SpringApplication的run方法的过程。


扩展SpringBootServletInitializer

与扩展SpringApplication类似,ApplicationContextInitializerApplicationListener可以基于SpringApplicationBuilder提供的public方法进行扩展

public class ServletInitializer extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        application.initializers(MyApplicationContextInitializer1,MyApplicationContextInitializer2);
        application.listeners(MyApplicationListener1,MyApplicationListener2)
        return application.sources(DemoWarApplication.class);
    }

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

推荐阅读更多精彩内容