跟我一起阅读SpringBoot源码(十)——侦听器

454 阅读10分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

Spring里面有太多侦听器了,估计一时半会儿看不完,只能跟随着看SpringBoot的源码看点记点了。

EventListener

我们先看看侦听器的鼻祖:

package java.util;

/**
 * A tagging interface that all event listener interfaces must extend.
 * @since JDK1.1
 */
public interface EventListener {
}

JDK协议里面规定了,所有的事件侦听器必须继承EventListener 接口。

ApplicationListener

再来看看Spring里面侦听器的鼻祖:

/**
 * 由应用程序事件侦听器实现的接口。
 * 基于Observer设计模式的标准java.util.EventListener接口。
 * 从Spring 3.0开始, ApplicationListener可以一般性地声明其感兴趣的事件类型。在Spring ApplicationContext注册时,将相应地过滤事件,并且仅针对匹配事件对象调用侦听器。
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @param <E> the specific {@code ApplicationEvent} subclass to listen to
 * @see org.springframework.context.ApplicationEvent
 * @see org.springframework.context.event.ApplicationEventMulticaster
 * @see org.springframework.context.event.EventListener
 */
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);

}

只有一个onApplicationEvent接口; 再看看侦听器的实: 在这里插入图片描述 光spring里面的实现就有32个,如果每个都看那得看死个人哦,下面就分事件来看看侦听器吧,当然侦听器和事件的对应关系不是一一对应的,那就按阅读源码遇到的侦听器顺序来排列文章结构吧。

LoggingApplicationListener

/**
 * An {@link ApplicationListener} that configures the {@link LoggingSystem}. If the
 * environment contains a {@code logging.config} property it will be used to bootstrap the
 * logging system, otherwise a default configuration is used. Regardless, logging levels
 * will be customized if the environment contains {@code logging.level.*} entries and
 * logging groups can be defined with {@code logging.group}.
 * <p>
 * Debug and trace logging for Spring, Tomcat, Jetty and Hibernate will be enabled when
 * the environment contains {@code debug} or {@code trace} properties that aren't set to
 * {@code "false"} (i.e. if you start your application using
 * {@literal java -jar myapp.jar [--debug | --trace]}). If you prefer to ignore these
 * properties you can set {@link #setParseArgs(boolean) parseArgs} to {@code false}.
 * <p>
 * By default, log output is only written to the console. If a log file is required, the
 * {@code logging.file.path} and {@code logging.file.name} properties can be used.
 * <p>
 * Some system properties may be set as side effects, and these can be useful if the
 * logging configuration supports placeholders (i.e. log4j or logback):
 * <ul>
 * <li>{@code LOG_FILE} is set to the value of path of the log file that should be written
 * (if any).</li>
 * <li>{@code PID} is set to the value of the current process ID if it can be determined.
 * </li>
 * </ul>
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @author Madhura Bhave
 * @author HaiTao Zhang
 * @since 2.0.0
 * @see LoggingSystem#get(ClassLoader)
 */
public class LoggingApplicationListener implements GenericApplicationListener {

大意就是说: LoggingApplicationListener是一个LoggingSystem配置的侦听器,如果环境中有配置logging.config属性,那么这将用于引导启动日志系统,否则将采用默认配置。日志级别可以通过logging.level.*进行自定义,日志组通过logging.group进行配置。 如果环境中包含为被设置成false的debug或trace属性,那么Spring, Tomcat, Jetty 和 Hibernate 的Debug和trace日志将被启动。(i.e. 如果你启动你的应用以这种方式:java -jar myapp.jar [--debug | --trace],如果你想忽略这些属性,你可以设置parseArgs为false[setParseArgs(boolean)方法]) 默认情况下,日志只会被输出到控制台,如果需要一个日志文件,通过配置logging.file.pathlogging.file.name来实现。 有些系统设置可能会被作为侧面效果进行设置,如果日志配置支持占位符(i.e. log4j or logback),那么他们可以被用到。

看看这个侦听器的onApplicationEvent方法:

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationStartingEvent) {
			onApplicationStartingEvent((ApplicationStartingEvent) event);
		}
		else if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		else if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent((ApplicationPreparedEvent) event);
		}
		else if (event instanceof ContextClosedEvent
				&& ((ContextClosedEvent) event).getApplicationContext().getParent() == null) {
			onContextClosedEvent();
		}
		else if (event instanceof ApplicationFailedEvent) {
			onApplicationFailedEvent();
		}
	}

ApplicationStartingEvent

先看下ApplicationStartingEvent事件的处理:

	private void onApplicationStartingEvent(ApplicationStartingEvent event) {
		//先加载一个日志系统,然后用加载的日志系统执行一个beforeInitialize方法。
		this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
		this.loggingSystem.beforeInitialize();
	}

