SpringBoot 启动流程

目录

      • SpringApplication的启动思路
      • SpringApplication的构造方法
        • 整体流程
        • 设置基础资源 primarySources
        • 推断应用类型 WebApplicationType
        • 设置应用上下文初始化器、应用监听器
        • 推断主类 deduceMainApplicationClass()
      • SpringApplication的实例run()方法
        • 整体流程
        • 运行监听器 SpringApplicationRunListener
        • 准备环境 prepareEnvironment()
        • 打印Banner printBanner()
        • 创建应用上下文 createApplicationContext()
        • 准备上下文 prepareContext()
        • 刷新上下文 refreshContext()
        • callRunners() 执行Runner回调
      • 流程图
        • 大体流程
        • SpringApplication的构造方法
        • 实例run()方法
      • springboot启动过程中的扩展点
      • 深入解析springboot启动过程中的应用监听器、事件广播器
      • 附录
        • Runner、运行监听器、springboot的启动日志分析
        • 关于 refresh() 方法的名称
        • 关于spring、springboot的名称

 

使用的 springboot 源码版本 2.3.12.RELEASE,这是2.3.x系列的最后一个版本。

springboot应用的启动方式很多,可以在引导类中配置 ConfigurableApplicationContext,可以继承 SpringBootServletInitializer 重写SpringApplication的配置,可以用 SpringApplicationBuilder 来构建SpringApplication…不同的启动方式、不同版本的源码,启动过程、调用的一些方法有所差别,整体过程大同小异,此处以默认的启动方式进行讲述。

文中涉及到的Banner、SpringApplicationRunListener、Runner、StopWatch 的使用方式,可参考我的另一篇博文

https://blog.csdn.net/chy_18883701161/article/details/120753879

 

SpringApplication的启动思路

引导类

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
    	//调用 SpringApplication 的静态方法 run() 进行启动
        SpringApplication.run(DemoApplication.class, args);
    }

}

 

实质是调用 SpringApplication 本身的另一个重载的静态 run() 方法

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

核心只有一句代码 new SpringApplication(primarySources).run(args),2个步骤

  • 调用 SpringApplication 的构造方法创建 SpringApplication 实例
  • 调用 SpringApplication 实例的run()方法进行启动

 

springboot的启动思路

  • 核心类是 SpringApplication ,核心方法是 SpringApplication 的实例 run() 方法
  • 核心思路:创建 SpringApplication 实例,调用 SpringApplication 实例的 run() 方法进行启动
  • 默认的启动方式:调用 SpringApplication 的静态 run() 方法进行启动,由 静态 run() 方法创建 SpringApplication 实例, 调用实例的 run() 方法启动应用。

 

有了上面的核心思路,我们完全可以自己写main()方法中的启动代码

// 默认的启动代码
// SpringApplication.run(DemoApplication.class, args);


//创建实例,传入基础资源(引导类)
SpringApplication springApplication = new SpringApplication(DemoApplication.class);

// 甚至可以对SpringApplication实例进行配置,比如添加应用上下文初始化器
// springApplication.addInitializers(new MyApplicationContextInitializer());

//调用实例的run()方法进行启动,传入main()方法的参数
springApplication.run(args);

这种启动方式很常用,经常需要在创建 SpringApplication 实例后、调用实例 run() 方法启动应用之前,对 SpringApplication 实例进行配置。

 

SpringApplication的构造方法

整体流程
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	//设置资源加载器 resourceLoader
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	//设置要加载的基础资源 primarySources
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	//推断应用类型
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	//设置应用上下文的初始化器 ApplicationContextInitializer
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	//设置应用监听器 ApplicationListener
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	//推断主类
	this.mainApplicationClass = deduceMainApplicationClass();
}

主要是初始化 SpringApplication 的一些成员变量,那2个setter方法其实是 this.setXxx(); 省略了this

 

设置基础资源 primarySources
//基础资源
private Set<Class<?>> primarySources;

基础资源指的是springboot应用中自定义的一些核心类,通常是引导类。所谓加载基础资源,其实就是处理这些核心类,让这些核心类生效,比如解析引导类上的 @SpringBootApplication、@MapperScan,使之生效。

//默认的启动代码:第一个参数指定的就是 primarySources
SpringApplication.run(DemoApplication.class, args);

 

拓展问题:SpringBoot中最核心的类是哪个?

  • SpringBoot框架本身最核心的类是 SpringApplication:负责配置、启动应用,包括创建、配置、刷新spring容器
  • 自定义的类中最核心的是引导类:引导类是应用的启动入口,包含了 @SpringBootApplication、@EnableAsync、@EnableCaching、@MapperScan、@EnableEurekaClient 等关键注解、配置

 

推断应用类型 WebApplicationType

枚举类 WebApplicationType

public enum WebApplicationType {

	/**
	 * 不是web应用,不会使用内嵌的web容器来启动
	 */
	NONE,

	/**
	 * 基于servlet的web应用,会使用内嵌的servlet容器启动
	 */
	SERVLET,

	/**
	 * 基于reactive的web应用,会使用内嵌的reactive容器启动
	 */
	REACTIVE;


	//servlet、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";


	/**
	 * 默认的推断方式,检测classpath中是否有对应的核心类
	 */
	static WebApplicationType deduceFromClasspath() {
		//使用ClassUtils.isPresent()判断 classpath中是否有servlet或reactive的核心类,是否能用类加载器进行加载
		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;
	}


	/**
	 * 另一种推断方式,检测使用的上下文类型是否是servlet、reactive体系的
	 */
	static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
		//使用的上下文是否是 WebApplicationContext 或其子类,WebApplicationContext 是servlet体系的基础上下文
		if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
			return WebApplicationType.SERVLET;
		}
		//使用的上下文是否是 ReactiveWebApplicationContext 或其子类
		if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
			return WebApplicationType.REACTIVE;
		}
		return WebApplicationType.NONE;
	}


	/**
	 * 参数分别是String形式、Class形式的类名,用于判断后者是否是前者本身或者其子类
	 */
	private static boolean isAssignable(String target, Class<?> type) {
		try {
			return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
		}
		catch (Throwable ex) {
			return false;
		}
	}

}

