Spring Boot源码分析-启动原理

1.简介

Spring Boot是由Pivotal团队提供的快速开发框架,基于SpringMVC经过注解+内置Http服务器如:tomcat-embed-core,简化了XML配置,快速将一些经常使用的第三方依赖整合(经过Maven继承依赖关系),最终实现以Java应用程序的方式进行执行。java

1.1 SpringBoot起源

  • Spring框架:Spring框架从早期的IOC与AOP衍生出了不少产品例如Spring (boot、security、jpa)等等
  • SpringMVC框架:Spring MVC提供了一种轻度耦合的方式来开发web应用,它是Spring的一个web框架。经过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等,是基于Spring的一个 MVC 框架。
  • SpringBoot框架:Spring Boot实现了自动配置,下降了项目搭建的复杂度。它主要是为了解决使用Spring框架须要进行大量的配置太麻烦的问题,因此它并非用来替代Spring的解决方案,而是和Spring框架紧密结合用于提高Spring开发者体验的工具。同时它集成了大量经常使用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),是基于Spring4的条件注册的一套快速开发整合包。

图片描述

1.2 便捷的starter poms (启动器)

starter包含了搭建项目快速运行所需的依赖。它是一个依赖关系描述符的集合。当应用须要一种spring的其它服务时,不须要粘贴拷贝大量的依赖关系描述符。例如想在spring中使用redis,只须要在项目中包含 spring-boot-starter-redis 依赖就可使用了,全部的starters遵循一个类似的命名模式:spring-boot-starter-,在这里是一种特殊类型的应用程序。该命名结构能够帮你找到须要的starter。不少IDEs集成的Maven容许你经过名称搜索依赖。react

1.3 demo

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

这是一个简单的SpringBoot的启动类实现,下文章将围绕这个demo进行扩展分析。web

1.4 SpringBoot启动流程

clipboard.png

2 @SpringBootApplication注解分析

如下是注解@SpringBootApplication的源码实现redis

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

能够发现它是由众多注解组合而成的,下面具体分析下这里每一个注解所起到的做用。spring

  • @Target Target经过ElementType来指定注解可以使用范围的枚举集合(FIELD/METHOD/PARAMETER...)
  • @Retention Retention(保留)注解说明,这种类型的注解会被保留到那个阶段. 有三个值:tomcat

    • RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
    • RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
    • RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,因此他们能在运行时被JVM或其余使
  • @Documented 注解代表这个注解应该被 javadoc工具记录. 默认状况下,javadoc是不包括注解的. 但若是声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 因此注解类型信息也会被包括在生成的文档中
  • @Inherited 容许子类继承父类的注解,仅限于类注解有用,对于方法和属性无效。
  • @SpringBootConfiguration 注解实际上和@Configuration有相同的做用,配备了该注解的类就可以以JavaConfig的方式完成一些配置,能够再也不使用XML配置。
  • @ComponentScan 这个注解完成的是自动扫描的功能,至关于Spring XML配置文件中的:<context:component-scan>,可以使用basePackages属性指定要扫描的包,及扫描的条件。若是不设置则默认扫描@ComponentScan注解所在类的同级类和同级目录下的全部类,因此咱们的Spring Boot项目,通常会把入口类放在顶层目录中,这样就可以保证源码目录下的全部类都可以被扫描到。
  • @EnableAutoConfiguration 这个注解是让Spring Boot的配置可以如此简化的关键性注解。我把EnableAutoConfiguration的实现端上来了,你们来鉴赏一下!服务器

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    • @AutoConfigurationPackage 注解用于保存自动配置类以供以后的使用,好比给JPA entity扫描器,用来扫描开发人员经过注解@Entity定义的entity类。通俗的讲就是,注册bean定义到容器中。
    • @Import(AutoConfigurationImportSelector.class)是EnableAutoConfiguration注解中最关键的来,它借助AutoConfigurationImportSelector,能够帮助SpringBoot应用将全部符合条件的@Configuration配置都加载到当前SpringBoot建立并使用的IoC容器中。关于@Import注解要说的内容还比较多,改天再聊。

关于注解的话题就先谈到这里,下面开启撸代码环节。并发

3 剖析代码

查看SpringApplication的源代码能够发现SpringApplication的启动由两部分组成:app

  • new SpringApplication(primarySources):建立SpringApplication对象
  • run(args):调用run方法

