一、前言

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

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

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

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

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

二、Spring Boot 入口类

springboot 启动类变成了java普通文件 springboot启动类没有run_监听器

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

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

springboot 启动类变成了java普通文件 springboot启动类没有run_监听器_02

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

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

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

三、SpringApplication 的实例化过程

springboot 启动类变成了java普通文件 springboot启动类没有run_初始化_03

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

springboot 启动类变成了java普通文件 springboot启动类没有run_spring_04

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

springboot 启动类变成了java普通文件 springboot启动类没有run_spring_05

从上面的源码可以知道,整个实例化过程有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;
        }
    }

}

这个就是根据类路径下是否有对应项目类型的类推断出不同的应用类型,这里也说明 Spring Boot 2是支持响应式编程

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

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

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

public interface ApplicationContextInitializer<C 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> 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;
    }

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

这里是实例化的核心:

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

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

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

Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

loadFactoryNames 的源码如下:

public static List<String> 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 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
            Enumeration<URL> 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<T> 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 ApplicationListener<E 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 启动类变成了java普通文件 springboot启动类没有run_spring_06

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