这个枚举类主要

  • 定义了3种应用类型:none、servlet、reactive
  • 提供了2种推断应用类型的方式
    • deduceFromClasspath:检测classpath中是否有对应的核心类(默认方式)
    • deduceFromApplicationContext:检测使用的上下文类型是否是servlet、reactive体系的

 

设置应用上下文初始化器、应用监听器

spring.factories 文件格式

#实质是 properties 文件,key是接口,value指定该接口要使用的实现类,可指定多个实现类,都是使用全限定类名
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

 

new SpringApplication()中的相关源码

private List<ApplicationContextInitializer<?>> initializers;

private List<ApplicationListener<?>> listeners;
//实质是 this.setXxx() 给SpringApplication实例对应的成员变量赋值
//先调用 getSpringFactoriesInstances() 根据 spring.factories 中的配置获取要使用的接口(应用监听器、初始化器)实例,再把这些实例赋给对应的成员变量
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

 

SpringApplication 的实例方法 getSpringFactoriesInstances()

用于根据 spring.factories 中的配置,获取指定接口要使用的各个实现类的实例

/**
 * 根据 spring.factories 中的配置,获取指定接口要使用的各个实现类的实例
 * (通过反射调用无参构造器创建实例)
 * 
 * @param type 指定接口
 * @return 指定接口要使用的各个实现类的实例
 */
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
	//实质是调用下面的重载方法,注意这里传入了一个空数组
	return getSpringFactoriesInstances(type, new Class<?>[] {});
}


/**
 * 根据 spring.factories 中的配置,获取指定接口要使用的各个实现类的实例
 * (通过反射调用指定的造器器创建实例)
 * 
 * @param type 指定接口
 * @param parameterTypes 指定要使用的构造器的形参表的参数类型
 * @param args 对应的实参
 * @return 指定接口要使用的各个实现类的实例
 */
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
	//获取类加载器:如果 this.resourceLoader 不为空则使用resourceLoader的类加载器,否则使用默认的类加载
	ClassLoader classLoader = getClassLoader();
	//加载、解析classpath中的所有spring.factories,得到指定接口要使用的各个实现类的全限定类名
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	//给这些实现类创建实例
	//实质是通过反射调用指定的构造方法创建实例,会根据传入的构造方法的参数类型 parameterTypes 自动确定要使用的构造器
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	AnnotationAwareOrderComparator.sort(instances);
	return instances;
}

看第1个 getSpringFactoriesInstances(), return getSpringFactoriesInstances(type, new Class[] {}); 传的是空数组,要使用的构造方法的形参表是空数组,即是调用实现类的无参构造器创建实例。

spring.factories 文件指定的实现类很多,这些实现类的构造方法多种多样,都是统一调用无参的构造方法创建实例。我们在使用 spring.factories 机制时,应该给指定的实现类提供无参的构造器。

 

SpringFactoriesLoader 的静态方法 loadFactoryNames()

用于根据 spring.factories 中的配置,获取指定接口要使用的各个实现类的(全限定)类名

/**
 * 根据 spring.factories 中的配置,获取指定接口要使用的实现类的全限定类名列表
 * 
 * @param factoryType 指定接口
 * @param classLoader 要使用的类加载器,为null时会使用系统类加载器、引导类加载器|根加载器
 * @return List,指定接口要使用的实现类的类名列表
 */
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	//从接口的Class对象获取接口的全限定类名
	String factoryTypeName = factoryType.getName();
	//先调用 loadSpringFactories() 加载、解析所有的 spring.factories
	//再调用 getOrDefault() 从解析结果中获取指定接口要使用的实现类类名列表。如果解析结果中没有指定接口或该接口要使用的实现类为空,则返回指定的默认值(空集合)
	return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}



/**
 * 加载、解析classpath中所有的 META-INF/spring.factories 文件
 * 
 * @param classLoader 要使用的类加载器,为null时会使用系统类加载器、引导类加载器|根加载器
 * @return Map>,key是接口名,value是该接口要使用的实现类的类名列表,都是全限定的
 */
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	//先从缓存中获取,有就直接返回,没有才加载、解析 spring.factories
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}

	try {
		//使用类加载器加载 classpath中所有 META-INF/spring.factories ,得到各个 spring.factories 文件的路径
		//如果传入的类加载器为null,则使用系统类加载器 SystemClassLoader,如果系统类加载器也为null,则使用根类加载器|引导类加载器 BootstrapClassLoader
		Enumeration<URL> urls = (classLoader != null ?
				classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
				
		// LinkedMultiValueMap,MultiValue 顾名思义 一个key可以对应多个value
		// 内部用 Map> 存储数据,add(key,value)添加元素时,会把value放到对应的List中
		// 用于存储所有spring.factories文件的解析结果,一个键值对即一个 接口 => 要使用的实现类
		result = new LinkedMultiValueMap<>();
		
		//遍历所有的spring.factories文件,逐个解析
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			UrlResource resource = new UrlResource(url);
			//解析为 Properties
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			//遍历各个键值对
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
				//key是接口名
				String factoryTypeName = ((String) entry.getKey()).trim();
				//value是包含多个实现类的字符串,切分为字符串数组进行遍历
				for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					//添加到 LinkedMultiValueMap 中
					result.add(factoryTypeName, factoryImplementationName.trim());
				}
			}
		}
		
		//把解析结果放到缓存中
		cache.put(classLoader, result);
		//返回解析结果
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

 

从上面的分析很容易看出

第一次调用 getSpringFactoriesInstances() 时,就加载、解析了所有的 spring.factories,把解析结果放到了缓存中,但只实例化了指定接口要使用的实现类。

后续再调用 getSpringFactoriesInstances() 时,都是直接从缓存中获取解析结果( Map> ),从中获取指定接口要使用的实现类列表,对其进行实例化。

spring.factories文件只加载、解析了1次

 

扩展:SPI机制使用自定义的接口

spring.factories中指定的接口通常是作为框架本身提供的扩展配置,比如应用监听器 ApplicationListener、应用初始化器 ApplicationContextInitializer,框架本身会处理这些扩展配置、使之生效。

