SpringBoot启动过程

从 java main 方法启动

SpringApplication 构造方法

    @SuppressWarnings({ "unchecked", "rawtypes" })
    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 = deduceWebApplicationType(); // 推断 web 应用类型
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class)); // 获取 spring 工厂实例
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = deduceMainApplicationClass();
    }

getSpringFactoriesInstances 源码

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
        Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(
            SpringFactoriesLoader.loadFactoryNames(type, classLoader));            // 获取指定类型的工厂名字
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,    // 根据名字、类型创建工厂实例
            classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

从源码我们看出主要做了三件事:

  1. loadFactoryNames,加载指定类型的工厂名称
    loadSpringFactories:
    a. 查找类路径下全部的META-INF/spring.factories的URL
    b. 根据url加载全部的spring.factories中的属性
    c. 将所有spring.factories中的值缓存到 SpringFactoriesLoader 的 cache 中,方便下次调用
  2. createSpringFactoriesInstances,创建指定类型的工厂实例,根据上面获取的指定类型的工厂名称列表来实例化工厂 bean,我们可以简单的认为通过反射来实例化
  3. 对工厂实例进行排序,然后返回排序后的实例列表

构造总结:

  1. 构造自身实例
  2. 推测 web 应用类型,并赋值到属性 webApplicationType
  3. 设置属性 initializers 和 listeners 中途读取了类路径下所有 META-INF/spring.factories 的属性,并缓存到了 SpringFactoriesLoader 的 cache 缓存中
  4. 推断主类,并赋值到属性 mainApplicationClass

run 方法

/**
 * Run the Spring application, creating and refreshing a new
 * {@link ApplicationContext}.
 * @param args the application arguments (usually passed from a Java main method)
 * @return a running {@link ApplicationContext}
 */
public ConfigurableApplicationContext run(String... args) {
    // 秒表,用于记录启动时间;记录每个任务的时间,最后会输出每个任务的总费时
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // spring应用上下文,也就是我们所说的spring根容器
    ConfigurableApplicationContext context = null;
    // 自定义SpringApplication启动错误的回调接口
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 设置jdk系统属性java.awt.headless,默认情况为true即开启
    configureHeadlessProperty();
    //  KEY 1 - 获取启动时监听器
    SpringApplicationRunListeners listeners = getRunListeners(args)
    // 触发启动事件,启动监听器会被调用,一共5个监听器被调用
    listeners.starting(); 
    try {
        // 参数封装,也就是在命令行下启动应用带的参数,如--server.port=9000
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        // KEY 2 - 准备环境 1、加载外部化配置的资源到environment;2、触发ApplicationEnvironmentPreparedEvent事件
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        // 配置spring.beaninfo.ignore,并添加到名叫systemProperties的PropertySource中;默认为true即开启
        configureIgnoreBeanInfo(environment);
        // 打印banner图
        Banner printedBanner = printBanner(environment);
        // KEY 3 - 创建应用上下文,并实例化了其三个属性:reader、scanner和beanFactory
        context = createApplicationContext();
        // 获取异常报道器,即加载spring.factories中的SpringBootExceptionReporter实现类
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        //  KEY 4 - 准备上下文前置处理
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        // KEY 5 - Spring上下文刷新
        refreshContext(context);
        // KEY 6 - Spring上下文后置处理
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        listeners.started(context);
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
}

getRunListeners() 方法:返回一个新的SpringApplicationRunListeners实例对象
细看的话,这次是从SpringFactoriesLoader的cache中取SpringApplicationRunListener类型的类(全限定名),然后实例化后返回。说的简单点,getRunListeners就是准备好了运行时监听器EventPublishingRunListener。
listeners.starting() 方法:构建了一个ApplicationStartingEvent事件,并将其发布出去,对每个listener进行invokeListener,调用过滤出的监听器


prepareEnvironment() 方法:

// 准备环境
private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    // Create and configure the environment 创建和配置环境
    // 获取或创建环境
    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;
}

configureEnvironment() 方法:将配置任务按顺序委托给 configurePropertySources 和 configureProfiles

