【springboot源码解读系列】(四、springboot启动之SpringApplicationRunListeners:通知所有监听器,系统启动中...用于非常早的初始化)

SpringApplicationRunListeners系统运行监听器,监听者springboot项目运行各个生命周期,这节讲解starting,用于系统很早的初始化扩展,如果不想看细节,直接想了解SpringApplicationRunListeners各个周期的朋友,直接划到文章末尾,有对各个周期的详细注释,它的周期是围绕着项目启动来做的。

在【springboot源码解读系列】(一、springboot创建SpringApplication实例,定制SpringApplication)中我们讲到了,在创建SpringApplication实例的时候,构造函数中有如下两段代码:

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

分别是设置系统启动器和设置监听器,而他们的原理就是通过SpringFactoriesLoader来实现的,然后将加载的存放在SpringApplication的属性中,之前没有讲解具体的实现。

今天来具体将一下getRunListeners方法获取所有的SpringApplicationRunListener(启动监听器,用于非常早的初始化),和设置启动器和系统监听器原理一模一样,调用的方法都是一样的。

// 所有的系统启动监听器都存放在listeners对象中,这里只暴露出来一个对象,将集合放在实例中。
// 调用的时候直接调用一个方法,封装得特别好
SpringApplicationRunListeners listeners = getRunListeners(args);

此方法在run方法体中,在实际启动开始之前。

public ConfigurableApplicationContext run(String... args) {

		// 创建一个简单的秒表实例(其实并不简单)
		StopWatch stopWatch = new StopWatch();
		// 开始计时,内部使用的是System.nanoTime(),纳秒级别的
		stopWatch.start();
		// 声明一个上下文实例:context,并且复制为null
		ConfigurableApplicationContext context = null;
		// 创建一个SpringBootExceptionReporter集合。他是用于定制异常报告的,
		// 简单点来说,我们可以对抛出的异常进行定制化输入,
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		// 设置无头属性:java.awt.headless,
		// 该应用程序,即使没有检测到显示器,也允许其启动.
		// 对于服务器来说,是不需要显示器的,所以要这样设置.
		configureHeadlessProperty();
		// 获取所有的spring应用启动监听器,同样也是通过SpringFactoriesLoader获取的监听器
		// 并将其方式SpringApplicationRunListeners中的listeners属性中,他是使用final修饰的,
		// 在调用SpringApplicationRunListeners构造函数的时候赋值的。赋值之后不能修改
		SpringApplicationRunListeners listeners = getRunListeners(args);
		// 调用所有监听器的starting方法,通知各个监听器,spring开始启动了。要干事情的准备开干了
		listeners.starting();
		...
		return context;
	}

看getRunListeners方法:

	// 获取所有的SpringApplicationRunListener的实例
	private SpringApplicationRunListeners getRunListeners(String[] args) {
		// 创建一个Class数组,里面包含SpringApplication和String[]的Class,具体是干什么的,还不清楚
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		// 这里分两步走
		// 1、调用getSpringFactoriesInstances方法。获取所有的SpringApplicationRunListener实现类的实例,
		// 2、创建SpringApplicationRunListeners实例,并将所有的自定义应用启动监听器放入listeners属性中
		// 然后将其返回
		return new SpringApplicationRunListeners(logger,
				getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
	}

第一步:调用getSpringFactoriesInstances方法。获取所有的SpringApplicationRunListener实现类的实例,

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		// 获取ClassLoader类加载器
		ClassLoader classLoader = getClassLoader();
		// 使用名称,并确保唯一,所以使用Set集合。因为加载的是全限定类名
		// 如果名称重复,那么就会创建多个实例,最后导致同一个监听器调用两次的情况
		// SpringFactoriesLoader是spring.core.io包下面的一个加载器
		// 后面讲解关于SpringFactoriesLoader的
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		// 获取到所有的names,然后根据反射创建实例。
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		// 对所有的实例进行排序(根据类上面的Order注解进行排序)
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

通过反射创建所有的实例createSpringFactoriesInstances。

	private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
		// 创建一个实例集合
		List<T> instances = new ArrayList<>(names.size());
		for (String name : names) {
			try {
				// 通过反射获取Class
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				// 判断Class是否数据传进来的类型。如果不是,则报错。
				Assert.isAssignable(type, instanceClass);
				// 通过获取构造器,parameterTypes : SpringApplication.class, String[].class
				// 通过getDeclaredConstructor获取构造器,可以根据构造器创建有参或者无参的实例
				Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				// 通过BeanUtils获取对象实例
				// BeanUtils是spring.bean下面的一个工具类
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				// 将创建的实例加入到实例集合中
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}

2、创建SpringApplicationRunListeners实例,并将所有的自定义应用启动监听器放入listeners属性中

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

获取完之后调用listeners.starting();方法通知所有的监听器:
此方法为run方法第一次启动时立即调用的,可以用于非常早期的初始化。

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

starting方法是SpringApplicationRunListener接口的默认方法,如果实现的话,就调用其实现的,如果没有实现,那么他就调用默认空方法。

public interface SpringApplicationRunListener {

	/**
	 *
	 * 当run方法第一次启动时立即调用。可以用于非常早期的初始化。
	 */
	default void starting() {
	}
}

SpringApplicationRunListener 接口中有比较多的生命周期函数提供给我们扩展,我们可以在各个周期对自己的项目进行定制扩展。

public interface SpringApplicationRunListener {

	/**
	 *
	 * 当run方法第一次启动时立即调用。可以用于非常早期的初始化。
	 */
	default void starting() {
	}

	/**
	 * 在准备环境之后,但在创建ApplicationContext之前调用。
	 */
	default void environmentPrepared(ConfigurableEnvironment environment) {
	}

	/**
	 * 一旦创建并准备好了ApplicationContext,但是在加载源之前调用。
	 */
	default void contextPrepared(ConfigurableApplicationContext context) {
	}

	/**
	 * 在加载应用程序上下文之前调用
	 * @param context the application context
	 */
	default void contextLoaded(ConfigurableApplicationContext context) {
	}

	/**
	 * 上下文已经刷新并且应用程序已经启动,但是ApplicationRunner还没有通知去执行的,
	 * @since 2.0.0
	 */
	default void started(ConfigurableApplicationContext context) {
	}

	/**
	 * 当应用程序上下文被刷新并且所有
	 * {@link ApplicationRunner ApplicationRunners}和{@link ApplicationRunner ApplicationRunners}
	 * 都被调用时,在run方法结束之前立即调用。
	 * @param context the application context.
	 * @since 2.0.0
	 */
	default void running(ConfigurableApplicationContext context) {
	}

	/**
	 * 在运行应用程序时发生故障时调用。
	 * 
	 * @param context the application context or {@code null} if a failure occurred before
	 * the context was created
	 * @param exception the failure
	 * @since 2.0.0
	 */
	default void failed(ConfigurableApplicationContext context, Throwable exception) {
	}

}

一个相信努力就会有结果的程序员,以兴趣驱动技术!         ------ CoderOu

你可能感兴趣的:(Springboot,源码,spring,boot)