hi,你好!欢迎访问本站!登录
本站由网站地图腾讯云宝塔系统阿里云强势驱动
当前位置:首页 - 教程 - 杂谈 - 正文 君子好学,自强不息!

SpringBoot是怎样启动的?这篇文章通知你答案!

2019-11-18杂谈搜奇网32°c
A+ A-

本文是经由历程检察SpringBoot源码整理出来的SpringBoot大抵启动流程,团体大方向是以简朴为起点,不说太多庞杂的东西,内部完成细节本文不深扣由于每一个人的思绪、明白都不一样,我个人看的明白跟人人看的一定不一样,到时候表达的出来的云里雾里也没啥用。

起首我将SpringBoot的启动流程整理成以下阶段:

  • SpringApplicaiton初始化
    • 检察ApplicationContext范例
    • 加载ApplicationContextInitializer
    • 加载ApplicationListener
  • Environment初始化
    • 剖析命令行参数
    • 竖立Environment
    • 设置Environment
    • 设置SpringApplication
  • ApplicationContext初始化
    • 竖立ApplicationContext
    • 设置ApplicationContext
    • 革新ApplicationContext
  • 运转顺序进口

省去了一些不影响主流程的细节,在检察SpringBoot源码之前,不得不提一下spring.factories这个文件的运用和功用。

关于spring.factories

spring.factories是一个properties文件,它位于classpath:/META-INF/目次内里,每一个jar包都能够有spring.factories的文件。Spring供应东西类SpringFactoriesLoader担任加载、剖析文件,如spring-boot-2.2.0.RELEASE.jar内里的META-INF目次内里就有spring.factories文件:

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

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

关于spring.factories须要晓得些什么?

  • spring.factories是一个properties文件
  • spring.factories里的键值对的value是以逗号分开的完整类名列表
  • spring.factories里的键值对的key是完整接口称号
  • spring.factories键值对的value是key的完成类
  • spring.factories是由SpringFactoriesLoader东西类加载
  • spring.factories位于classpath:/META-INF/目次
  • SpringFactoriesLoader会加载jar包内里的spring.factories文件并举行兼并

晓得spring.factories的观点后,继承来剖析SpringBoot的启动。

SpringApplicaiton初始化

Java顺序的进口在main要领SpringBoot的一样能够经由历程main要领启动,只须要少许的代码加上@SpringBootApplication注解,很轻易的就启动SpringBoot:

@SpringBootApplication
@Slf4j
public class SpringEnvApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringEnvApplication.class, args);
    }

}

SpringApplicaiton初始化位于SpringApplication的组织函数中:

    public SpringApplication(Class<?>... primarySources) {
        this(null, primarySources);
    }
 
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

简朴的说下SpringApplication的组织函数干了些啥:

  • 基础变量赋值(resourceLoader、primarySources、...)
  • 检察ApplicationContext范比方(Web、Reactive、Standard)
  • 加载ApplicationContextInitializer
  • 加载ApplicationListener
  • 检察启动类(main要领的类)

然后再来逐一剖析这些步骤。

检察ApplicationContext范例

