Spring-boot之启动原理--源码分析+实现ApplicationContextInitializer和SpringApplicationRunListener

前言

本文针对Spring-boot的启动原理分析基于Spring-boot 2.1.4版本。主要分为两大模块,首先是从源代码入手进行分析,接着通过实现ApplicationContextInitializerSpringApplicationRunListener两个接口来更好的认识整个启动流程

一、启动

Spring-boot的启动我把它分为两个部分,一是创建SpringApplication对象,二是运行SpringApplication对象的run方法。即

//核心步骤
(new SpringApplication(primarySources)).run(args);
1、创建SpringApplication对象

启动环境

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>

        <groupId>org.springframework</groupId>

        <artifactId>spring-aop</artifactId>

    </dependency>

        <dependency>

            <groupId>org.aspectj</groupId>

            <artifactId>aspectjweaver</artifactId>

        </dependency>
</dependencies>

启动环境如上,只是一个普通的Spring-boot项目,引入了WEB和AOP模块而已。

开始
我们对启动主类进行Debug
起点
一直step into到
关键
到这里就是我们的关键点创建SpringApplication对象运行SpringApplication对象的run方法

创建SpringApplication对象
接着上面step into >> this((ResourceLoader)null, primarySources); >> public SpringApplication(ResourceLoader resourceLoader, Class… primarySources)
这个构建方法就是创建SpringApplication对象的关键构造。

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");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        //以上变量初始化完毕
		//重点:在这里从类路径下的“MAMATA_INF/spring.factories”文件中得到所要加载的所有ApplicationContextInitializer   
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        //同上:在这里从类路径下的“MAMATA_INF/spring.factories”文件中得到所要加载的所有ApplicationListener
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

在运行上面的构造方法后,SpringApplication对象就创建完成。重点注意this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));这两个方法,他们分别从项目的类路径下的MAMATA_INF/spring.factories中找到所有ApplicationContextInitializer和ApplicationListener,并把它们都加载到对象中。
;两个对象
可以看到SpringApplication对象中的Initializer有六个,Listener有10个,它们都是在spring.factories中事先写好的。分别是:
init
listener
大家可以去spring-boot-autoconfigure-2.1.4.RELEASE.jar包的META-INF\spring.factories中查看,看是不是里面的ApplicationContextInitializer和ApplicationListener都确确实实的加载进去了。

目前SpringApplication对象已经创建好了,接下来就是运行它的run方法了。

2、运行run方法

接下来是第二部分,上一步创建好的SpringApplication调用它的run方法

public ConfigurableApplicationContext run(String... args) {
		//创建一个stopwatch对象,用于记录整个应用跑起来的时间
        StopWatch stopWatch = new StopWatch();
        //开始记录
        stopWatch.start();
        //spring应用上下文
        ConfigurableApplicationContext context = null;
        //定义SpringApplication启动错误的回调接口
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        //设置headless,非重点
        this.configureHeadlessProperty();
        //从SpringFactoriesLoader的cache中获取所有的监听器
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        //重点:对所有的监听器回调他们的starting方法
        listeners.starting();

        Collection exceptionReporters;
        try {
        	//封装命令行参数
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //重点:准备配置环境,看这里的传入的参数,有listeners,说明这个方法肯定有用到监听器的地方
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            //打印spring的banner
            Banner printedBanner = this.printBanner(environment);
            //创建应用上下文
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            //重点:准备应用上下文,看有listeners传入,肯定会用到监听器,其中还会用到ApplicationContextInitializer
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //refresh上下文,这个时候把我们应用注册的组件装配到上下文中。
            this.refreshContext(context);
            //从ioc容器中获取所有的ApplicationRunner和CommandLineRunner进行回调
     	 	//ApplicationRunner先回调,CommandLineRunner再回调
            this.afterRefresh(context, applicationArguments);
            //记录时间停止
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
			//调用监听器的started方法
            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
        	//调用监听器的running方法
            listeners.running(context);
            //返回应用上下文,到这里spring-boot启动完成。
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

总结一下run方法的流程:
run
通过上面的分析我们已经大概了解清楚了spring-boot启动流程。在整个启动过程中我们一开始提过的ApplicationContextInitializer和ApplicationListener的身影时不时出现在其中。接下来我们自定义ApplicationContextInitializer和ApplicationListener,通过他们来更好的认识整个过程。

二、实现ApplicationContextInitializer和SpringApplicationRunListener

1、 实现ApplicationContextInitializer

ApplicationContextInitializer接口如下:

package org.springframework.context;

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

可以看到只有一个方法需要我们实现,就是initialize(C var1)方法。他会在run方法中准备上下文的时候(this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);)通过调用applyInitializers()方法被调用。

实现自己的ApplicationContextInitializer

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("MyApplicationContextInitializer 的initialize 方法 被调用了" );
    }
}
2、实现SpringApplicationRunListener