3.1 实例化SpringApplication对象

源码以下:框架

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;//一、初始化资源加载器
        Assert.notNull(primarySources, "PrimarySources must not be null");//二、断言资源加载类不能为 null
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));//三、初始化加载资源类集合并去重
        this.webApplicationType = deduceWebApplicationType();//四、 推断应用类型是Standard仍是Web
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));//五、设置应用上下文初始化器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//六、设置监听器 
        this.mainApplicationClass = deduceMainApplicationClass();//七、推断应用入口类
    }

下面将针对源码中的重要实现进行详细的分析。

3.1.1 初始化资源加载器

ResourceLoader接口,在 Spring 中用于加载资源,经过它能够获取一个Resouce 对象。使用spring的朋友都知道它加载资源的方式由多种,下面就挑两个经常使用的继承ResourceLoader的接口与实现类提及。

  • DefaultResourceLoader : 做为 ResourceLoader 接口的直接实现类,该类实现了基本的资源加载功能,能够实现对单个资源的加载。
  • ResourcePatternResolver :该接口继承了 ResourceLoader,定义了加载多个资源的方法, 能够实现对多个资源的加载。
一、DefaultResourceLoader

上面介绍过该类经过实现 ResourceLoader 接口实现了加载单个资源的功能。它的子类经过继承它来实现具体的资源访问策略。下面来探究下该类如何加载单个资源:

public Resource getResource(String location) {//这里是三种识别location加载出Resource的方式。
        Assert.notNull(location, "Location must not be null");
        //1.先看有没有自定义的ProtocolResolver,若是有则先根据自定义的ProtocolResolver解析location获得Resource
        for (ProtocolResolver protocolResolver : this.protocolResolvers) {
            Resource resource = protocolResolver.resolve(location, this);
            if (resource != null) {
                return resource;
            }
        }
        //2.根据路径是否匹配"/"或"classpath:"来解析获得ClassPathResource
        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }else if (location.startsWith(CLASSPATH_URL_PREFIX)) {//classpath
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }else {
            try {
                //默认传入的location是一个URL路径,加载获得一个UrlResource
                URL url = new URL(location);
                return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
            }
            catch (MalformedURLException ex) {
                // 若是以上三种状况都不知足,则按照“/”来处理
                return getResourceByPath(location);
            }
        }
    }

扫盲:ProtocolResolver是解析location的自定义拓展类,有了它咱们才能随意传入不一样格式的location,而后根据对应的格式去解析并得到咱们的Resource便可。
扩展:在Spring容器初始化过程当中,咱们能够自定义一个类实现ProtocolResolver接口,而后实现该resolve方法,就能够解析特定的location获得Resoure。

二、ResourcePatternResolver

该接口继承了ResourceLoader接口,在其基础上增长了同时对多个资源的访问功能。

public interface ResourcePatternResolver extends ResourceLoader {
    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

    // 例如使用 ant 风格的路径,匹配路径下的多个资源
    Resource[] getResources(String locationPattern) throws IOException;
}

PathMatchingResourcePatternResolver是 ResourcePatternResolver 接口的直接实现类,它是基于模式匹配的,默认使用AntPathMatcher 进行路径匹配,它除了支持 ResourceLoader 支持的前缀外,还额外支持 “classpath*” ,下面查看源码看看它如何实现对多个资源的访问。

public Resource[] getResources(String locationPattern) throws IOException {
        Assert.notNull(locationPattern, "Location pattern must not be null");
         //首先判断资源路径是不是类路径下的资源(以 “classpath*:” 开头)
        if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
            // 经过 getPathMatcher 方法取得 PathMatcher ,默认只有 AntPathMatcher 一个实现类
           // 经过 isPattern 方法判断路径是否容许存在多个匹配的资源(路径中包含 “*” 或 “?”)
            if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
                //找到全部匹配路径(ant 风格)的资源
                return findPathMatchingResources(locationPattern);
            }
            else {
                //经过类加载器查找
                return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
            }
        }
        else {
            int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
                    locationPattern.indexOf(':') + 1);
            // 判断资源路径 ":" 以后的部分是否包含 "*" 或 "?"
            if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
                // a file pattern
                return findPathMatchingResources(locationPattern);
            }
            else {
                // 若不存在表示是单个资源,则经过从构造函数传入的 ResourceLoader 取得
                return new Resource[] {getResourceLoader().getResource(locationPattern)};
            }
        }
    }

