深入理解 SpringBoot 启动机制——初始化流程(run方法)

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 深入理解 SpringBoot 启动机制——初始化流程(run方法)

点击上方“后端技术精选”,选择“置顶公众号”

技术文章第一时间送达!

作者:MuBug juejin.im/post/5d32fca5518825477162ab5e

一、前言

在上一篇,我们主要介绍了,注解@SpringbootApplication的自动化配置原理,那么首先我们先回顾一下,这个注解主要为我们的sprngboot工程做了什么。

我们可以将自动配置的关键几步以及相应的注解总结如下:

  • **@Configuration&与@Bean:**基于java代码的bean配置
  • **@Conditional:**设置自动配置条件依赖
  • **@EnableConfigurationProperties与@ConfigurationProperties:**读取配置文件转换为bean。
  • **@EnableAutoConfiguration、@AutoConfigurationPackage与@Import:**实现bean发现与加载。
  • @Conditional:设置自动配置条件依赖

    @EnableAutoConfiguration、@AutoConfigurationPackage与@Import:实现bean发现与加载。

    今天,我们来通过源码来分析一下它的启动过程.

    本篇基于 2.0.4.RELEASE 版本进行分析,阅读本文需要有一些 Java 和 Spring 框架基础,如果还不知道 Spring Boot 是什么,建议先看下官网的 Spring Boot 教程。

    二、Spring Boot 入口类

    深入理解 SpringBoot 启动机制:初始化流程(run方法)

    上面是 Spring Boot 最简单通用的入口类。入口类的要求是最顶层包下面第一个含有 main 方法的类,使用注解 @SpringBootApplication 来启用 Spring Boot 特性,使用 SpringApplication.run 方法来启动 。

    首先来看一下这个类里面run方法的调用源码:

    深入理解 SpringBoot 启动机制:初始化流程(run方法)

    第一个参数 primarySource:加载的主要资源类;

    第二个参数 args:传递给应用的应用参数。

    先用主要资源类来实例化一个 SpringApplication 对象,再调用这个对象的 run 方法,所以我们分两步来。

    三、SpringApplication 的实例化过程

    深入理解 SpringBoot 启动机制:初始化流程(run方法)

    跟着上面的run进入到下面的方法:

    深入理解 SpringBoot 启动机制:初始化流程(run方法)

    进入SpringApplication可以看到以下源码:

    深入理解 SpringBoot 启动机制:初始化流程(run方法)

    从上面的源码可以知道,整个实例化过程有7个步骤:

    1.资源初始化资源加载器为 null

    
    this.resourceLoader = resourceLoader;
    

    2.断言主要加载资源类不能为 null,否则报错

    
    Assert.notNull(primarySources, "PrimarySources must not be null");
    

    3.初始化主要加载资源类集合并去重

    
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    

    4.推断当前 WEB 应用类型

    
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    

    这里进去WebApplicationType方法看一下源码以及相关构造方法:

    
    public enum WebApplicationType {
    
        /**
         * The application should not run as a web application and should not start an
         * embedded web server.
         */
        NONE,
    
        /**
         * The application should run as a servlet-based web application and should start an
         * embedded servlet web server.
         */
    
        SERVLET,
    
        /**
         * The application should run as a reactive web application and should start an
         * embedded reactive web server.
         */
        REACTIVE;
    
        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";
    
        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;
        }
    
        static WebApplicationType deduceFromApplicationContext(Class? applicationContextClass) {
            if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
                return WebApplicationType.SERVLET;
            }
            if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
                return WebApplicationType.REACTIVE;
            }
            return WebApplicationType.NONE;
        }
    
        private static boolean isAssignable(String target, Class? type) {
            try {
                return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
            }
            catch (Throwable ex) {
                return false;
            }
        }
    
    }
    

    这个就是根据类路径下是否有对应项目类型的类推断出不同的应用类型,这里也说明 。

    5.设置应用上下文初始化器

    
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    

    进入ApplicationContextInitializer,我们可以知道其作用:

    
    public interface ApplicationContextInitializerC extends ConfigurableApplicationContext {
    
        /**
         * Initialize the given application context.
         * @param applicationContext the application to configure
         */
        void initialize(C applicationContext);
    
    }
    

    用来初始化指定的 Spring 应用上下文,如注册属性资源、激活 Profiles 等。

    再来看下 setInitializers 方法源码,其实就是初始化一个 ApplicationContextInitializer 应用上下文初始化器实例的集合。

    
        public void setInitializers(Collection? extends ApplicationContextInitializer? initializers) {
            this.initializers = new ArrayList();
            this.initializers.addAll(initializers);
        }
    

    最后我们来看一下核心方法getSpringFactoriesInstances 其源码如下:

    
        private T CollectionT getSpringFactoriesInstances(ClassT type, Class?[] parameterTypes, Object... args) {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            // Use names and ensure unique to protect against duplicates
            SetString names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
            List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
            AnnotationAwareOrderComparator.sort(instances);
            return instances;
        }
    

    设置应用上下文初始化器可分为以下 5 个步骤。

    这里是实例化的核心:

    5.1) 获取当前线程上下文类加载器

    
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    

    5.2) 获取 ApplicationContextInitializer 的实例名称集合并去重

    
    SetString names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    

    loadFactoryNames 的源码如下:

    
        public static ListString loadFactoryNames(Class? factoryClass, @Nullable ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
        }
    
        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
        private static MapString, ListString loadSpringFactories(@Nullable ClassLoader classLoader) {
            MultiValueMapString, String result = cache.get(classLoader);
            if (result != null) {
                return result;
            }
    
            try {
                EnumerationURL urls = (classLoader != null ?
                        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
                result = new LinkedMultiValueMap();
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    for (Map.Entry?, ? entry : properties.entrySet()) {
                        String factoryClassName = ((String) entry.getKey()).trim();
                        for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            }
            catch (IOException ex) {
                throw new IllegalArgumentException("Unable to load factories from location [" +
                        FACTORIES_RESOURCE_LOCATION + "]", ex);
            }
        }
    

    根据类路径下的 META-INF/spring.factories 文件解析并获取 ApplicationContextInitializer 接口的所有配置的类路径名称。

    spring-boot-autoconfigure-2.0.4.RELEASE.jar!/META-INF/spring.factories 的初始化器相关配置内容如下:

    
    # Initializers
    org.springframework.context.ApplicationContextInitializer=
    org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,
    org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
    
    # Application Listeners
    org.springframework.context.ApplicationListener=
    org.springframework.boot.autoconfigure.BackgroundPreinitializer
    
    # Auto Configuration Import Listeners
    org.springframework.boot.autoconfigure.AutoConfigurationImportListener=
    org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
    
    # Auto Configuration Import Filters
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=
    org.springframework.boot.autoconfigure.condition.OnClassCondition
    
    # Auto Configure
    ......
    

    5.3) 根据以上类路径创建初始化器实例列表

    
    List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    

    5.4) 初始化器实例列表排序

    
    AnnotationAwareOrderComparator.sort(instances);
    

    5.5) 返回实例对象

    
    return instances;
    

    6.设置监听器

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

    ApplicationListener 的作用是什么?源码如下。(有空再写一篇springboot2 监听器的应用)

    
    @FunctionalInterface
    public interface ApplicationListenerE extends ApplicationEvent extends EventListener {
    
        /**
         * Handle an application event.
         * @param event the event to respond to
         */
        void onApplicationEvent(E event);
    
    }
    

    看源码,这个接口继承了 JDK 的 java.util.EventListener 接口,实现了观察者模式,它一般用来定义感兴趣的事件类型,事件类型限定于 ApplicationEvent 的子类,这同样继承了 JDK 的 java.util.EventObject 接口。

    设置监听器和设置初始化器调用的方法是一样的,只是传入的类型不一样,设置监听器的接口类型为:getSpringFactoriesInstances,对应的 spring-boot-autoconfigure-2.0.4.RELEASE.jar!/META-INF/spring.factories 文件配置内容在上面的配置文件里面:Application Listeners

    通过文件可以看出当前只有一个 BackgroundPreinitializer 监听器。

    7.推断主入口应用类

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

    通过构造一个运行时异常,再遍历异常栈中的方法名,获取方法名为 main 的栈帧,从来得到入口类的名字再返回该类。

    四、总结

    今天主要分析SpringBoot初始化实例的源码分析,本章暂时分析到:

    深入理解 SpringBoot 启动机制:初始化流程(run方法)

    下一章节再继续分析run方法。

    END

    Java面试题专栏

    深入理解 SpringBoot 启动机制:初始化流程(run方法)

    欢迎长按下图关注公众号后端技术精选

    深入理解 SpringBoot 启动机制:初始化流程(run方法)

    原文始发于微信公众号(后端技术精选):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 深入理解 SpringBoot 启动机制——初始化流程(run方法)


     上一篇
    SpringBoot 结合 Spring Cache 操作 Redis 实现数据缓存 SpringBoot 结合 Spring Cache 操作 Redis 实现数据缓存
    点击上方“后端技术精选”,选择“置顶公众号” 技术文章第一时间送达! 作者:超级小豆丁 http://www.mydlq.club/article/55/ 系统环境: Redis 版本:5.0.7 SpringBoot 版
    下一篇 
    深入理解 SpringBoot 启动机制——run()启动源码全过程分析 深入理解 SpringBoot 启动机制——run()启动源码全过程分析
    作者:MyBug juejin.im/post/5d3eddb4f265da03b94fcca5 一、前言在我们了解到 new SpringApplication(primarySources)实例初始化源码的加载过程,