有时候需要对 spring.factories 中的接口配置做一些自定义的处理,比如要在 spring.factories 中给自定义的接口指定实现类,spring|springboot 本身不会处理这些自定义接口的配置,需要我们自行写代码处理。
 

SpringFactoriesLoader 是spring提供的处理spring.factories的工具类,常用方法如下

//参数都是指定接口、要使用的类加载器,类加载器可以为null

//获取指定接口要使用的实现类的类名列表
List<String> classNameList = SpringFactoriesLoader.loadFactoryNames(Xxx.class, null);

//获取指定接口要使用的各个实现类的实例
List<Xxx> instanceList = SpringFactoriesLoader.loadFactories(Xxx.class, null);

 
SpringApplication类是springboot提供的,也有几个处理 spring.factories 文件的方法,都是在 SpringFactoriesLoader 的基础上实现的,使用 private 修饰,只在SpringApplication内部使用,不暴露出去。

 

推断主类 deduceMainApplicationClass()

这个方法是 SpringApplication 的实例方法,根据方法调用栈找到主类

private Class<?> deduceMainApplicationClass() {
	try {
		//获取各个调用的栈帧,一个栈帧对应一个方法调用
		StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
		//遍历这些栈帧
		for (StackTraceElement stackTraceElement : stackTrace) {
			//找到方法名为main的栈帧
			if ("main".equals(stackTraceElement.getMethodName())) {
				//把该栈帧对应的java类作为主类返回
				return Class.forName(stackTraceElement.getClassName());
			}
		}
	}
	catch (ClassNotFoundException ex) {
		// Swallow and continue
	}
	return null;
}

 

SpringApplication的实例run()方法

整体流程

实例run()方法用于启动spring应用,创建并刷新一个新的应用上下文

/**
 * Run the Spring application, creating and refreshing a new ApplicationContext
 * 
 * @param args main()方法传递的参数
 * @return 一个已创建、启动的 ApplicationContext
 */
public ConfigurableApplicationContext run(String... args) {

	//创建并启动计时器
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	
	ConfigurableApplicationContext context = null;

	//配置系统属性 java.awt.headless
	configureHeadlessProperty();

	//创建运行监听器组
	//先根据spring.factories中的配置创建要使用的运行监听器实例,再把这些运行监听器实例封装为运行监听器组
	SpringApplicationRunListeners listeners = getRunListeners(args);

	//执行运行监听器的starting()回调方法
	listeners.starting();
	
	try {
	
		//构建应用参数
		//应用参数指的是传给main()方法的参数 arg,这一步会将main()方法的参数封装为ApplicationArguments
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		
		//准备环境
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		
		//配置 spring.beaninfo.ignore 属性
		configureIgnoreBeanInfo(environment);
		
		//打印banner
		Banner printedBanner = printBanner(environment);
		
		//创建应用上下文
		context = createApplicationContext();

		//准备上下文
		//主要是对上一步创建好的上下文进行配置、完善,这一步会加载所有的bean定义
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);

		//刷新上下文
		//主要是调用高级容器的refresh()刷新上下文,这一步会实例化所有的单例bean(懒加载的除外)
		refreshContext(context);

		//执行刷新后处理
		//这个是protected修饰的空方法,作为预留的扩展点可被子类重写,以在上下文刷新之后做一些自定义的操作
		afterRefresh(context, applicationArguments);

		//终止计时器,打印启动花费的时长
		stopWatch.stop();
		if (this.logStartupInfo) {
			//日志示例:Started DemoApplication in 4.25 seconds (JVM running for 5.368)
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}

		//执行运行监听器的 started() 回调方法
		listeners.started(context);
		
		//执行 Runner 回调
		//这也是预留的一个扩展点,可以在应用启动后,根据main()参数做一些自定义的处理
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		//主要是:1执行运行监听器的 failed() 回调方法;2、使用异常报告器 SpringBootExceptionReporter 打印|记录异常信息
		handleRunFailure(context, ex, listeners);
		throw new IllegalStateException(ex);
	}

	try {
		//执行运行监听器的 running() 回调方法
		listeners.running(context);
	}
	catch (Throwable ex) {
		//和上面的 handleRunFailure() 差不多,只是传入的运行监听器组是null,不会执行运行监听器的的failed()回调方法,只打印异常信息
		handleRunFailure(context, ex, null);
		throw new IllegalStateException(ex);
	}
	
	//返回应用上下文
	return context;
}

 

运行监听器 SpringApplicationRunListener

准确来说,应该叫做 启动监听器。
 

SpringApplicationRunListener 接口

这个监听器用于监听spring应用的启动事件,即监听 SpringApplication 的实例 run() 方法的执行过程,提供了一系列回调方法,可在run()方法执行 | 启动过程的特定阶段做一些额外操作

public interface SpringApplicationRunListener {

	default void starting() {
	}

	default void environmentPrepared(ConfigurableEnvironment environment) {
	}

	default void contextPrepared(ConfigurableApplicationContext context) {
	}
	
	default void contextLoaded(ConfigurableApplicationContext context) {
	}

	default void started(ConfigurableApplicationContext context) {
	}

	default void running(ConfigurableApplicationContext context) {
	}

	default void failed(ConfigurableApplicationContext context, Throwable exception) {
	}

}

注意:这个接口和 ApplicationListener 都是监听器的2个顶级接口,这2个接口之间没有继承关系。

 

ApplicationListener 应用监听器、SpringApplicationRunListener 运行监听器,都是监听器,但区别很大

  • 来源不同:ApplicationListener是spring提供的,用于构建spring的事件驱动模型;SpringApplicationRunListener是springboot提供的,用于在应用启动的特定阶段做一些额外操作
  • 监听的对象不同: ApplicationListener是监听事件广播器发布的应用事件,SpringApplicationRunListener是监听应用的启动过程(SpringApplication的实例run方法的执行过程)
  • 触发机制不同:ApplicationListener要监听到事件广播器发布的特定事件时,才会触发回调方法;SpringApplicationRunListener是在应用启动的特定阶段执行方法回调,应用启动到了特定阶段就会执行,跟事件发布没有必然联系

 

EventPublishingRunListener 实现类