扩展:查看源码发现ApplicationContext接口也继承了 ResourcePatternResolver 接口,说明它也集成了对对单个或多个资源的访问功能。

  • 当 Spring 须要进行资源访问时,实际上并不须要直接使用 Resource 实现类,而是调用 getResource 方法来获取资源。
  • 当经过 ApplicationContext 实例获取 Resource 实例时,它将会负责选择具体的 Resource 的实现类。代码以下:
    Resource res = ctx.getResource("some/resource/path/myTemplate.txt);

Spring采用和 ApplicationContext 相同的策略来访问资源。也就是说:

  • 若是 ApplicationContext 是 FileSystemXmlApplicationContext,res 就是 FileSystemResource 实例;
  • 若是 ApplicationContext 是 ClassPathXmlApplicationContext,res 就是 ClassPathResource 实例;
  • 若是 ApplicationContext 是 XmlWebApplicationContext,res 是 ServletContextResource 实例。

也就是说 ApplicationContext 将会肯定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来,这就体现了策略模式的优点。

3.1.2 设置应用上下文初始化器setInitializers

  • 介绍:initializers是SpringApplication中的一个实例属性:List<ApplicationContextInitializer<?>> initializers,每个initailizer都是一个实现了ApplicationContextInitializer接口的实例。ApplicationContextInitializer是Spring IOC容器中提供的一个接口,源码以下:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext>{
        void initialize(C applicationContext);
    }
  • 做用:ApplicationContextInitializer接口的做用就是在spring prepareContext的时候作一些初始化工做,在spring 初始化的过程当中 执行prepareContext方法的时候里面会经过applyInitializers方法回调全部ApplicationContextInitializer接口的实现。因此在SpringApplication的构造方法中执行了setInitializers方法,该方法是把初始化的ApplicationContextInitializer实现类所有加载到SpringApplication内部的集合中。
  • 实现:这些初始化器(initializers)是Spring Boot从本地的META-INF/spring.factories文件和jar文件中的META-INF/spring.factories 文件获取配置的初始化类(注意这边是获取了配置文件的所有配置类),而后经过loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());

}方法得到ApplicationContextInitializer接口所有实现类的完整名称,最后经过反射的机制得到ApplicationContextInitializer实现类。

  • 使用:经过getSpringFactoriesInstances(

ApplicationContextInitializer.class)方法得到实现类

3.1.3 设置监听器(setListeners)

listeners成员变量,是一个ApplicationListener<?>类型对象的集合。能够看到获取该成员变量内容使用的是跟成员变量initializers同样的方法,只不过传入的类型从ApplicationContextInitializer.class变成了ApplicationListener.class。

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

这个接口基于JDK中的EventListener接口,实践了观察者模式。对于Spring框架的观察者模式实现,它限定感兴趣的事件类型须要是ApplicationEvent类型的子类,而这个类一样是继承自JDK中的EventObject类。

3.1.4 推断应用入口类

该方法经过构造一个运行时异常,经过异常栈中方法名为main的栈帧来获得main()所在类的名字。

private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }

3.2 run方法

源码以下:

public ConfigurableApplicationContext run(String... args) {
    //一、计时监控类
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    
    // 二、初始化应用上下文和异常报告集合
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    
    // 三、设置系统属性java.awt.headless的值,默认值为:true(没有图形化界面)
    configureHeadlessProperty();
    
    // 四、建立全部 Spring 运行监听器并发出开始执行的事件
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        // 五、初始化默认应用参数类
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
                
        // 六、根据SpringApplicationRunListeners和应用参数来准备 Spring 环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        
        // 七、准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
        Banner printedBanner = printBanner(environment);
        
        // 八、建立Spring上下文
        context = createApplicationContext();
        
        // 九、准备异常报告器
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
                
        // 十、Spring上下文前置处理
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
                
        // 十一、刷新Spring上下文
        refreshContext(context);
        
        // 十二、Spring上下文后置处理
        afterRefresh(context, applicationArguments);
        
        // 1三、中止计时监控类
        stopWatch.stop();
        
        // 1四、输出日志记录执行主类名、时间信息
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        
        // 1五、发布应用上下文启动完成事件
        listeners.started(context);
        
        // 1六、执行全部 Runner 运行器
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }
 
    try {
        // 1七、发布应用上下文就绪事件
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    // 1八、返回应用上下文
    return context;
}