SpringBoot会在初始化阶段检察ApplicationContext的范例,检察体式格局是经由历程罗列WebApplicationTypededuceFromClasspath静态要领:

    static WebApplicationType deduceFromClasspath() {
        if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
                && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        for (String className : SERVLET_INDICATOR_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

WebApplicationType罗列用于标记顺序是不是为Web顺序,它有三个值:

  • NONE:不是web顺序
  • SERVLET:基于Servlet的Web顺序
  • REACTIVE:基于Reactive的Web顺序

简朴的来讲该要领会经由历程classpath来推断是不是Web顺序,要领中的常量是完整的class类名:

private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

比方经由历程pom.xml文件引入spring-boot-starter-web那classpath就会有org.springframework.web.context.ConfigurableWebApplicationContextjavax.servlet.Servlet类,如许就决议了顺序的ApplicationContext范例为WebApplicationType.SERVLET

加载ApplicationContextInitializer

ApplicationContextInitializer会在革新context之前实行,平常用来做一些分外的初始化工程如:增加PropertySource、设置ContextId等事变它只要一个initialize要领:

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

SpringBoot经由历程SpringFactoriesLoader加载spring.factories中的设置读取key为org.springframework.context.ApplicationContextInitializer的value,前面提到过spring.factoies中的设置的value都为key的完成类:

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

上面列出的是spring-boot-2.2.0.RELEASE.jar中包含的设置,其他jar包也有能够设置org.springframework.context.ApplicationContextInitializer来完成分外的初始化事变。

加载ApplicationListener

ApplicationListener用于监听ApplicationEvent事宜,它的初始加载流程跟加载ApplicationContextInitializer类似,在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.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

ApplicationListener的加载流程跟ApplicationContextInitializer类似都是经由历程SpringFactoriesLoader加载的。

小结

完成初始化阶段后,能够晓得以下信息:

  • ApplicationContext是Web照样其他范例
  • SpringApplication中有一些ApplicationContextInitializer完成类
  • SpringApplication中有一些ApplicationListener的完成类

Environment初始化

初始化事变完成后SpringBoot会干许多事变来为运转顺序做好预备,SpringBoot启动中心代码大部分都位于SpringApplication实例的run要领中,在环境初始化大抵的启动流程包含:

  • 剖析命令行参数
  • 预备环境(Environment)
  • 设置环境

固然还会有一些别的操纵如:

  • 实例化SpringApplicationRunListeners
  • 打印Banner
  • 设置非常报告
  • ...

这些不是重要的操纵就不讲解了,能够看完文章再细细研讨。

剖析命令行参数

命令行参数是由main要领的args参数通报进来的,SpringBoot在预备阶段竖立一个DefaultApplicationArguments类用来剖析、保留命令行参数。如--spring.profiles.active=dev就会将SpringBoot的spring.profiles.active属性设置为dev。

public ConfigurableApplicationContext run(String... args) {
    ...
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); 
    ...
}

SpringBoot还会将收到的命令行参数放入到Environment中,供应一致的属性笼统。

竖立Environment

竖立环境的代码比较简朴,依据之前提到过的WebApplicationType来实例化差别的环境:

private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    switch (this.webApplicationType) {
    case SERVLET:
        return new StandardServletEnvironment();
    case REACTIVE:
        return new StandardReactiveWebEnvironment();
    default:
        return new StandardEnvironment();
    }
}

预备Environment

环境(Environment)大抵由Profile和PropertyResolver构成:

  • Profile是BeanDefinition的逻辑分组,定义Bean时能够指定Profile使SpringBoot在运转时会依据Bean的Profile决议是不是注册Bean
  • PropertyResolver是特地用来剖析属性的,SpringBoot会在启动时加载设置文件、体系变量等属性

SpringBoot在预备环境时会挪用SpringApplicationprepareEnvironment要领:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    ...
    return environment;
}

prepareEnvironment要领大抵完成以下事变:

  • 竖立一个环境
  • 设置环境
  • 设置SpringApplication的属性

设置Environment

竖立完环境后会为环境做一些简朴的设置:

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    if (this.addConversionService) {
        ConversionService conversionService = ApplicationConversionService.getSharedInstance();
        environment.setConversionService((ConfigurableConversionService) conversionService);
    }
    configurePropertySources(environment, args);
    configureProfiles(environment, args);
}

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
    
    if (this.addCommandLineProperties && args.length > 0) {
            ...
            sources.addFirst(new SimpleCommandLinePropertySource(args));
            ...
        }
    }
    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        environment.setActiveProfiles(StringUtils.toStringArray(profiles));
    }
    

篇幅有限省去一些不重要的代码,设置环境重要用于:

  • 设置ConversionService: 用于属性转换
  • 将命令行参数增加到环境中
  • 增加分外的ActiveProfiles

SpringApplicaton属性设置

设置SpringApplicaton重如果将已有的属性连接到SpringApplicaton实例,如spring.main.banner-mode属性就对应于bannerMode实例属性,这一步的属性泉源有三种(没有自定义的状况):

  • 环境变量
  • 命令行参数
  • JVM体系属性

SpringBoot会将前缀为spring.main的属性绑定到SpringApplicaton实例:

protected void bindToSpringApplication(ConfigurableEnvironment environment) {
    try {
        Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
    }
    catch (Exception ex) {
        throw new IllegalStateException("Cannot bind to SpringApplication", ex);
    }
}

Environment初始化小结

总结下环境预备阶段所做的大抵事变:

  • 依据WebApplicationType罗列竖立环境
  • 设置ConversionService用于转换属性变量
  • 将命令行参数args增加到环境
  • 将外部设置的Profiles增加到环境
  • 绑定SprinngApplicaiton属性
  • 发送环境Prepared事宜

ApplicationContext初始化

前面提到的一些步骤大部分都是为了预备ApplicationContext所做的事变,ApplicationContext供应加载Bean、加载资本、发送事宜等功用,SpringBoot在启动历程当中竖立、设置好ApplicationContext不须要开辟都作分外的事变(太轻易啦~~)。

本文不盘算深切ApplicationContext中,由于与ApplicationContext相干的类许多,不是一两篇文章写的完的,发起按模块来看,末了再整合起来看ApplicationContext源码

竖立ApplicationContext