初始化日志系统

来看看日志系统的加载方法:

/**
	 *一个System属性,可用于指示要使用的LoggingSystem 。
	 */
	public static final String SYSTEM_PROPERTY = LoggingSystem.class.getName();
	/**
	 *检测并返回正在使用的日志系统。支持Logback和Java日志记录。
	 * @param classLoader the classloader
	 * @return the logging system
	 */
	public static LoggingSystem get(ClassLoader classLoader) {
		String loggingSystem = System.getProperty(SYSTEM_PROPERTY);
		if (StringUtils.hasLength(loggingSystem)) {
			if (NONE.equals(loggingSystem)) {
				return new NoOpLoggingSystem();
			}
			return get(classLoader, loggingSystem);
		}
		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"));
	}

从这里可以看到,我们是可以在通过配置系统属性org.springframework.boot.logging.LoggingSystem来指定采用哪个日志系统的,如果org.springframework.boot.logging.LoggingSystem的值是none,则会构建一个空操作日志系统(该日志系统不会做任何事情)。 如果没有系统配置,则会SYSTEMS中去选取一个存在的日志系统进行实例化。 看下SYSTEMS里面提供的日志系统有哪些:

static {
		Map<String, String> systems = new LinkedHashMap<>();
		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);
	}

debug可以看到最后我们采用的是: 在这里插入图片描述

在这里插入图片描述 其实这三个日志系统spring都有提供的,这里为什么要执行一波判断然后再选第一个呢?而且SYSTEMS 也是不可修改的map,这点没搞懂

日志系统干的事无非就是记录日志打印日志,先不看了。

ApplicationEnvironmentPreparedEvent

看看这个侦听者在环境准备完成事件发生时要干什么:

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		if (this.loggingSystem == null) {
			this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
		}
		initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
	}
	/**
	 * 根据环境和classpath对日志系统进行一些设置
	 * @param environment the environment
	 * @param classLoader the classloader
	 */
	protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
		new LoggingSystemProperties(environment).apply();	
		this.logFile = LogFile.get(environment);
		if (this.logFile != null) {
			this.logFile.applyToSystemProperties();
		}
		this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
		initializeEarlyLoggingLevel(environment);
		initializeSystem(environment, this.loggingSystem, this.logFile);
		initializeFinalLoggingLevels(environment, this.loggingSystem);
		registerShutdownHookIfNecessary(environment, this.loggingSystem);
	}

ClasspathLoggingApplicationListener

直接上onApplicationEvent方法吧:

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (logger.isDebugEnabled()) {
			if (event instanceof ApplicationEnvironmentPreparedEvent) {
				logger.debug("Application started with classpath: " + getClasspath());
			}
			else if (event instanceof ApplicationFailedEvent) {
				logger.debug("Application failed to start with classpath: " + getClasspath());
			}
		}
	}

感觉也没什么好说的,下一个

BackgroundPreinitializer

在这里插入图片描述

/**
 * {@link ApplicationListener} to trigger early initialization in a background thread of
 * time consuming tasks.
 * <p>
 * Set the {@link #IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME} system property to
 * {@code true} to disable this mechanism and let such initialization happen in the
 * foreground.
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @author Artsiom Yudovin
 * @since 1.3.0
 */
@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer implements ApplicationListener<SpringApplicationEvent> {

目前貌似看不懂,往后看,看onApplicationEvent方法:

	@Override
	public void onApplicationEvent(SpringApplicationEvent event) {
		if (!Boolean.getBoolean(IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME)
				&& event instanceof ApplicationStartingEvent && multipleProcessors()
				&& preinitializationStarted.compareAndSet(false, true)) {
			performPreinitialization();
		}
		if ((event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent)
				&& preinitializationStarted.get()) {
			try {
				preinitializationComplete.await();
			}
			catch (InterruptedException ex) {
				Thread.currentThread().interrupt();
			}
		}
	}

	/**
	 * 指示Spring Boot如何运行预初始化的系统属性。 当该属性设置为true ,不进行任何预初始化,并且每一项都需要在前台进行初始化。
	 * 当该属性为false (默认值)时,预初始化在后台的单独线程中运行。
	 * @since 2.1.0
	 */
	public static final String IGNORE_BACKGROUNDPREINITIALIZER_PROPERTY_NAME = "spring.backgroundpreinitializer.ignore";
	//执行类下,当前可用线程是大于1的。这个属性好像更系统配置有关。
	private boolean multipleProcessors() {
		return Runtime.getRuntime().availableProcessors() > 1;
	}
//这里用的是原子性Boolean对象,保证后面比较交换过程中的线程安全性。避免与初始化多次。
private static final AtomicBoolean preinitializationStarted = new AtomicBoolean(false);

可以看到默认情况下ApplicationStartingEvent事件发生时会执行预初始化,ApplicationReadyEvent或ApplicationFailedEvent事件发生的时候都会等待预初始化过程完成。

这里我并没发现任何地方有将preinitializationStarted设置会false的地方,感觉这个地方是有什么没看到的。这里有点看不懂。

performPreinitialization

看看performPreinitialization方法吧:

	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 JacksonInitializer());
					runSafely(new CharsetInitializer());
					preinitializationComplete.countDown();
				}

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

			}, "background-preinit");
			thread.start();
		}
		catch (Exception ex) {
			// This will fail on GAE where creating threads is prohibited. We can safely
			// continue but startup will be slightly slower as the initialization will now
			// happen on the main thread.
			preinitializationComplete.countDown();
		}
	}