下面针对run方法中提到的一些重要步骤进行阐述:

3.2.1 计时监控类

启动方法以下:

public void start() throws IllegalStateException {
        start("");
    }
    
    public void start(String taskName) throws IllegalStateException {
        if (this.currentTaskName != null) {
            throw new IllegalStateException("Can't start StopWatch: it's already running");
        }
        this.currentTaskName = taskName;
        this.startTimeMillis = System.currentTimeMillis();
    }

你能够看到它传入了一个空字符串给当前任务做为任务名称,而后记录当前Spring Boot应用启动的开始时间。而且它会判断当前任务名是否存在,用于保证Spring Boot应用不重复启动。

3.2.2 初始化应用上下文和异常报告集合

这里仅初始化一个应用上下文对象context和一个空的异常报告集合,具体用途看下文。

3.2.3 设置系统属性

configureHeadlessProperty的方法实现以下:

private void configureHeadlessProperty() {
        System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
                SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
    }
    private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

该方法设置了一个名为java.awt.headless的系统属性,观其源码能够发现它给属性设值System.setProperty(),而它的值来源于System.getProperty(),这并不奇怪,由于System中getProperty()有2个重载方法,其中getProperty()有单参和双参两个方法,这里调用的是双参数方法,该方法在没有的时候会返回一个调用者指定的默认值,因此这里先获取后设置。这个设置的目的是保证即便没有检测到显示器(服务器不须要显示器),也容许程序启动。

3.2.4 建立全部 Spring 运行监听器并发出开始执行的事件

实现以下:

//根据args获取全部SpringApplicationRunListeners监听器
SpringApplicationRunListeners listeners = getRunListeners(args);

//这段代码你们应该已经熟悉了,获取SpringApplicationRunListeners扩展
private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

经过Class<?>[] types = new Class<?>[]{SpringApplication.class, String[].class};类型加载对应的监听器,并建立SpringApplicationRunListener实例

下面的内容和以前实例化初始化器的流程是同样,经过getSpringFactoriesInstances方法从spring.factories中获取与SpringApplicationRunListener.class相关的实例类名列表。

3.2.5 初始化默认应用参数类

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

args是启动Spring应用的命令行参数,该参数能够在Spring应用中被访问。
如:--server.port=9000

3.2.6 根据运行监听器和应用参数来准备 Spring 环境

建立并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile(其做用就是指定激活的配置文件,能够区分环境来加载不一样的配置)),并遍历调用全部的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
源码以下:

private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        //获取或建立环境(存在就直接返回,不存在建立一个再返回)
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        //配置环境:配置PropertySources和activeProfiles
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        //listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)。
        listeners.environmentPrepared(environment);
        //将环境绑定到SpringApplication
        bindToSpringApplication(environment);
        //若是非web环境,将环境转换成StandardEnvironment
        if (this.webApplicationType == WebApplicationType.NONE) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        //配置PropertySources对它本身的递归依赖
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

prepareEnvironment的做用:加载外部化配置资源到environment,包括命令行参数、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties)等;初始化日志系统。

3.2.7 准备Banner打印器

该功能仅供自娱自乐,不作过多解读,看源码即是。

private Banner printBanner(ConfigurableEnvironment environment) {
        if (this.bannerMode == Banner.Mode.OFF) {
            return null;
        }
        ResourceLoader resourceLoader = (this.resourceLoader != null ? this.resourceLoader
                : new DefaultResourceLoader(getClassLoader()));
        SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(
                resourceLoader, this.banner);
        if (this.bannerMode == Mode.LOG) {
            return bannerPrinter.print(environment, this.mainApplicationClass, logger);
        }
        return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
    }

3.2.8 建立Spring上下文

源码以下:

public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context.annotation.AnnotationConfigApplicationContext";