竖立ApplicationContext的历程与竖立环境基础模类似,依据WebApplicationType推断顺序范例竖立差别的ApplicationContext

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
            case SERVLET:
                contextClass = Class.forName(DEFAULT_SERVLET_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);
}

前面提到过WebApplicationType有三个成员(SERVLET,REACTIVE,NONE),离别对应差别的context范例为:

  • SERVLET: AnnotationConfigServletWebServerApplicationContext
  • REACTIVE: AnnotationConfigReactiveWebServerApplicationContext
  • NONE: AnnotationConfigApplicationContext

预备ApplicationContext

竖立完ApplicationContext完后须要初始化下它,设置环境、运用ApplicationContextInitializer、注册Source类等,SpringBoot的预备Context的流程能够归纳以下:

  • ApplicationContext设置环境(之前竖立的环境)
  • 基础设置操纵设置BeanNameGenerator、ResourceLoader、ConversionService等
  • 实行ApplicationContextInitializerinitialize要领(ApplicationContextInitializer是在初始化阶段猎取的)
  • 注册命令行参数(springApplicationArguments)
  • 注册Banner(springBootBanner)
  • 注册sources(由@Configuration注解的类)

预备ApplicationContext的代码以下所示:

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    context.setEnvironment(environment);
    postProcessApplicationContext(context);
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // Add boot specific singleton beans
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    if (this.lazyInitialization) {
        context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
    }
    // Load the sources
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    load(context, sources.toArray(new Object[0]));
    listeners.contextLoaded(context);
}

注重注册sources这一步,sources是@Configuration注解的类SpringBoot依据供应的sources注册Bean,基础道理是经由历程剖析注解元数据,然后竖立BeanDefinition然后将它注册进ApplicationContext内里。

革新ApplicationContext

如果说SpringBoot的是个汽车,那前面所做的操纵都是开门、系安全带等基础操纵了,革新ApplicationContext就是点火了,没革新ApplicationContext只是保留了一个Bean的定义、后处理器啥的没有真正跑起来。革新ApplicationContext这个内容很重要,要明白ApplicationContext照样要看革新操纵的源码,
这里先简朴列一下基础步骤:

  • 预备革新(考证属性、设置监听器)
  • 初始化BeanFactory
  • 实行BeanFactoryPostProcessor
  • 注册BeanPostProcessor
  • 初始化MessageSource
  • 初始化事宜播送
  • 注册ApplicationListener
  • ...

革新流程步骤比较多,关联的类库都相对比较庞杂,发起先看完其他辅佐类库再来看革新源码,会事半功倍。

运转顺序进口

context革新完成后Spring容器能够完整运用了,接下来SpringBoot会实行ApplicationRunnerCommandLineRunner,这两接口功用类似都只要一个run要领只是吸收的参数差别而以。经由历程完成它们能够自定义启动模块,如启动dubbogRPC等。

ApplicationRunnerCommandLineRunner的挪用代码以下:

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);
    for (Object runner : new LinkedHashSet<>(runners)) {
        if (runner instanceof ApplicationRunner) {
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            callRunner((CommandLineRunner) runner, args);
        }
    }
}

callRunners实行完后,SpringBoot的启动流程就完成了。

总结

经由历程检察SpringApplication的源码,发明SpringBoot的启动源码还好明白,重要照样为ApplicationContext供应一个初始化的进口,免除开辟人员设置ApplicationContext的事变。SpringBoot的中心功用照样自动设置,下次剖析下SpringBoot Autoconfig的源码,要充足明白SpringBoot看源码是少了的。

看完SpringApplication的源码还有些题目值得思索:

  • SpringBoot是启动Tomcat的流程
  • SpringBoot自动设置道理
  • SpringBoot Starter自定义
  • BeanFactoryPostProcessor和BeanPostProcessor完成道理
  • ...





《架构文摘》天天一篇架构范畴重磅好文,触及一线互联网公司运用架构(高可用、高性 能、高稳固)、大数据、机械进修等各个热点范畴。

  选择打赏方式
微信赞助

打赏

QQ钱包

打赏

支付宝赞助

打赏

  移步手机端
SpringBoot是怎样启动的?这篇文章通知你答案!

1、打开你手机的二维码扫描APP
2、扫描左则的二维码
3、点击扫描获得的网址
4、可以在手机端阅读此文章
未定义标签

本文来源:搜奇网

本文地址:https://www.sou7.cn/282324.html

关注我们:微信搜索“搜奇网”添加我为好友

版权声明: 本文仅代表作者个人观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。请记住本站网址https://www.sou7.cn/搜奇网。

发表评论

选填

必填

必填

选填

请拖动滑块解锁
>>