springboot系列文章之SpringApplication详解

本文详细分析了SpringApplication的初始化过程,包括推断应用类型、加载ApplicationContextInitializer和ApplicationListener、设置main方法所在类。接着,深入探讨了SpringApplication的run方法,涵盖Headless模式设置、加载SpringApplicationRunListeners、配置环境模块、创建ApplicationContext等关键步骤。通过本文,可以理解SpringBoot启动的完整流程。

前言

还是从SpringBoot的启动类说起,这篇文章主要分析启动类中的SpringApplication

@SpringBootApplication
public class Application {


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

可以看出main函数中重要的就是SpringApplication.run(),这可以分为两部分来探讨:
- SpringApplication的构造过程
- SpringApplication的run()方法

SpringApplication的初始化

首先进入SpringApplication的构造函数,先是单个参数的构造方法,后进入两个参数的构造方法,ResourceLoader是Spring的资源加载器,这里没有自定义的ResourceLoader传入,所以为NULL,而primarySources参数就是我们传入的Application.class启动类

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));
        //1. 推断应用类型
        this.webApplicationType = deduceWebApplicationType();
        //2. initializer初始化模块,加载ApplicationContextInitializer
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        //3. 加载监听器
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        //4. 配置应用main方法所在的类
        this.mainApplicationClass = deduceMainApplicationClass();
    }

SpringApplication的初始化主要包括以下4个步骤:
- 推断应用类型
- 加载初始化构造器ApplicationContextInitializer
- 创建应用监听器
- 设置应用main()方法所在的类

1. 推断应用类型

this.webEnvironment=deduceWebApplicationType(); 判断应用的类型,是否是servlet应用还是reactive应用或者是none,webEnvironment中定义了这三种类型
webApplication
web

2. 加载初始化构造器ApplicationContextInitializer

setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class)): 通过SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer
init
进入loadFactoryNames方法,然后进入loadSpringFactories方法,获取当前ClassLoader下的所有META-INF/spring.factories文件的配置信息

而后通过loadSpringFactories(classloader).getOrDefault(factoryClassName,Collections.emptyList()) 从所有META-INF/spring.factories文件的配置信息的map中获取指定的factory的值
laod
spring
默认情况下,从 spring.factories 文件找出的 key 为 ApplicationContextInitializer 的类有如上图中所示4种

对于 ApplicationContextInitializer,它是应用程序初始化器,做一些初始化工作

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

    /**
     * Initialize the given application context.
     * @param applicationContext the application to configure
     */
    void initialize(C applicationContext);

}

3. 创建应用监听器

setListeners()方法与setInitializers()方法类似,只不过它是使用SpringFactoriesLoader在应用的classpath的META-INT/spring.factories中查找并加载所有可用的ApplicationListener

        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

a
ApplicationListener,应用程序事件(ApplicationEvent)监听器:

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

更详细的分析可以参阅我之前的文章: springboot系列文章之启动时初始化数据

4. 设置应用main()方法所在的类

在SpringApplication构造函数的最后一步,根据调用栈推断并设置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;
    }

SpringApplication的run方法

SpringApplication实例初始化完成并且完成设置后,就可以开始run方法的逻辑了,对于这个run方法我将分为以下几点进行逐步剖析,而StopWatch是一个工具类,主要是方便记录程序运行时间,这里就不仔细介绍了。

    public ConfigurableApplicationContext run(String... args) {
            //构造一个任务执行观察期
        StopWatch stopWatch = new
        StopWatch();
        //开始执行,记录开始时间
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //1
        configureHeadlessProperty();
        //2
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {

             //3
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            //4
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            //5
            configureIgnoreBeanInfo(environment);
            //6
            Banner printedBanner = printBanner(environment);
            //7
            context = createApplicationContext();
            //8
            exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
            //9
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            //10
            refreshContext(context);
            //2.0版本中是空实现
            //11
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            //12
            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;
    }

SpringApplication的run方法主要分为以下几步:
- Headless模式设置
- *加载SpringApplicationRunListeners监听器*
- 封装ApplicationArguments对象
- 配置环境模块
- 根据环境信息配置要忽略的bean信息
- Banner配置SpringBoot彩蛋
- 创建ApplicationContext应用上下文
- 加载SpringBootExceptionReporter
- ApplicationContext基本属性配置
- 更新应用上下文
- 查找是否注册有CommandLineRunner/ApplicationRunner

1. Headless模式设置

configureHeadlessProperty()设置 headless 模式,即设置系统属性java.awt.headless,它是J2SE的一种模式,用于在缺少显示屏,键盘,或者鼠标时的系统配置,该属性会被设置为true,更多的信息可以参考这里

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

2. 加载SpringApplicationRunListeners监听器

        SpringApplicationRunListeners listeners = getRunListeners(args);

getRunListeners(args)也是通过 SpringFactoriesLoaderMETA-INF/spring.factories查找到并加载的SpringApplicationRunListener。该类实际上是监听SpringApplication的run方法的执行

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
                SpringApplicationRunListener.class, types, this, args));
    }

    .....
    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
        //通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListner
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

这里的SpringApplicationRunListener监听器与SpringApplication时加载的ApplicationListener监听器不同,SpringApplicationRunListener是SpringBoot新增的类,SpringApplicationRunListener目前只有一个实现类EventPublishingRunListener。虽然说是新增的, 但是它们之间是有联系的,它们之间的的关系是通过ApplicationEventMulticaster广播出去的SpringApplicationEvent所联系起来的
startup