看了下代码,似懂非懂,先留坑。

DelegatingApplicationListener

/**
 *委托给在context.listener.classes环境属性下指定的其他侦听器的ApplicationListener 。
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @since 1.0.0
 */
public class DelegatingApplicationListener implements ApplicationListener<ApplicationEvent>, Ordered {

看下这个侦听器的onApplicationEvent:

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
					((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
			if (delegates.isEmpty()) {
				return;
			}
			this.multicaster = new SimpleApplicationEventMulticaster();
			for (ApplicationListener<ApplicationEvent> listener : delegates) {
				this.multicaster.addApplicationListener(listener);
			}
		}
		if (this.multicaster != null) {
			this.multicaster.multicastEvent(event);
		}
	}
@SuppressWarnings("unchecked")
	private List<ApplicationListener<ApplicationEvent>> getListeners(ConfigurableEnvironment environment) {
		if (environment == null) {
			return Collections.emptyList();
		}
		String classNames = environment.getProperty(PROPERTY_NAME);
		List<ApplicationListener<ApplicationEvent>> listeners = new ArrayList<>();
		if (StringUtils.hasLength(classNames)) {
			for (String className : StringUtils.commaDelimitedListToSet(classNames)) {
				try {
					Class<?> clazz = ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
					Assert.isAssignable(ApplicationListener.class, clazz,
							"class [" + className + "] must implement ApplicationListener");
					listeners.add((ApplicationListener<ApplicationEvent>) BeanUtils.instantiateClass(clazz));
				}
				catch (Exception ex) {
					throw new ApplicationContextException("Failed to load context listener class [" + className + "]",
							ex);
				}
			}
		}
		AnnotationAwareOrderComparator.sort(listeners);
		return listeners;
	}
	private static final String PROPERTY_NAME = "context.listener.classes";

只有当环境准备完成事件发生的时候,然后获取通过配置的context.listener.classes属性来获取侦听器实例,然后把这些侦听器加到应用的侦听器集合里面,然后再次发布这个事件。

这个地方get到我们可以通过实现ApplicationListener的方式来自定义侦听器,通过context.listener.classes属性配置的方式将侦听器注册到Spring里面。 接着我们自定义的监听器就可以监听spring里面的事件了。

LiquibaseServiceLocatorApplicationListener

在这里插入图片描述

/**
 * {@link ApplicationListener} that replaces the liquibase {@link ServiceLocator} with a
 * version that works with Spring Boot executable archives.
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @since 1.0.0
 */
public class LiquibaseServiceLocatorApplicationListener implements ApplicationListener<ApplicationStartingEvent> {

没多大看懂,直接看onApplicationEvent方法:

	@Override
	public void onApplicationEvent(ApplicationStartingEvent event) {
		if (ClassUtils.isPresent("liquibase.servicelocator.CustomResolverServiceLocator",
				event.getSpringApplication().getClassLoader())) {
			new LiquibasePresent().replaceServiceLocator();
		}
	}

在这里插入图片描述 看来又是一个超纲知识,先pass。

ConfigFileApplicationListener

/**
 * EnvironmentPostProcessor通过从众所周知的位置加载属性来配置上下文。默认情况下这些属性会被从以下这些目录下的`application.properties`和/或`application.yml`加载:

 - file:./config/
 - file:./
 - classpath:config/ 
 - classpath:

 这个集合是按优先级排序的。(高优先级目录下的属性将覆盖低优先级目录下的属性)
 另外可以使用setSearchLocations(String)和setSearchNames(String)来指定搜索路径和文件名称。
 还将根据活动配置文件加载其他文件。 例如,如果“ Web”配置文件处于活动状态,则将考虑“ application-web.properties”和“ application-web.yml”。
 “ spring.config.name”属性可用于指定要加载的备用名称,“ spring.config.location”属性可用于指定备用搜索位置或特定文件。
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @author Eddú Meléndez
 * @author Madhura Bhave
 * @since 1.0.0
 */
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {

看来这个侦听器是去加载配置文件的。 看下onApplicationEvent方法:

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}

看来,这个侦听器会在环境准备好的时候和应用程序准备好的时候被called。

ApplicationEnvironmentPreparedEvent

先看看环境准备好的时候,这个侦听器做了什么:

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
		}
	}