configurePropertySources:注释说明是增加、移除或者重排序应用环境中的 PropertySource。就目前而言,如果有命令行参数则新增封装命令行参数的PropertySource,并将它放到sources的第一位置。

configureProfiles:配置应用环境中的哪些配置文件处于激活状态(或默认激活)。可以通过spring.profiles.active属性在配置文件处理期间激活其他配置文件。说的简单点就是设置哪些Profiles是激活的。

listeners.environmentPrepared(environment):这其中会初始化 PropertiesPropertySourceLoader 和 YamlPropertySourceLoader 这两个加载器从 file:./config/,file:./,classpath:/config/,classpath:/ 路径下加载配置文件,PropertiesPropertySourceLoader 加载配置文件 application.xml 和application.properties,YamlPropertySourceLoader 加载配置文件 application.yml 和application.yaml。目前我们之后 classpath:/ 路径下有个 application.yml 配置文件,将其属性配置封装进了一个名叫 applicationConfig:[classpath:/application.yml] 的 OriginTrackedMapPropertySource 中,并将此对象放到了 propertySourceList 的最后。

environmentPrepared 方法会触发所有监听了 ApplicationEnvironmentPreparedEvent 事件的监听器,加载外部化配置资源到 environment,包括命令行参数、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties) 等;

bindToSpringApplication(environment):就是将 environment 绑定到 SpringApplication


createApplicationContext:根据SpringApplication的webApplicationType来实例化对应的上下文,对其部分属性:reader、scanner、beanFactory进行了实例化;reader中实例化了属性conditionEvaluator;scanner中添加了两个AnnotationTypeFilter:一个针对@Component,一个针对@ManagedBean;beanFactory中注册了8个注解配置处理器。


prepareContext:
1、将 context 中的 environment 替换成 SpringApplication 中创建的 environment
2、将SpringApplication中的 initializers 应用到 context 中
设置application id,并将application id封装成ContextId对象,注册到beanFactory中
向context的beanFactoryPostProcessors中注册了一个ConfigurationWarningsPostProcessor实例
向context的applicationListeners中注册了一个ServerPortInfoApplicationContextInitializer实例
向context的beanFactoryPostProcessors中注册了一个CachingMetadataReaderFactoryPostProcessor 实例
向context的applicationListeners中注册了一个ConditionEvaluationReportListener实例
3、加载两个单例bean到beanFactory中
向beanFactory中注册了一个名叫springApplicationArguments的单例bean,该bean封装了我们的命令行参数;
向beanFactory中注册了一个名叫springBootBanner的单例bean。
4、加载bean定义资源
5、将 SpringApplication 中的 listeners 注册到 context 中,并广播 ApplicationPreparedEvent 事件
总共11个 ApplicationListener 注册到了 context 的 applicationListeners 中;
ApplicationPreparedEvent 事件的监听器一共做了两件事:
+ 向 context 的 beanFactoryPostProcessors 中注册了一个 PropertySourceOrderingPostProcessor 实例
+ 向 beanFactory 中注册了一个名叫 springBootLoggingSystem 的单例 bean,也就是我们的日志系统 bean
context 中主要是三个属性增加了内容:beanFactory、beanFactoryPostProcessors 和 applicationListeners


load:就是加载 bean 定义资源,支持4种方式:Class、Resource、Package和CharSequence。
Class:注解形式的 Bean 定义;AnnotatedBeanDefinitionReader 负责处理。
Resource:一般而言指的是 xml bean 配置文件,也就是我们在 spring 中常用的 xml 配置。说的简单点就是:将 xml 的 bean 定义封装成 BeanDefinition 并注册到 beanFactory 的 BeanDefinitionMap 中;XmlBeanDefinitionReader 负责处理。
Package:以扫包的方式扫描bean定义; ClassPathBeanDefinitionScanner 负责处理。
CharSequence:以先后顺序进行匹配 Class、Resource 或 Package 进行加载,谁匹配上了就用谁的处理方式处理。

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

推荐阅读更多精彩内容