更详细的分析请参阅 :SpringBoot源码分析之SpringBoot的启动过程

3. 封装ApplicationArguments对象

将args参数封装成 ApplicationArguments 对象

    public DefaultApplicationArguments(String[] args) {
        Assert.notNull(args, "Args must not be null");
        this.source = new Source(args);
        this.args = args;
    }

官网对 ApplicationArguments 的解释如下
args

4. 配置环境模块

根据listenersapplicationArguments 创建并配置当前SpringBoot应用将要使用的Enviroment

    private ConfigurableEnvironment prepareEnvironment(
            SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (this.webApplicationType == WebApplicationType.NONE) {
            environment = new EnvironmentConverter(getClassLoader())
                    .convertToStandardEnvironmentIfNecessary(environment);
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

遍历调用所有SpringApplicationRunListener的enviromentPrepared()方法就是宣告当前SpringBoot应用使用的Enviroment准备好了

5. 根据环境信息配置要忽略的bean信息

    private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
        if (System.getProperty(
                CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
            Boolean ignore = environment.getProperty("spring.beaninfo.ignore",
                    Boolean.class, Boolean.TRUE);
            System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
                    ignore.toString());
        }
    }

6. Banner配置SpringBoot彩蛋

打印banner标志,就是启动SpringBoot项目时出现的Spring字样,当然我们也可以自定义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);
    }

7. 创建ApplicationContext应用上下文

createApplicationContext()根据用户是否明确设置了applicationContextClass类型以及SpringApplication初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成。

public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
        + "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
        + "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
        "org.springframework.web.context.ConfigurableWebApplicationContext" };  


protected ConfigurableApplicationContext createApplicationContext() {
    //用户是否明确设置了applicationContextClass,在SpringApplication中有对应的setter方法
    Class<?> contextClass = this.applicationContextClass;
        //如果没有主动设置
    if (contextClass == null) {
        try {
            //判断当前应用的类型,也就是之前SpringApplication初始化阶段的推断结果
            switch (this.webApplicationType) {
            //servlet应用程序
            case SERVLET: 
                contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
                break;
            //reactive响应式程序
            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);
}

在SpringBoot官网对ApplicationContext的类型是如下定义的:
Applicaiton
- 当SpringMVC存在的时候,就使用AnnotationConfigServletWebServerApplicationContext
- 当SpringMVC不存在的时候,Spring WebFlux响应式存在的时候,使用AnnotationConfigReactiveWebServerApplicationContext
- 如果以上都不是,默认就用AnnotationConfigApplicationContext
- SpringApplication存在设置ApplicationContext的方法,在JUnit测试中使用SpringApplication通常要设置ApplicationContext

8. 加载SpringBootExceptionReporter

exceptionReporters = getSpringFactoriesInstances(
                    SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);
    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;
    }

这里也是通过SpringFactoriesLoader加载META-INF/spring.factories中key为SpringBootExceptionReporter的全类名的value值

  • SpringBootExceptionReporter是一个回调接口,用于支持对SpringApplication启动错误的自定义报告。里面就一个报告启动失败的方法
  • 其实现类:org.springframework.boot.diagnostics.FailureAnalyzers
    用于触发从spring.factories加载的FailureAnalyzerFailureAnalysisReporter实例

9. ApplicationContext基本属性配置

    private void prepareContext(ConfigurableApplicationContext context,
            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments, Banner printedBanner) {
        //设置应用的环境
        context.setEnvironment(environment);
        //对 context 进行了预设置
        postProcessApplicationContext(context);
        applyInitializers(context);
        遍历调用SpringApplicationRunListener的contextPrepared()方法,通告SpringBoot应用使用的ApplicationContext准备好了
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }

        // Add boot specific singleton beans
        context.getBeanFactory().registerSingleton("springApplicationArguments",
                applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }

        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[0]));
        //遍历调用SpringApplicationRunListener的contextLoaded()方法,通告ApplicationContext装填完毕
        listeners.contextLoaded(context);
    }
1). applyInitializers(context);
    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);
        }
    }

遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理

2). load(ApplicationContext context, Object[] sources)
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();
    }

设置资源加载器,加载各种beans到ApplicationContext对象中

10. 更新应用上下文

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

进入内部的refresh()方法,准备环境所需的bean工厂,通过工厂产生环境所需的bean,重点就是产生bean

    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

11. afterRefresh()

上下文刷新后调用该方法,目前没有操作

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

12. callRunner()

    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);
            }
        }
    }

查找当前的ApplicationContext中是否注册有CommandLineRunner或者ApplicationRunner,如果有,就遍历执行他们。

SpringBoot启动流程总结

上面从SpringApplication的初始化到SpringApplication.run()方法执行,基本上按照其内部函数调用的顺序一步一步分析下来,内容非常多,很容易把人搞晕。在网上发现一张图,图出自SpringBoot启动流程解析,画的比较清楚明白,把SpringBoot启动整个流程都包含进来了
appl
再总结下run方法中最关键的几步:
- 加载SpringApplicationRunListeners监听器
- 配置环境模块
- 创建ApplicationContext应用上下文
- ApplicationContext基本属性配置
- 更新应用上下文,产生环境所需要的bean

小结

上面的分析都是基于SpringBoot2.0版本,在之前的版本,内容上可能有些偏差,大体思路是差不多的。在阅读源码的过程中,看了很多前人分析的博客文章,也借鉴了他们的分析流程,有点「前人栽树,后人乘凉」的感觉,现在「取之网络,再回馈之网络」

参考资料 & 鸣谢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值