SpringApplicationRunListener 接口提供的都是默认方法,只提供了空实现,springboot只给这个接口提供了一个实现类 EventPublishingRunListener,用于在应用启动的各个阶段发布对应的事件

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

	private final SpringApplication application;

	private final String[] args;

	private final SimpleApplicationEventMulticaster initialMulticaster;

	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		//初始化应用事件广播器
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		//把之前创建的应用事件监听器实例添加到这个事件广播器上
		// new SpringApplication() 创建 SpringApplication 实例时,只是创建了 ApplicationListener 的实例,这一步添加到事件广播器上,ApplicationListener才会生效,此能监听这个广播器发布的事件
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}

	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	public void starting() {
		this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
	}

	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster
				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {
		this.initialMulticaster
				.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
	}

	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		for (ApplicationListener<?> listener : this.application.getListeners()) {
			if (listener instanceof ApplicationContextAware) {
				((ApplicationContextAware) listener).setApplicationContext(context);
			}
			context.addApplicationListener(listener);
		}
		this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
	}

	@Override
	public void started(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
		AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
	}

	@Override
	public void running(ConfigurableApplicationContext context) {
		context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
		AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
	}

	@Override
	public void failed(ConfigurableApplicationContext context, Throwable exception) {
		ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
		if (context != null && context.isActive()) {
			// Listeners have been registered to the application context so we should
			// use it at this point if we can
			context.publishEvent(event);
		}
		else {
			// An inactive context may not have a multicaster so we use our multicaster to
			// call all of the context's listeners instead
			if (context instanceof AbstractApplicationContext) {
				for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
						.getApplicationListeners()) {
					this.initialMulticaster.addApplicationListener(listener);
				}
			}
			this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
			this.initialMulticaster.multicastEvent(event);
		}
	}

	private static class LoggingErrorHandler implements ErrorHandler {
		//...
	}

}

主要关注2点

  • 在构造方法中创建了事件广播器 ApplicationEventMulticaster,将之前创建的 ApplicationListener 实例绑定到事件广播器上,使这些应用监听器生效
  • 提供了7个回调方法,回调方法都是通过事件广播器发布相应的事件,对应7种事件,除去 failed() —— ApplicationFailedEvent,剩下的6个对应springboot应用启动的6个阶段。

contextLoaded() 这个回调方法比较特殊,后续会说到。

 

SpringApplicationRunListeners 运行监听器组

SpringApplicationRunListener 的复数形式,用于封装多个运行监听器,以便对这些运行监听器进行批量调用

class SpringApplicationRunListeners {

	private final Log log;

	private final List<SpringApplicationRunListener> listeners;

	SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
		this.log = log;
		this.listeners = new ArrayList<>(listeners);
	}

	void starting() {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.starting();
		}
	}

	void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}

	void contextPrepared(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.contextPrepared(context);
		}
	}

	void contextLoaded(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.contextLoaded(context);
		}
	}

	void started(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.started(context);
		}
	}

	void running(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.running(context);
		}
	}

	void failed(ConfigurableApplicationContext context, Throwable exception) {
		for (SpringApplicationRunListener listener : this.listeners) {
			callFailedListener(listener, context, exception);
		}
	}

	private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
			Throwable exception) {
		//...
	}

}

 

springboot中对应的启动过程

//调用 SpringApplication 本身的 getRunListeners() 方法
SpringApplicationRunListeners listeners = getRunListeners(args);

//执行运行监听器的starting()回调方法
listeners.starting();
private SpringApplicationRunListeners getRunListeners(String[] args) {
	Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
	//先根据spring.factories中的配置创建要使用的运行监听器实例,再把这些运行监听器实例封装为运行监听器组
	return new SpringApplicationRunListeners(logger, 
		getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

 

springboot本身只给 SpringApplicationRunListener 提供了一个实现类,springboot自身的 spring.factories 中也只启用了这个实现

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

为什么还要打包为 SpringApplicationRunListeners 进行批量操作?就一个运行监听器,似乎没有打包的必要。

因为开发者可能需要在应用启动的特定阶段做一些自定义操作,要实现 SpringApplicationRunListener 重写回调方法,这样就会存在多个 SpringApplicationRunListener。

 

准备环境 prepareEnvironment()
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	//获取或创建环境,有现成的环境就直接使用,没有则调用构造方法new一个
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	//配置环境:上一步调用无参构造器创建了Environment实例,这一步对创建好的Environment实例进行配置
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	//绑定配置属性源、环境
	ConfigurationPropertySources.attach(environment);
	//至此环境已配置好,触发 SpringApplicationRunListener 的 environmentPrepared() 回调方法
	listeners.environmentPrepared(environment);
	//将配置好的环境绑定到 SpringApplication 上
	bindToSpringApplication(environment);
	//如果不是自定义的环境,则根据情况确定是否需要转换环境
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
	//重新绑定属性源、环境
	ConfigurationPropertySources.attach(environment);
	return environment;
}

 

getOrCreateEnvironment() 获取或创建环境

private ConfigurableEnvironment getOrCreateEnvironment() {
	if (this.environment != null) {
		return this.environment;
	}
	//会根据应用类型创建对应的环境
	switch (this.webApplicationType) {
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
	}
}

 

configureEnvironment() 配置环境

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
	// 处理 ConversionService 配置
	// ConversionService 是spring的一个线程安全的类型转换接口,convert(Object, Class) 可以将对象转换为指定类型,关键在于是线程安全的
	if (this.addConversionService) {
		ConversionService conversionService = ApplicationConversionService.getSharedInstance();
		environment.setConversionService((ConfigurableConversionService) conversionService);
	}
	//配置属性源
	configurePropertySources(environment, args);
	//配置profiles属性:这一步会把 spring.profiles 激活的配置文件添加到环境中
	configureProfiles(environment, args);
}


protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
	MutablePropertySources sources = environment.getPropertySources();
	//配置默认属性
	if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
		sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
	}
	//配置命令行属性:如果给main()方法传递了命令行参数,则添加到Environment实例中
	if (this.addCommandLineProperties && args.length > 0) {
		String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
		if (sources.contains(name)) {
			PropertySource<?> source = sources.get(name);
			CompositePropertySource composite = new CompositePropertySource(name);
			composite.addPropertySource(
					new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
			composite.addPropertySource(source);
			sources.replace(name, composite);
		}
		else {
			sources.addFirst(new SimpleCommandLinePropertySource(args));
		}
	}
}

 