protected ConfigurableApplicationContext createApplicationContext() {
        // 先判断有没有指定的实现类
        Class<?> contextClass = this.applicationContextClass;
        // 若是没有,则根据应用类型选择
        if (contextClass == null) {
            try {
                //根据webApplicationType的类型去反射建立ConfigurableApplicationContext的具体实例。
                switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            }
            catch (ClassNotFoundException ex) {
                throw new IllegalStateException(
                        "Unable create a default ApplicationContext, "
                                + "please specify an ApplicationContextClass",
                        ex);
            }
        }
        // 经过反射获取对应类的实例
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

createApplicationContext()该方法的逻辑较为简单,共两个分支:

  • 自定义ApplicationContext的实现类
  • 根据当前应用的类型webApplicationType来匹配对应的ApplicationContext,是servlet、reactive或者非web应用

3.2.9 准备异常报告器

这一步的逻辑和实例化初始化器和监听器的同样,都是经过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化全部的异常处理类。

3.2.10 Spring上下文前置处理

源码以下:

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);
        }
 
        // 添加特定于引导的单例bean
context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }
        // 加载全部资源
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        //加载咱们的启动类,将启动类注入容器
        load(context, sources.toArray(new Object[0]));
 
        //触发全部 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
        listeners.contextLoaded(context);
    }

这块会对整个上下文进行一个预处理,好比触发监听器的响应事件、加载资源、设置上下文环境等等。

3.2.11 刷新Spring上下文

源码以下:

private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                //向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }

这块主要作了两件事:

  1. 经过refresh方法对整个IoC容器的初始化(包括Bean资源的定位、解析、注册等等)
  2. 经过context.registerShutdownHook()(向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文,除非它当时已经关闭。)

3.2.12 Spring上下文后置处理

protected void afterRefresh(ConfigurableApplicationContext context,
        ApplicationArguments args) {
}

该方法没有实现,能够根据须要作一些定制化的操做。

3.2.13 中止计时监控类

源码以下:

public void stop() throws IllegalStateException {
        if (this.currentTaskName == null) {
            throw new IllegalStateException("Can't stop StopWatch: it's not running");
        }
        long lastTime = System.currentTimeMillis() - this.startTimeMillis;
        this.totalTimeMillis += lastTime;
        this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
        if (this.keepTaskList) {
            this.taskList.add(this.lastTaskInfo);
        }
        ++this.taskCount;
        this.currentTaskName = null;
    }

该方法主要是作计时监听器中止操做,并统计一些任务执行信息。

3.2.14 输出日志记录执行主类名、时间信息

源码以下:

if (this.logStartupInfo) {
    new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
}

用于打印主类信息和时间信息。

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

源码以下:

public void started(ConfigurableApplicationContext context) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.started(context);
    }
}

执行全部SpringApplicationRunListener实现的started方法。

3.2.16 执行全部 Runner 运行器

Runner 运行器用于在服务启动时进行一些业务初始化操做,这些操做只在服务启动后执行一次。
Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口CommandLineRunner、ApplicationRunner
对比

  • 相同点

    • 二者均在服务启动完成后执行,而且只执行一次。
    • 二者都能获取到应用的命令行参数。
    • 二者在执行时机上是一致的(能够经过Ordered相关的接口或注解来实现自定义执行优先级。)。
  • 不一样点

    • 虽然二者都是获取到应用的命令行参数,可是ApplicationRunner获取到的是封装后的ApplicationArguments对象,而CommandLine获取到的是ApplicationArguments中的sourceArgs属性(List<String>),即原始参数字符串列表(命令行参数列表)。

源码以下:

private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<>();
// 从Spring容器中获取ApplicationRunner实现类        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 从Spring容器中获取CommandLineRunner实现类        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        //排序
        AnnotationAwareOrderComparator.sort(runners);
        //回调
        for (Object runner : new LinkedHashSet<>(runners)) {
            if (runner instanceof ApplicationRunner) {
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }

ApplicationRunner仍是CommandLineRunner,都是在应用启动完成后执行一次业务初始化代码,达到的效果也相似,因为ApplicationRunner的方法参数是ApplicationArguments对象,使用起来更加方便,因此更推荐使用。

3.2.17 发布应用上下文就绪事件

源码以下

public void running(ConfigurableApplicationContext context) {
        for (SpringApplicationRunListener listener : this.listeners) {
            listener.running(context);
        }
    }

触发全部 SpringApplicationRunListener 监听器的 running 事件方法。

总结

SpirngBoot启动流程到这里就分析完了、接下来的文章会针对SpringBoot的特定场景进行分析,但愿每一个看到这篇文章的朋友都能有所收获,同时也欢迎你们多提宝贵意见共同成长。
相关文章
相关标签/搜索