Spring源码解析----SpringBoot启动流程

一个简单的SB程序如下,点击main方法左边的原谅色的三角形就能把程序启动起来,虽然什么功能都没有,但是启动做了很多处理,加载了很多支持多种功能的组件(类似使用new ClassPathXmlApplicationContext()启动一个Spring程序,其中加载了很多东西)

@SpringBootApplication
public class SpringBootDemoApplication {

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

}

分析的入口就是从SpringApplication的run方法开始,而@SpringBootApplication注解在后续的处理中会用到,所以暂时忽略

1.入口

进入run方法

    public static ConfigurableApplicationContext run(Class<?> primarySource,
            String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }

    public static ConfigurableApplicationContext run(Class<?>[] primarySources,
            String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

2.初始化SpringApplication

首先初始化了SpringApplication,然后再调用其run方法,那么先看下SpringApplication的构造方法里的逻辑

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        // 即demo里的SpringBootDemoApplication对应的Class对象集合
        this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
        // 应用的类型,共三种类型
        // reactive、servlet  、非web应用
        this.webApplicationType = deduceWebApplicationType();
        // 获取spring.factories中配置的ApplicationContextInitializer的实现类并实例化,最后放到集合当中
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        // 原理同上
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 找到执行main方法的那个类的Class对象,即demo中的SpringBootDemoApplication对应的Class对象
        this.mainApplicationClass = deduceMainApplicationClass();
    }

2.1获取应用类型

SpringApplication构造方法里,调用了deduceWebApplicationType来判断是什么类型的应用,那么SpringBoot是如何判断的呢?往里进去看下具体实现

    private WebApplicationType deduceWebApplicationType() {
        //REACTIVE_WEB_ENVIRONMENT_CLASS = org.springframework.web.reactive.DispatcherHandler
        //MVC_WEB_ENVIRONMENT_CLASS = org.springframework.web.servlet.DispatcherServlet
        if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
                && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
            return WebApplicationType.REACTIVE;
        }
        //WEB_ENVIRONMENT_CLASSES=javax.servlet.Servlet
        // 或者org.springframework.web.context.ConfigurableWebApplicationContext
        for (String className : WEB_ENVIRONMENT_CLASSES) {
            if (!ClassUtils.isPresent(className, null)) {
                return WebApplicationType.NONE;
            }
        }
        return WebApplicationType.SERVLET;
    }

ClassUtils.isPresent方法中尝试加载传入的类,如果加载成功,则返回true,如果失败则返回false,SpringBoot使用这种方式在判断当前是什么类型的应用。

从假设我们应用中什么依赖都没有加入,那么WEB_ENVIRONMENT_CLASSESREACTIVE_WEB_ENVIRONMENT_CLASS或者MVC_WEB_ENVIRONMENT_CLASS都是加载失败的,最终返回WebApplicationType.NONE

从另一个方面来说,如果我们想构建一个SpringMVC的web应用,那么只需要加入如下依赖:

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

此时,SpringBoot会帮我们间接依赖SpringMVC所需要的包,那么就间接引入了org.springframework.web.context.ConfigurableWebApplicationContext这个包,由于SpringMVC底层是基于Servlet,那么会引入javax.servlet.Servlet相关的包。加了上面这一个依赖之后,deduceWebApplicationType方法就会返回WebApplicationType.SERVLET。

后续SpringBoot会根据deduceWebApplicationType的返回类型,来做不同的初始化,这也算是SpringBoot自动化的其中一个体现。

2.2 Spring的扩展机制

在构造方法中,会初始化initializers和listeners两个几个,里面的对应的类型分别是ApplicationContextInitializerApplicationListener类型的实例。

  • ApplicationContextInitializer:用于上下文刷新前(refreshContext)的回调处理
  • ApplicationListener:用于事件的通知

这两个集合都是通过getSpringFactoriesInstances方法去加载的,而核心的实现就是Spring的扩展机制。

SpringBoot会基于这个机制,会提供一些系统内置的类(分别实现ApplicationContextInitializer和ApplicationListener),让系统自动的调用对应的实现,而这些内置类的加载则是通过getSpringFactoriesInstances方法实现的:

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
            Class<?>[] parameterTypes, Object... args) {
        //获取类加载器
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // 获取对应的类名
        Set<String> names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        //实例化
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

SpringFactoriesLoader就是扩展机制的核心实现,其大概逻辑就是,查找META-INF文件下的spring.factories文件,通过对应类型获取配置好的类名,以ApplicationContextInitializer为例,引入的jar包中,其下面的spring.factories文件如下:

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

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

getSpringFactoriesInstances(ApplicationContextInitializer.class)得到的是的是上面6个类的实例对象集合,ApplicationListener就不再分析

基于此,我们也可以在工程的对应路径下加入spring.factories文件,并用上面的规则,加入自己的实现来扩展对应的功能

3.SpringApplication.run方法

当SpringApplication构造完成,就进入到run方法,进行真正的初始化步骤

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //设置awt相关属性
        configureHeadlessProperty();
        // 通过扩展机制获取SpringApplicationRunListener的实现类,并封装成SpringApplicationRunListeners对象
        // 目前只有一个实现EventPublishingRunListener
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 发布ApplicationStartingEvent事件
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            //环境准备
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            //设置需要忽略的Bean信息
            configureIgnoreBeanInfo(environment);
            Banner printedBanner = printBanner(environment);
            //根据应用类型创建对应的ApplicationContext
            context = createApplicationContext();
            //获取错误报告处理器
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            //刷新上下文前的准备工作
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            //刷新上下文,即Spring的核心流程,IOC,AOP,Bean初始化等流程
            refreshContext(context);
            //刷新完成后置处理
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            //发布ApplicationStartedEvent事件
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
                //....
        }
                //发布ApplicationReadyEvent事件
        listeners.running(context);
        return context;
    }