打印Banner printBanner()

Banner接口

@FunctionalInterface
public interface Banner {

	/**
	 * Print the banner to the specified print stream
	 * 
	 * @param environment the spring environment
	 * @param sourceClass the source class for the application
	 * @param out the output print stream
	 */
	void printBanner(Environment environment, Class<?> sourceClass, PrintStream out);

	/**
	 * banner的打印模式
	 */
	enum Mode {

		/**
		 * 禁用banner,不打印banner
		 */
		OFF,

		/**
		 * 输出流使用 System.out,标准输出,输出到控制台
		 */
		CONSOLE,

		/**
		 * 输出到日志文件中
		 */
		LOG

	}

}

 

SpringApplicaition 的 printBanner() 方法

//banner的默认打印模式是输出到控制台
private Banner.Mode bannerMode = Banner.Mode.CONSOLE;

/**
 * 根据打印模式打印 banner
 */
private Banner printBanner(ConfigurableEnvironment environment) {
	if (this.bannerMode == Banner.Mode.OFF) {
		return null;
	}
	ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
			: new DefaultResourceLoader(null);
	//通过 SpringApplicationBannerPrinter 类打印banner
	SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
	if (this.bannerMode == Mode.LOG) {
		return bannerPrinter.print(environment, this.mainApplicationClass, logger);
	}
	//console模式的打印
	return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}

 

SpringApplicationBannerPrinter 类,用于打印banner

class SpringApplicationBannerPrinter {

	//文本banner的文件位置对应的配置项
	static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";

	//图片banner的文件位置对应的配置项
	static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";

	//默认的banner文件加载位置
	static final String DEFAULT_BANNER_LOCATION = "banner.txt";

	//图片banner支持的文件扩展名
	static final String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };
	
	/**
	 * 默认banner
	 */
	private static final Banner DEFAULT_BANNER = new SpringBootBanner();

	private final ResourceLoader resourceLoader;

	private final Banner fallbackBanner;

	SpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) {
		//...
	}

	Banner print(Environment environment, Class<?> sourceClass, Log logger) {
		//...
	}

	Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
		//...
	}
	
	private Banner getBanner(Environment environment) {
		//...
	}

	/**
	 * 打印文本形式的banner
	 */
	private Banner getTextBanner(Environment environment) {
		//...
	}

	/**
	 * 打印图片形式的banner
	 */
	private Banner getImageBanner(Environment environment) {
		//...
	}

	/**
	 * 创建文本形式的banner字符串
	 */
	private String createStringFromBanner(Banner banner, Environment environment, Class<?> mainApplicationClass)
			throws UnsupportedEncodingException {
		//...
	}


	private static class Banners implements Banner {
		//...
	}

	private static class PrintedBanner implements Banner {
		//...
	}

}

 

创建应用上下文 createApplicationContext()
/**
 * 要使用的应用上下文的类型
 */
private Class<? extends ConfigurableApplicationContext> applicationContextClass;

protected ConfigurableApplicationContext createApplicationContext() {
	//确定要创建的应用上下文类型:如果没有指定应用上下文的类型,则根据推断得到的应用类型自动确定
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
			switch (this.webApplicationType) {
			case SERVLET:
				contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
				break;
			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);
}

 

准备上下文 prepareContext()

上一步的创建应用上下文,只是通过反射调用构造方法创建实例,尚未对上下文实例进行配置,这一步用于配置、初始化创建好的上下文实例

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
		SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	//设置环境
	context.setEnvironment(environment);
	//执行应用上下文的后置处理,主要是设置一些尚未填充的属性
	postProcessApplicationContext(context);
	//执行应用上下文的各个初始化器 ApplicationContextInitializer,对上下文进行初始化
	// ApplicationContextInitializer 接口中只有一个 initialize() 方法,用于对应用上下文进行初始化,这个可当做扩展点做一些自定义的初始化操作
	applyInitializers(context);
	//至此上下文准备完毕,执行运行监听器的 contextPrepared() 回调方法
	listeners.contextPrepared(context);
	
	//如果允许打印启动信息,则
	if (this.logStartupInfo) {
		//打印应用正在启动的信息,示例:Starting DemoApplication on DESKTOP-E74978P with PID 49824
		logStartupInfo(context.getParent() == null);
		//打印 profiles 激活的配置,示例:The following profiles are active: test
		//没有设置激活的配置则打印默认配置:No active profile set, falling back to default profiles: default
		//spring.profiles除了include、active之外,还有一个default属性用于指定默认配置文件
		logStartupProfileInfo(context);
	}
	
	//获取内置的低级容器,以对内置的低级容器进行一些配置
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	//注册一些特殊的单例bean:应用参数、banner
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	//配置是否允许bean定义覆盖
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	//配置是否允许bean的懒加载。默认false,高级容器启动时就实例化所有单例
	if (this.lazyInitialization) {
		//如果允许bean的懒加载,则给内置的低级容器添加懒加载对应的后置处理器
		context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
	}
	
	//获取所有的资源,所有的资源指的是 this.primarySources、this.sources 2个属性指定的资源
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	//加载所有的资源:使用 BeanDefinitionLoader 加载所有的bean定义,完成bean的注册
	load(context, sources.toArray(new Object[0]));
	
	//执行运行监听器的 contextLoaded() 回调方法
	listeners.contextLoaded(context);
}


