> 文章列表 > SpringBoot源码学习系列——构造流程分析

SpringBoot源码学习系列——构造流程分析

SpringBoot源码学习系列——构造流程分析

通过执行SpringApplication的静态run()方法,可以完成SpringBoot应用的启动。本文对SpringApplication的实例化过程进行分析。

SpringApplication初始化简介

SpringBoot源码学习系列——构造流程分析
查看SpringApplication#run方法,可以看到,实际上就是new了一个SpringApplication对象,参数primarySources即为入口类:
SpringBoot源码学习系列——构造流程分析
SpringBoot源码学习系列——构造流程分析
根据上面的分析,启动类也可以实现如下:

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

如下图,这种方式可以在启动应用前进行一些设置,如设置Banner、添加初始化操作等。
SpringBoot源码学习系列——构造流程分析

SpringApplication实例化流程

整体流程

通过构造方法进行初始化:

	public SpringApplication(Class<?>... primarySources) {this(null, primarySources);}// 参数resourceLoader:资源加载接口,默认使用DefaultResourceLoader,// 例如指定banner信息文件路径,默认为classpath下,banner.*文件// 参数primarySources:默认为入口类,只要直接或间接配置了@EnableAutoConfiguration注解的类均可@SuppressWarnings({ "unchecked", "rawtypes" })public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.resourceLoader = resourceLoader;// 入口类不能为空Assert.notNull(primarySources, "PrimarySources must not be null");// 将入口类参数转换成LinkedHashSet,去重,赋值给成员变量this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// 推断web应用类型this.webApplicationType = WebApplicationType.deduceFromClasspath();// 加载并初始化ApplicationContextInitializer及其相关实现类setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 加载并初始化ApplicationListener及其相关实现类setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 推断main方法Class类this.mainApplicationClass = deduceMainApplicationClass();}

web应用类型推断

SpringBoot源码学习系列——构造流程分析

    // 基于classpath下是否存在类进行类型推断static WebApplicationType deduceFromClasspath() {// DispatcherHandler存在,DispatcherServlet和ServletContainer不存在,为REACTIVE类型if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {return WebApplicationType.REACTIVE;}// Servlet和ConfigurableWebApplicationContext任一个不存在,不是web应用for (String className : SERVLET_INDICATOR_CLASSES) {if (!ClassUtils.isPresent(className, null)) {return WebApplicationType.NONE;}}// 否则为SERVLET类型return WebApplicationType.SERVLET;}

此处核心是使用ClassUtils#isPresent方法通过反射创建指定类,创建成功则存在,否则不存在。

ApplicationContextInitializer加载

源码分析

ApplicationContextInitializer是Spring IOC容器提供的一个接口,只定义了一个initialize(C applicationContext)方法,用于初始化应用上下文。
SpringBoot源码学习系列——构造流程分析
可以看到,ApplicationContextInitializer的加载包括两步:

  1. 通过getSpringFactoriesInstances获取相关实例
  2. 通过setInitializers设置实例
    首先看下通过getSpringFactoriesInstances获取相关实例:
    SpringBoot源码学习系列——构造流程分析
    可以看到,还是首先通过SpringFactoriesLoader.loadFactoryNames方法去获取META-INF/spring.factories配置文件中,属性org.springframework.context.ApplicationContextInitializer的值,值均是ApplicationContextInitializer的实现类。
    SpringBoot源码学习系列——构造流程分析

获取到初始化器全限定名后,通过createSpringFactoriesInstances方法进行ApplicationContextInitializer的实例化:

	private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,ClassLoader classLoader, Object[] args, Set<String> names) {List<T> instances = new ArrayList<>(names.size());// 遍历initializer全类名for (String name : names) {try {// 获取ClassClass<?> 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;}

由此便变成了初始化器ApplicationContextInitializer的实例化,然后对创建的bean进行排序,最后完成initializers对象的设置。

    // 此处通过new ArrayList给this.initializers赋值了一个新创建的listpublic void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {this.initializers = new ArrayList<>(initializers);}

自定义Initializer示例

首先自定义一个ApplicationContextInitializer实现类
SpringBoot源码学习系列——构造流程分析
自定义初始化器生效的方式包括以下三种:

  1. 与源码类似,在spring.factories文件的org.springframework.context.ApplicationContextInitializer属性值添加该类的全限定名;
  2. application.properties等配置文件中指定;
  3. 在执行run方法之前通过SpringApplication#addInitializers方法添加初始化器
    SpringBoot源码学习系列——构造流程分析

ApplicationListener加载

源码分析

ApplicationListener加载过程与ApplicationContextInitializer完全一致:
SpringBoot源码学习系列——构造流程分析

  1. 通过getSpringFactoriesInstances获取相关实例
  2. 通过setListeners设置实例
    getSpringFactoriesInstances中还是通过SpringFactoriesLoader.loadFactoryNames获取spring.factoriesApplicationListener属性的值,然后创建对象,并进行排序,赋值。
    SpringBoot源码学习系列——构造流程分析

自定义ApplicationListener示例

Spring的事件传播机制是基于观察者模式实现的,例如,在ApplicationContext管理Bean的生命周期过程中,将一些改变定义为事件ApplicationEvent,ApplicationContext通过ApplicationListener监听ApplicationEvent,事件被发布时,ApplicationListener进行相应的处理。也就是ApplicationListenerApplicationEvent配置使用,可以实现ApplicationContext的事件处理。容器中存在ApplicationListener的对象时,当ApplicationListener调用publishEvent方法时,对应Bean被触发。

SpringBoot源码学习系列——构造流程分析
ApplicationListener接口只有一个onApplicationEvent方法,用于处理事件,EventApplicationEvent的具体实现,即具体的事件。

ApplicationContext被初始化或刷新时,会触发ContextRefreshedEvent事件,通过如下方式可监听:
SpringBoot源码学习系列——构造流程分析
要注意的是,ApplicationListener需要被注册成Bean对象。

入口类推断

SpringBoot源码学习系列——构造流程分析
SpringApplication#deduceMainApplicationClass

   private Class<?> deduceMainApplicationClass() {try {// 创建运行时异常,获取栈元素数组// 通过 new RuntimeException().getStackTrace() 获取信息,// 与Thread.currentThread().getStackTrace()相比,效率更高,性能开销较小StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();for (StackTraceElement stackTraceElement : stackTrace) {// 如果包含方法main,则通过Class.forName创建对象并返回,// 最后赋值给SpringApplication的mainApplicationClass属性if ("main".equals(stackTraceElement.getMethodName())) {return Class.forName(stackTraceElement.getClassName());}}}catch (ClassNotFoundException ex) {// 忽略异常,返回null// Swallow and continue}return null;}