SpringApplicationRunListener接口如下:

package org.springframework.boot;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

public interface SpringApplicationRunListener {
    void starting();

    void environmentPrepared(ConfigurableEnvironment environment);

    void contextPrepared(ConfigurableApplicationContext context);

    void contextLoaded(ConfigurableApplicationContext context);

    void started(ConfigurableApplicationContext context);

    void running(ConfigurableApplicationContext context);

    void failed(ConfigurableApplicationContext context, Throwable exception);
}

看到这个接口是不是有一种恍然大悟的感觉。结合上面的启动流程,会发现SpringApplicationRunListener中的各个方法其实就是监听spring-boot启动的各个节点。
现在实现我们的SpringApplicationRunListener

import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

public class MySpringApplicationRunListener implements SpringApplicationRunListener {
	//必须要有一个带有SpringApplication application, String[] args参数的构造函数
    public MySpringApplicationRunListener(SpringApplication application, String[] args){

    }
    @Override
    public void starting() {
		System.out.println("MySpringApplicationRunListener 的 starting 方法被调用了>>>>应用开始初始化!");
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        System.out.println("MySpringApplicationRunListener 的 environmentPrepared 方法被调用了>>>>环境准备完毕!");
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener 的 contextPrepared 方法被调用了>>>>上下文准备完毕!");

    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener 的 contextLoaded 方法被调用了>>>>上下文装备完毕!");

    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener 的 started 方法被调用了>>>>应用已运行!");
    }

    @Override
    public void running(ConfigurableApplicationContext context) {
        System.out.println("MySpringApplicationRunListener 的 running 方法被调用了>>>>应用正在运行!");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("MySpringApplicationRunListener 的 sfailed 方法被调用了>>>>应用运行失败!");
    }
}
让自定义的ApplicationContextInitializer和SpringApplicationRunListener生效

前面说过,ApplicationContextInitializer和SpringApplicationRunListener都是通过类路径下的META-INF/spring.factories装配的。
所以我们需要在类路径下创建META-INF/spring.factories,让spring-boot装配我们的ApplicationContextInitializer和SpringApplicationRunListener
spring.factories如下:

org.springframework.context.ApplicationContextInitializer=\
yong.com.springaop.Listener.MyApplicationContextInitializer

org.springframework.boot.SpringApplicationRunListener=\
yong.com.springaop.Listener.MySpringApplicationRunListener
运行结果

可以看到控制台的打印
结果
可以看到我们自定义的ApplicationContextInitializer和SpringApplicationRunListener生效了。
如果我们愿意,也可以debug看一下

Initializer
在这里插入图片描述
对比前面的Initializer,会发现这里多出一个我们自定义的Initializer

对于listener,我们在调用 listeners.starting();的时候可以看到
listen
step into
into
可以看到调用了自定义listener的starting方法
mylisten
接着控制台输出
listen输出

以上便是通过实现ApplicationContextInitializer和SpringApplicationRunListener来参与到spring-boot的启动过程。到这里相信大家对spring-boot的原理已经有了初步的了解。

总结

spring-boot的启动在我看来可以简单分为两个大模块,分别是创建SpringApplication对象和调用SpringApplication对象的run方法。当然其中还有很多细节是没有全部说出来的,但是通过上面,相信我们已经能够对spring-boot的启动有了一个大致的了解。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值