【springboot】启动流程之starting

一、概述

starting作为springboot启动流程中最早的一个生命周期,过程相对比较简洁因此源码也相对较少,比较适合入门者的源码研究。在此阶段中spring主要完成了日志系统的选择、后台预加载等动作。

二、SpringApplicationRunListener和ApplicationListener

上一篇文章中的最后一小节,我们可以发现在进行第一个生命周期回调(也就是starting)前,spring先通过getRunListeners方法获取了一个SpringApplicationRunListeners对象,可是通过下面源码发现这个对象并不是ApplicationListener的集合而是SpringApplicationRunListener的集合(虽然factories文件中默认只定义了一个类EventPublishingRunListener),那么这两者究竟有啥区别又有啥关联呢?

// org.springframework.boot.SpringApplication#getRunListeners
private SpringApplicationRunListeners getRunListeners(String[] args) {
	Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
	return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
			SpringApplicationRunListener.class, types, this, args));
}

细心的读者可能已经发现在SpringApplication的构造方法中已经通过setListeners将ApplicationListener注册进来了,从某种意义上来说后来构造的SpringApplicationRunListener其实是ApplicationListener的一个大管家。为什么这么说?来看下源码便一目了然。

// org.springframework.boot.context.event.EventPublishingRunListener
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();
	// 关键代码——将SpringApplication的ApplicationListener包含进来
	for (ApplicationListener<?> listener : application.getListeners()) {
		this.initialMulticaster.addApplicationListener(listener);
	}
}

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

// 省略其他生命周期方法...

EventPublishingRunListener作为SpringApplicationRunListener的默认唯一实现,在构造的时候就将springApplication对象中的ApplicationListener添加进来并进行统一管理。从类的命名可以看出,这个类就是在观察者模式的基础上实现发布订阅,当启动流程执行到某一个阶段后,该类会生成一个生命周期事件(该事件类继承于java.util.EventObject),并将这个事件广播给已经注册的ApplicationListener,从而执行对应的钩子方法。感兴趣的读者可以看下这个类的完整源码,springboot的每一个生命周期都对应了该类的一个方法。

三、事件广播

通过上面代码可以发现有一个类也非常关键,那就是SimpleApplicationEventMulticaster,如果说SpringApplicationRunListener是一个大管家的话,那么SimpleApplicationEventMulticaster则是真正传话干活的人,spring的观察者们直接打交道的对象就是它。在每个生命周期节点,大管家也是调用它的multicastEvent方法进行事件发布。
SimpleApplicationEventMulticaster
通过下面源码可以看出multicastEvent方法其实就是遍历ApplicationListener对象,并进行钩子方法的回调。不过有一点值得一提的是getApplicationListeners也并非返回所有的ApplicationListener,而是会根据当前生命周期来进行过滤并排序,过滤的方式有两种:一种是利用反射获取Listener类声明的ApplicationEvent泛型;还有一种是根据观察者的supportsEventType方法的返回值(布尔类型)来判断。详细的过程可以参考AbstractApplicationEventMulticaster的supportsEvent方法,这里就不多展开。

// org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		Executor executor = getTaskExecutor();
		if (executor != null) {
			executor.execute(() -> invokeListener(listener, event));
		}
		else {
			// 相当于最后调用了listener.onApplicationEvent(event);
			invokeListener(listener, event);
		}
	}
}

四、观察者们

在starting这个生命周期中,getApplicationListeners方法最终会过滤出如下几个ApplicationListener。其中第二个和第四个什么也没干所以忽略不计,第三个是我在factories文件中自己定义的观察者,仅仅只是打印了一句话而已。所以在starting中我们重点关注的观察者还是前两个。他们分别用于选择日志框架,以及将一些耗时的初始化在后台进行。
ApplicationListener(starting)

1. LoggingApplicationListener

先来看下日志观察者在启动时的源码,它主要执行了两个步骤:第一个是扫描类路径选择日志框架,第二个是预初始化日志框架

// org.springframework.boot.context.logging.LoggingApplicationListener#onApplicationStartingEvent
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
	this.loggingSystem = LoggingSystem
			.get(event.getSpringApplication().getClassLoader());
	this.loggingSystem.beforeInitialize();
}

选择日志框架主要是在LoggingSystem的工厂方法get中完成的,可以发现在这个类初始化的时候会定义一个有序的Map,第一顺位是logback日志框架,第二顺位是log4j,最后一个是JDK日志。spring首先会过滤出类路径下存在的这几个框架,若存在多个则找到第一个。寻找完毕后通过反射的方式将对应的LoggingSystem实例化。

