SpringBoot 四、Spring Boot 启动全过程构造器源码分析

本篇基于 Spring Boot 2.4.4 版本进行分析
Spring Boot 的入口类

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

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

来看一下这个类的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);
}

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

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

先用主要资源类来实例化一个 SpringApplication 对象,再调用这个对象的 run 方法,所以我们分两步来分析这个启动源码。
SpringApplication 的实例化过程
SpringBoot 四、Spring Boot 启动全过程构造器源码分析_第1张图片

接着上面的 SpringApplication 构造方法进入以下源码:

public SpringApplication(Class... primarySources) {
        this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
    // 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();

    // 5、设置回调接口,可用于在使用BootstrapRegistry之前初始化它。从springboot2.4开始有
	this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));

    // 6、设置应用上线文初始化器
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));      

    // 7、设置监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    // 8、推断主入口应用类
    this.mainApplicationClass = deduceMainApplicationClass();

}

可知这个构造器类的初始化包括8个过程。
前三个过程是加载资源,主要分析之后几个过程

  1. 推断当前 WEB 应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();

来看下 deduceWebApplicationType 方法和相关的源码:

private WebApplicationType deduceWebApplicationType() {
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}

private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.reactive.DispatcherHandler";

private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
        + "web.servlet.DispatcherServlet";

private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
            "org.springframework.web.context.ConfigurableWebApplicationContext"
 };      

public enum WebApplicationType {

    /**
     * 非 WEB 项目
     */
    NONE,

    /**
     * SERVLET WEB 项目
     */
    SERVLET,

    /**
    * 响应式 WEB 项目
     */
     REACTIVE

}

5、设置回调接口,可用于在使用BootstrapRegistry之前初始化它。从springboot2.4开始有

this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));

看下getSpringFactoriesInstances方法,这个方法就是通过工厂模式获取类

private  Collection getSpringFactoriesInstances(Class type) {
		return getSpringFactoriesInstances(type, new Class[] {});
	}

	private  Collection getSpringFactoriesInstances(Class type, Class[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

	@SuppressWarnings("unchecked")
	private  List createSpringFactoriesInstances(Class type, Class[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set names) {
		List instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
				Class instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				Constructor constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}
  1. 设置应用上下文初始化器
setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));

ApplicationContextInitializer 的作用是什么?源码如下。

public interface ApplicationContextInitializer {

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

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

7、 设置监听器

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

ApplicationListener 的作用是什么?源码如下。


@FunctionalInterface
public interface ApplicationListener 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.4.3.RELEASE.jar!/META-INF/spring.factories 文件配置内容请见下方。

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

可以看出有好几个监听器

8.推断主入口应用类

his.mainApplicationClass = deduceMainApplicationClass();

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 的栈帧,从来得到入口类的名字再返回该类。

总结
源码分析内容有点多,也很麻烦,本章暂时分析到 SpringApplication 构造方法的初始化流程,下章再继续分析其 run 方法

你可能感兴趣的:(springboot)