下面对核心步骤进行分析

3.1 环境准备

环境主要指的是Environment,Environment主要包括两部分功能:

  • profile
  • properties

其中profile是Environment自身的功能,而properties相关功能则是由Environment继承的接口PropertyResolver提供

prepareEnvironment方法会构建一个ConfigurableEnvironment实例,并对其进行初始化

    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // 根据应用类型返回不同的实例
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // properties和profile相关处理
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        //发布ApplicationEnvironmentPreparedEvent事件
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (this.webApplicationType == WebApplicationType.NONE) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

先看下getOrCreateEnvironment方法

    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {
            return this.environment;
        }
        if (this.webApplicationType == WebApplicationType.SERVLET) {
            return new StandardServletEnvironment();
        }
        return new StandardEnvironment();
    }

由于是Servlet的web应用,则返回StandardServletEnvironment实例,其他情况则返回StandardEnvironment(StandardEnvironment是StandardServletEnvironment的父类)。这里为什么Servlet应用需要特殊处理呢?

前面说过Environment包括properties和profile,我们知道Servlet的properties来源和其他类型的应用相比,还有ServletContext里的参数,那么Servlet应用需要另外的解析工作(基础解析工作由StandardEnvironment完成),而这个工作需要由StandardServletEnvironment特殊处理。

在其父类AbstractEnvironment的构造方法中,会调用到customizePropertySources方法,这个方法由子类重写来自定义解析策略(实际是加入处理的PropertySource),StandardServletEnvironmentcustomizePropertySources方法如下:

    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
        propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
            propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
        }
        super.customizePropertySources(propertySources);
    }

这里看到该方法中加入两个PropertySource用于Servlet特有的属性解析,然后再调用父类StandardEnvironment的方法保证基础的属性解析也没有问题,看下StandardEnvironment的方法

    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

这里加入了两个PropertySource用于解析其他属性,即System.getProperties()和System.getEnv()两个方法所附带的属性。

  • 至于PropertySource所涉及的属性解析原理,这里不再展开,后续会另起文章详细分析属性解析原理。

环境Environment的准备中,主要是为属性解析和profile做了准备工作,例如添加了PropertySource待后续参数获取使用

3.2 创建对应的ApplicationContext

创建上下文主要交由createApplicationContext处理

    public static final String DEFAULT_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 {
                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);
    }

逻辑比较简单,根据应用类型创建不同的上下文对象

3.3 刷新上下文前的准备工作

该工作交由prepareContext处理:

    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        context.setEnvironment(environment);
        // 后置处理,注册BeanNameGenerator和设置ResourceLoader
        postProcessApplicationContext(context);
        // 遍历initializers集合内的ApplicationContextInitializer实例进行调用
        applyInitializers(context);
        // 发布事件,目前看到的实现为空
        listeners.contextPrepared(context);
        // 将之前封装的参数对象注册到容器中
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }
        // 即上文提到的primarySources
        Set<Object> sources = getAllSources();
        // 将我们的启动类注册到容器中      
        load(context, sources.toArray(new Object[0]));
        //发布ApplicationPreparedEvent事件
        listeners.contextLoaded(context);
    }

3.3.1 postProcessApplicationContext

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
        if (this.beanNameGenerator != null) {
            context.getBeanFactory().registerSingleton(
                    AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                    this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            if (context instanceof GenericApplicationContext) {
                ((GenericApplicationContext) context)
                        .setResourceLoader(this.resourceLoader);
            }
            if (context instanceof DefaultResourceLoader) {
                ((DefaultResourceLoader) context)
                        .setClassLoader(this.resourceLoader.getClassLoader());
            }
        }
    }

代码逻辑很简单,但是该方法中beanNameGeneratorresourceLoader都为空,所以这个地方是Springboot留的一个扩展点,可以将自定义的设置进去

3.3.2 applyInitializers

    protected void applyInitializers(ConfigurableApplicationContext context) {
        for (ApplicationContextInitializer initializer : getInitializers()) {
            Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                    initializer.getClass(), ApplicationContextInitializer.class);
            Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
            initializer.initialize(context);
        }
    }

在前面的分析中,Spring会从spring.factories中获取ApplicationContextInitializer的实现并加入到集合当中,而这里就是真正调用的地方,具体的每个实现后续在相关功能中进行分析

3.3.3 load

    protected void load(ApplicationContext context, Object[] sources) {
        if (logger.isDebugEnabled()) {
            logger.debug(
                    "Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
        }
        BeanDefinitionLoader loader = createBeanDefinitionLoader(
                getBeanDefinitionRegistry(context), sources);
        if (this.beanNameGenerator != null) {
            loader.setBeanNameGenerator(this.beanNameGenerator);
        }
        if (this.resourceLoader != null) {
            loader.setResourceLoader(this.resourceLoader);
        }
        if (this.environment != null) {
            loader.setEnvironment(this.environment);
        }
        loader.load();
    }

将启动类封装成一个BeanDefinitionLoader,然后设置beanNameGenerator、resourceLoader、environment,最后调用load方法,将启动类注册到容器当中。

主要是创建了一个AnnotatedGenericBeanDefinition,然后使用BeanDefinitionRegistry,以beanName为key,将其注册到容器中。

这里涉及了Spring的知识,就不再详细展开。

3.4 刷新上下文

    private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            }
            catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }

refresh为Spring的核心流程,即org.springframework.context.support.AbstractApplicationContext#refresh,进行bean的创建等等一些核心流程。

另外会注册一个核心关闭钩子。

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

推荐阅读更多精彩内容