/**
 * 执行应用上下文的后置处理,主要是设置一些尚未填充的属性
 */
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
	if (this.beanNameGenerator != null) {
		context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
				this.beanNameGenerator);
	}
	if (this.resourceLoader != null) {
		if (context instanceof GenericApplicationContext) {
			((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
		}
		if (context instanceof DefaultResourceLoader) {
			((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
		}
	}
	if (this.addConversionService) {
		context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
	}
}

 

刷新上下文 refreshContext()

实质是调用高级容器的2个方法

  • registerShutdownHook():向jvm注册ShutdownHook关闭钩子,在jvm终止时自动关闭应用上下文
  • refresh():刷新上下文
     

refresh() 执行的具体过程可参考的我的另一篇博文

https://blog.csdn.net/chy_18883701161/article/details/120550053

 

callRunners() 执行Runner回调

2种Runner

runner有2种:CommandLineRunner、ApplicationRunner

@FunctionalInterface
public interface CommandLineRunner {

	void run(String... args) throws Exception;

}
@FunctionalInterface
public interface ApplicationRunner {

	void run(ApplicationArguments args) throws Exception;

}

都是函数式接口、只有一个run()方法,区别是参数类型不同

  • CommandLineRunner:顾名思义,基于命令行,使用的使用 String… 接收命令行的多个参数
  • ApplicationRunner:使用的是应用参数 ApplicationArguments

其实使用的都是 main() 方法的参数,ApplicationArguments 只是对main()参数的封装。

 

springboot启动过程中调用的方法

private void callRunners(ApplicationContext context, ApplicationArguments args) {
	List<Object> runners = new ArrayList<>();
	//从容器中获取runner
	runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
	runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
	//对runner进行排序。就是说支持多个runner,可以用 @Order 或实现Order接口指定这些runner的执行顺序
	AnnotationAwareOrderComparator.sort(runners);
	//遍历执行这些runner的run()方法。注意传入的args是 ApplicationArguments 类型
	for (Object runner : new LinkedHashSet<>(runners)) {
		if (runner instanceof ApplicationRunner) {
			callRunner((ApplicationRunner) runner, args);
		}
		// 对于 CommandLineRunner,会先 args.getSourceArgs() 获取 String... 类型的参数
		if (runner instanceof CommandLineRunner) {
			callRunner((CommandLineRunner) runner, args);
		}
	}
}

man()参数可以包含 server.port 之类的应用预定义配置,也可以包含自定义的参数。通过2个runner接口,可以在应用启动后根据 main() 方法传入的参数做一些额外处理,当然,不使用main()参数、直接做一些与mian()参数无关的操作也行。

 

流程图

大体流程

SpringBoot 启动流程_第1张图片

 

 

SpringApplication的构造方法

SpringBoot 启动流程_第2张图片

 

 

实例run()方法

SpringBoot 启动流程_第3张图片

 

 

springboot启动过程中的扩展点

springboot在启动过程中主要预留了4个扩展点

1、上下文初始化接口 ApplicationContextInitializer

在 prepareContext() 准备上下文阶段,会调用 applyInitializers() 执行各个ApplicationContextInitializer接口,这个接口可做一些自定义的上下文初始化操作。
 

2、预留的 afterRefresh() 方法

在 refresh() 刷上下文后会调用这个方法,像 afterRefresh() 这种 protected 修饰的空方法,基本都是预留的扩展点,留给子类重写的,不想让子类重写的都是定义为 private。

不熟悉springboot的同学,使用 afterRefresh() 很容易踩坑,能用其它方式就用其它方式。
 

3、Runner 回调

在应用启动后会执行Runner回调,方法参数和 afterRefresh() 差不多,都是应用上下文 context、应用参数 applicationArguments。
 

4、自定义运行监听器 SpringApplicationRunListener

可以在启动过程中的多个阶段做一些额外处理。

 

深入解析springboot启动过程中的应用监听器、事件广播器

有同学可能会疑惑

//new SpringApplication() 阶段会创建 ApplicationListener 实例
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

//实例 run() 方法启动阶段,会创建事件广播器实例,把 ApplicationListener 实例添加到事件广播器上
SpringApplicationRunListeners listeners = getRunListeners(args);


// 调用高级容器的 refresh() 方法刷新上下文阶段
// initApplicationEventMulticaster() 会创建事件广播器实例
// registerListeners() 会把 ApplicationListener 实例添加到事件广播器上
refreshContext()

好像重复创建了事件广播器、重复添加了应用监听器,是否重复、矛盾?

 

首先明确2点

1、getSpringFactoriesInstances() 只负责创建 spring.factories 中指定的实现类的实例,这些实例是通过反射调用无参构造器直接创建的,没有放到容器中。

getSpringFactoriesInstances() 不会创建注解、xml配置文件、java代码配置方式配置的bean实例,这些方式配置的bean实例,由spring容器通过 beanFactory.getBean() 创建,会放到容器中。

比如应用监听器 ApplicationListener,getSpringFactoriesInstances() 创建spring.factories 中配置的应用监听器,spring容器创建
注解、xml配置文件、java代码配置方式配置的应用监听器,这才是要使用的全部的应用监听器。

 

2、应用监听器、事件广播器之间的添加|绑定,是实例之间的绑定,应用监听器实例只能接收到所绑定的事件广播器实例发布的事件。

this.initialMulticaster.addApplicationListener(listener);`

应用监听器实例 listenerA 绑定到事件广播器实例 multicasterA 上,没有绑定到事件广播器实例 multicasterB 上,能不能接收到 multicasterB 发布的事件?显然不能,你绑定的是 multicasterB,跟 multicasterA 有什么关系,multicasterA 都不认识你。

 

springboot中的相关源码

// new SpringApplication()创建SpringApplication实例阶段
// getSpringFactoriesInstances() 创建 spring.factories 中指定的 ApplicationListener 实现类的实例
// 保存在 SpringApplication实例 的成员变量中
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));


// 实例 run() 方法启动应用阶段
// 直接 new 创建事件广播器实例,保存在运行监听器 EventPublishingRunListener 实例自身的成员变量中
// 将之前创建的spring.factories的 ApplicationListener 实例添加到这个事件广播器实例上
SpringApplicationRunListeners listeners = getRunListeners(args);


//执行以上2步时,上下文|容器尚未创建,不能通过 beanFactory.getBean() 来获取实例,不能保存到容器中
//都是通过new或反射调用构造方法来创建实例,以成员变量的形式保存


//创建、准备应用上下文
createApplicationContext()
prepareContext() 

//至此,上下文已完成创建、配置,所有bean定义已加载


//调用高级容器的 refresh() 方法刷新上下文,创建所有非懒加载的单例bean的实例
refreshContext()

 

在 prepareContext() 准备上下文阶段,会调用运行监听器的 contextLoaded() 回调方法

/**
 * 其它回调方法都是直接发布事件,这个回调方法除了发布事件,
 * 还会把之前创建的、来自 spring.factories 的 ApplicationListener 实例,传给创建好的上下文 ApplicationContext
 */
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
	//遍历之前创建的来自 spring.factories 的 ApplicationListener 实例
	for (ApplicationListener<?> listener : this.application.getListeners()) {
		if (listener instanceof ApplicationContextAware) {
			((ApplicationContextAware) listener).setApplicationContext(context);
		}
		//传给 ApplicationContext
		context.addApplicationListener(listener);
	}
	//发布应用已准备好事件 ApplicationPreparedEvent
	this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}

 

高级容器 refresh() 刷新阶段

AbstractApplicationContext 的 refresh()方法

//准备刷新
prepareRefresh();

//初始化事件广播器
initApplicationEventMulticaster();

//注册应用监听器 ApplicationListener
registerListeners();

 

AbstractApplicationContext 中的相关成员变量

/**
 * 内置的事件广播器
 */
@Nullable
private ApplicationEventMulticaster applicationEventMulticaster;


/**
 * 这个成员变量用于存储所有非spring容器方式创建的 ApplicationListener 实例(通常是 spring.factories 方式创建的)
 * 包括spring自身创建的、其它来源的
 * 
 * 
 * AbstractApplicationContext 给这个成员提供了2个public方法对外暴露使用:
 * addApplicationListener() 添加单个监听器、 getApplicationListeners() 获取这个监听器集合
 *
 * final 修饰引用类型,只是不能修改引用本身,可以修改引用指向的对象
 * 下面2个集合因为会初始化、重置为null,引用会变化,没有用final修饰
 */
private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();


/**
 * 存储早期要使用的应用监听器
 * 
 * private,没有提供对外的方法暴露出去,只在内部使用
 */
@Nullable
private Set<ApplicationListener<?>> earlyApplicationListeners;


/**
 * 存储早期事件(在事件广播器创建之前触发的事件)
 */
@Nullable
private Set<ApplicationEvent> earlyApplicationEvents;

 

prepareRefresh() 准备刷新阶段

if (this.earlyApplicationListeners == null) {
	this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
else {
	//如果 earlyApplicationListeners 不是空的,则添加到 applicationListeners 中,后续会以 applicationListeners 进行绑定
	this.applicationListeners.clear();
	this.applicationListeners.addAll(this.earlyApplicationListeners);
}

//初始化存储早期事件的集合
this.earlyApplicationEvents = new LinkedHashSet<>();

 

initApplicationEventMulticaster() 初始化事件广播器阶段

protected void initApplicationEventMulticaster() {
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	//如果容器中有事件广播器的bean定义
	if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
		//则 beanFactory.getBean() 创建实例放到容器中,并赋给 AbstractApplicationContext 的成员变量保存
		this.applicationEventMulticaster =
				beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
		if (logger.isTraceEnabled()) {
			logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
		}
	}
	else {
		//否则直接new一个事件广播器实例,放到容器中,并赋给成员变量
		this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
		beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
		if (logger.isTraceEnabled()) {
			logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
					"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
		}
	}
}

总之就是创建事件广播器的实例,放到容器中,并赋给对应的成员变量。

 

registerListeners() 注册监听器阶段

protected void registerListeners() {
	//添加成员变量形式存储的、所有 spring.factories 方式创建的 ApplicationListener 实例
	for (ApplicationListener<?> listener : getApplicationListeners()) {
		getApplicationEventMulticaster().addApplicationListener(listener);
	}

	//添加spring容器中所有的 ApplicationListener 类型的bean
	//这里是以beanName的形式进行添加,添加的是bean定义中的beanName,对应的监听器实例此时还没有实例化,会在后续步骤进行实例化
	String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
	for (String listenerBeanName : listenerBeanNames) {
		getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
	}

	//广播早期集合中存储的事件,早期事件只广播给 spring.factories 中定义的早期监听器
	//此时事件广播器已经创建,这些spring.factories中的事件监听器都添加到了事件广播器上,可以广播早期事件了
	Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
	this.earlyApplicationEvents = null;
	if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
		for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
			getApplicationEventMulticaster().multicastEvent(earlyEvent);
		}
	}
}

添加了 spring.factories 方式创建的应用监听器、容器中的应用监听器,这才完成了要使用的全部监听器的注册。

 

总结

  • new SpringApplication() 创建SpringApplication实例时:会创建spring.factories中指定的早期监听器,以成员变量的形式保存。
  • 实例 run() 方法启动应用时:在创建默认运行监听器 EventPublishingRunListener 实例时会创建事件广播器实例,这个广播器实例只在默认运行监听器内部使用,绑定了早期监听器,只给这些早期监听器进行广播,广播的是springboot启动的各个阶段,以便在启动的各个阶段提供回调、做自定义的扩展处理。
  • 高级容器 refresh() 刷新阶段:创建了一个广播器实例放到容器中,也赋给了成员变量保存,这个广播器实例添加了所有的应用监听器实例,包括 spring.factories创建的、容器中的,绑定的监听器齐全,主要是spring自身使用以及暴露给开发者使用。

虽然整个启动过程创建了2个事件广播器实例,发生了多次应用监听器的绑定,但作用对象、作用范围各不相同,并不重复、矛盾。

 

事件广播器常见的使用方式

AbstractApplicationContext 自身提供了发布事件的方法

//如果处于早期阶段,尚未创建事件广播器、注册应用监听器,事件不会直接发布,而是暂存在早期事件集合中
if (this.earlyApplicationEvents != null) {
	this.earlyApplicationEvents.add(applicationEvent);
}
else {
	//实质是调用内置的事件广播器的方法广播|发布事件
	getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}

AbstractApplicationContext 以成员变量形式内置的事件广播器、放到容器中的事件广播器,其实是同一个事件广播器实例,使用容器中的事件广播器实例或者AbstractApplicationContext实例发布事件,本质都是一样的。

 

//注入 ApplicationContext 实例。创建的高级容器实例本身也会作为bean实例放到容器中。
//高级容器可以理解为 DefaultListableBeanFactory类中定义的储存 BeanDefinition、beanName、bean实例的一系列集合,使用的高级容器的实现类本身也是一个bean,创建后也会添加、注册上去
@Autowired
private ApplicationContext applicationContext;


//注入容器中的事件广播器实例,类型是 ApplicationEventMulticaster,beanName是对应的camel写法
@Autowired
private ApplicationEventMulticaster applicationEventMulticaster;


public void publish() {
    //事件,参数指定事件源
    MyApplicationEvent myApplicationEvent = new MyApplicationEvent(this);
    //通过容器中的事件广播器实例发布事件
    applicationEventMulticaster.multicastEvent(myApplicationEvent);
    //通过 AbstractApplicationContext 实例发布事件
    applicationContext.publishEvent(myApplicationEvent);
}

作为事件广播器来说,ApplicationContext 是高级容器,包含了太多东西,偏重量级,ApplicationEventMulticaster 比较纯粹,更推荐用 ApplicationEventMulticaster。

 

附录

Runner、运行监听器、springboot的启动日志分析

我在自定义运行监听器的各个回调方法中打印出了回调方法名称,并标注了对应的启动阶段、触发的回调方法、回调方法中发布的事件

"C:\Program Files\Java\jdk1.8.0_281\bin\java.exe" ...

# listeners.starting()中,starting() ,发布应用启动事件 ApplicationStartingEvent
starting...

# prepareEnvironment() 准备环境,environmentPrepared() ,发布环境已准备事件 ApplicationEnvironmentPreparedEvent
environmentPrepared...   


# printBanner() 打印banner。打印的默认banner中会包含springboot的版本
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v2.3.12.RELEASE)


# prepareContext() 准备上下文,contextPrepared() ,发布上下文已初始化事件 ApplicationContextInitializedEvent
contextPrepared... 

# prepareContext()打印的应用启动信息
2021-10-15 15:20:08.098  INFO 50880 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication on DESKTOP-E74978P with PID 50880 (C:\Users\chy\Desktop\demo\target\classes started by chy in C:\Users\chy\Desktop\demo)

# prepareContext()打印的profile激活的配置
2021-10-15 15:20:08.109  INFO 50880 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default


# refreshContext() 刷新上下文,contextLoaded() ,发布应用已准备好事件 ApplicationPreparedEvent
contextLoaded...


#初始化内置tmocat要使用的端口
2021-10-15 15:20:10.434  INFO 50880 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)

#开始启动内置tomcat
2021-10-15 15:20:10.453  INFO 50880 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]

#开启tomcat的servlet引擎,打印信息中包含了内置tomcat的版本
2021-10-15 15:20:10.453  INFO 50880 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.46]

#初始化tomcat本身的应用上下文,打印信息中包含了映射的域名、应用映射的根路径
2021-10-15 15:20:10.604  INFO 50880 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/mall]       : Initializing Spring embedded WebApplicationContext

#tomcat本身的根上下文初始化完成,打印花费的时间
2021-10-15 15:20:10.605  INFO 50880 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2327 ms

#初始化tomcat的执行器
2021-10-15 15:20:11.084  INFO 50880 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'

#内置tomcat启动完成,打印tomcat使用的端口、应用映射的根路径
2021-10-15 15:20:11.643  INFO 50880 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path '/mall'


#至此,应用启动完成,打印启动应用总共花费的时间
2021-10-15 15:20:11.678  INFO 50880 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 4.559 seconds (JVM running for 5.832)


# listeners.started() 执行运行监听器组的 started() 方法,started(),发布应用已启动事件 ApplicationStartedEvent
started...


# callRunners() 执行Runner 回调
ApplicationRunner...
CommandLineRunner...


# listeners.running() 执行运行监听器组的 running() 方法,running(),发布应用就绪事件 ApplicationReadyEvent
running...


#初始化springmvc的核心组件 DispatcherServlet,会作为bean放到容器中,已经打印出了bean的名称 'dispatcherServlet'
2021-10-15 15:20:12.917  INFO 50880 --- [(2)-10.10.10.84] o.a.c.c.C.[Tomcat].[localhost].[/mall]       : Initializing Spring DispatcherServlet 'dispatcherServlet'

#初始化DispatcherServlet这个servlet,把DispatcherServlet配置为servlet
2021-10-15 15:20:12.917  INFO 50880 --- [(2)-10.10.10.84] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'

#DispatcherServlet初始化完毕,打印初始化DispatcherServlet花费的时间
2021-10-15 15:20:12.929  INFO 50880 --- [(2)-10.10.10.84] o.s.web.servlet.DispatcherServlet        : Completed initialization in 12 ms

可以看到,在应用启动完成后会初始化 springmvc | spring webflux 的核心组件。

如果要查看更详细的启动信息,可以将 debug 设置为 true,或者将日志级别设置为 debug。

 

关于 refresh() 方法的名称
  • 粉刷墙壁,材料都准备好了,在墙上刷一下砂灰就出来了
  • 魔术师双手拿着一块布,遮住一个准备好的盘子,突然移开布(刷一下),变出2个苹果
  • 法海让小和尚准备好了一个大缸,老和尚大袖子遮住缸,手一挥(刷一下),变出了一缸子的水
  • 容器已经创建、初始化,所有bean定义都已解析、加载,刷一下(调用refresh方法),容器中变出了所有单例bean(懒加载除外)的实例

都是准备好环境、材料,刷一下,想要的东西、效果就有了,现在是不是觉得 refresh 刷新 这个方法名很形象?spring就是这么神奇,刷一下就有了。

 

关于spring、springboot的名称

bean 豆子 => bean定义

spring 春天 => 春回大地,一场春雨(refresh方法)过后,刷新大地,万物复苏,豆子萌芽茁壮成长(bean实例化)

springboot => boot 引导、启动,可以看做spring的脚手架,用于快速搭建spring项目

你可能感兴趣的:(SpringBoot,spring,boot,启动流程,应用监听器,运行监听器,上下文)