现在加载出EnvironmentPostProcessor,EnvironmentPostProcessor好像在前面哪里看过,再看下注释: 在这里插入图片描述 目前加上自己一共是5个处理器,为了结构所以不在这里写了,要看明细移步:跟我一起阅读SpringBoot源码(十一)——EnvironmentPostProcessor

ApplicationPreparedEvent

应用程序准备好事件尚未发布,等发布的时候再来看~~~~

AnsiOutputApplicationListener

/**
 * An {@link ApplicationListener} that configures {@link AnsiOutput} depending on the
 * value of the property {@code spring.output.ansi.enabled}. See {@link Enabled} for valid
 * values.
 *
 * @author Raphael von der Grün
 * @author Madhura Bhave
 * @since 1.2.0
 */
public class AnsiOutputApplicationListener
		implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {

通过 spring.output.ansi.enabled来配置AnsiOutput的侦听器。Enabled是一个枚举。 先看看AnsiOutput的定义:

/**
 * 生成ANSI编码的输出,自动尝试检测终端是否支持ANSI。
 *
 * @author Phillip Webb
 * @since 1.0.0
 */
 public abstract class AnsiOutput {

输出ANSI编码???? 再看看Enabled:

	/**
	 * 可能的值传递给setEnabled 。 确定何时为着色应用程序输出输出ANSI转义序列。
	 */
	public enum Enabled {

		/**
		 * 尝试检测ANSI着色功能是否可用。 AnsiOutput的默认值。
		 */
		DETECT,

		/**
		 * 启用ANSI彩色输出。
		 */
		ALWAYS,

		/**
		 * 禁用ANSI彩色输出。
		 */
		NEVER

	}

试了下,没啥效果,不知道怎么用的。

FileEncodingApplicationListener

在这里插入图片描述

	@Override
	public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
		ConfigurableEnvironment environment = event.getEnvironment();
		if (!environment.containsProperty("spring.mandatory-file-encoding")) {
			return;
		}
		String encoding = System.getProperty("file.encoding");
		String desired = environment.getProperty("spring.mandatory-file-encoding");
		if (encoding != null && !desired.equalsIgnoreCase(encoding)) {
			if (logger.isErrorEnabled()) {
				logger.error("System property 'file.encoding' is currently '" + encoding + "'. It should be '" + desired
						+ "' (as defined in 'spring.mandatoryFileEncoding').");
				logger.error("Environment variable LANG is '" + System.getenv("LANG")
						+ "'. You could use a locale setting that matches encoding='" + desired + "'.");
				logger.error("Environment variable LC_ALL is '" + System.getenv("LC_ALL")
						+ "'. You could use a locale setting that matches encoding='" + desired + "'.");
			}
			throw new IllegalStateException("The Java Virtual Machine has not been configured to use the "
					+ "desired default character encoding (" + desired + ").");
		}
	}

如果环境配置了spring.mandatory-file-encoding属性,则从系统配置中获取file.encoding配置,如果spring.mandatory-file-encodingfile.encoding的配置不同,则抛出异常终止Spring应用。

侦听Spring上下文初始化的侦听器

先来看看SpringBoot启动过程中,准备好上下文的事件侦听器(目前得知的是4个):

很显然这个侦听器并不是只侦听应用的启动,还包括环境的准备完成,应用准备完成,上下文关闭,应用失败这些事件。 目前读源码还在应用启动阶段,就先看看onApplicationStartingEvent事件吧。

private void onApplicationStartingEvent(ApplicationStartingEvent event) {
		/获取当前的日志系统并设置给侦听器。
		this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
		//然后执行beforeInitialize操作
		this.loggingSystem.beforeInitialize();
	}

这里就先不去看怎么获取日志系统的了,这个在前面看过LogbackLoggingSystem 在这里插入图片描述 看下LogbackLoggingSystem的beforeInitialize方法:

@Override
	public void beforeInitialize() {
		LoggerContext loggerContext = getLoggerContext();
		if (isAlreadyInitialized(loggerContext)) {
			return;
		}
		super.beforeInitialize();
		loggerContext.getTurboFilterList().add(FILTER);
	}

太细了,看不了了,先跳过。