// org.springframework.boot.logging.LoggingSystem
static {
	Map<String, String> systems = new LinkedHashMap<>();
	// key是具体日志框架的关键类,value是spring的适配类
	systems.put("ch.qos.logback.core.Appender",
			"org.springframework.boot.logging.logback.LogbackLoggingSystem");
	systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
			"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
	systems.put("java.util.logging.LogManager",
			"org.springframework.boot.logging.java.JavaLoggingSystem");
	SYSTEMS = Collections.unmodifiableMap(systems);
}
public static LoggingSystem get(ClassLoader classLoader) {
	String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
	// 若JVM参数配置了日志系统,则使用该配置的框架
	if (StringUtils.hasLength(loggingSystem)) {
		if (NONE.equals(loggingSystem)) {
			return new NoOpLoggingSystem();
		}
		return get(classLoader, loggingSystem);
	}
	// 找出类路径下存在日志框架,并实例化对应的spring适配类
	return SYSTEMS.entrySet().stream()
			.filter((entry) -> ClassUtils.isPresent(entry.getKey(), classLoader))
			.map((entry) -> get(classLoader, entry.getValue())).findFirst()
			.orElseThrow(() -> new IllegalStateException(
					"No suitable logging system located"));
}

private static LoggingSystem get(ClassLoader classLoader, String loggingSystemClass) {
	try {
		Class<?> systemClass = ClassUtils.forName(loggingSystemClass, classLoader);
		// spring的LoggingSystem类都有单classLoader参数的构造方法
		return (LoggingSystem) systemClass.getConstructor(ClassLoader.class)
				.newInstance(classLoader);
	}
	catch (Exception ex) {
		throw new IllegalStateException(ex);
	}
}

选择完日志框架后还有个预初始化,对应的方法为beforeInitialize,这个过程主要会和底层的日志框架交互,所以就不过多展开,简单地看下流程。由于这个方法是个抽象方法,所以我以logback为例。getLoggerContext方法是获取logback的LoggerFactory;然后调用了父类的beforeInitialize方法,这个方法主要是配置一些BridgeHandler。

// org.springframework.boot.logging.logback.LogbackLoggingSystem#beforeInitialize
@Override
public void beforeInitialize() {
	LoggerContext loggerContext = getLoggerContext();
	if (isAlreadyInitialized(loggerContext)) {
		return;
	}
	super.beforeInitialize();
	loggerContext.getTurboFilterList().add(FILTER);
}

2. BackgroundPreinitializer

从这个类名中也可以推断它是用于在后台初始化某些对象。例如Jackson的objectMapper对象的创建、MessageConverter的创建等都是在这个对象的后台线程内完成的。源码中初始化的过程也是比较值得借鉴的,将一些目前无关紧要但是后续会经常用到的对象在一开始通过一个独立线程去完成,在某一阶段需要这些对象存在时可以通过CountDownLatch.await来确保初始化过程全部结束。

// org.springframework.boot.autoconfigure.BackgroundPreinitializer
@Override
public void onApplicationEvent(SpringApplicationEvent event) {
	/*
	满足三个条件开始后台初始化
	1. 没有配置JVM参数"spring.backgroundpreinitializer.ignore"
	2. 当前生命周期是starting
	3. 能够成功将初始化标志位设置为true(CAS的方式)
	*/
	if (!Boolean.getBoolean("spring.backgroundpreinitializer.ignore")
			&& event instanceof ApplicationStartingEvent
			&& preinitializationStarted.compareAndSet(false, true)) {
		// 启用一个线程进行一些初始化工作
		performPreinitialization();
	}
	if ((event instanceof ApplicationReadyEvent
			|| event instanceof ApplicationFailedEvent)
			&& preinitializationStarted.get()) {
		try {
			// 这个对象是CountDownLatch,若初始化在ready和failed阶段还未完成,则会等待初始化完毕
			preinitializationComplete.await();
		}
		catch (InterruptedException ex) {
			Thread.currentThread().interrupt();
		}
	}
}

private void performPreinitialization() {
	try {
		// 单线程顺序执行下面的初始化器
		Thread thread = new Thread(new Runnable() {

			@Override
			public void run() {
				runSafely(new ConversionServiceInitializer());
				runSafely(new ValidationInitializer());
				runSafely(new MessageConverterInitializer());
				runSafely(new MBeanFactoryInitializer());
				runSafely(new JacksonInitializer());
				runSafely(new CharsetInitializer());
				// 所有初始化器执行完毕则释放CountDownLatch
				preinitializationComplete.countDown();
			}

			// 忽略异常
			public void runSafely(Runnable runnable) {
				try {
					runnable.run();
				}
				catch (Throwable ex) {
					// Ignore
				}
			}

		}, "background-preinit");
		thread.start();
	}
	catch (Exception ex) {
		// 创建或执行线程失败,释放CountDownLatch
		preinitializationComplete.countDown();
	}
}

五、参考

spring-boot-2.0.3启动源码篇二 - run方法(一)之SpringApplicationRunListener

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值