SpringBoot之事件发布和监听——ApplicationListener.class


本文将会通过解读SpringBoot的源码,从中学习如何使用springboot提供的事件机制,并了解其原理。最后会有示例代码,实例代码中会展示springboot的从启动到关闭的生命周期的事件监听。

1. 接口类ApplicationListener.class

在开始解读源码之前,还是先认识一些这个接口,此即可是spring中定义的监听事件的回调接口,接口中只有一个方法需要实现。所有基于spring事件的功能,都需要实现该接口,并且将实现类配置打环境中。接口源码如下:

package org.springframework.context;

import java.util.EventListener;

/**
 * Interface to be implemented by application event listeners.
 * Based on the standard {@code java.util.EventListener} interface
 * for the Observer design pattern.
 *
 * <p>As of Spring 3.0, an ApplicationListener can generically declare the event type
 * that it is interested in. When registered with a Spring ApplicationContext, events
 * will be filtered accordingly, with the listener getting invoked for matching event
 * objects only.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @param <E> the specific ApplicationEvent subclass to listen to
 * @see org.springframework.context.event.ApplicationEventMulticaster
 */
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

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

}

1.1. 接口如何使用

  • ApplicationListener继承了 java.util.EventListener,这没什么可说的,就是一个标记型接口而已。
  • 此接口有个泛型<E extends ApplicationEvent>,这个泛型对象必须是事件对象ApplicationEvent或这ApplicationEvent的子类。可以看出来,当实现ApplicationListener的实现类时,是可以通过泛型来指定监听哪些类型的事件的,如果需要监听全部事件,泛型可以就简单粗暴的写<ApplicationEvent>
  • 所以可以根据需要,自定义一个ApplicationEvent的子类。
  • 需要实现方法void onApplicationEvent(E event);参数中会得到一个泛型指定的事件对象。

1.2. 自定义事件对象

有了监听这接口,当然还需要事件对象,事件对象必须继承ApplicationEvent。
ApplicationEvent是一个抽象类,源码如下:

/*
 * Copyright 2002-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context;

import java.util.EventObject;

/**
 * Class to be extended by all application events. Abstract as it
 * doesn't make sense for generic events to be published directly.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
public abstract class ApplicationEvent extends EventObject {

	/** use serialVersionUID from Spring 1.2 for interoperability */
	private static final long serialVersionUID = 7099057708183571937L;

	/** System time when the event happened */
	private final long timestamp;


	/**
	 * Create a new ApplicationEvent.
	 * @param source the object on which the event initially occurred (never {@code null})
	 */
	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}


	/**
	 * Return the system time in milliseconds when the event happened.
	 */
	public final long getTimestamp() {
		return this.timestamp;
	}

}

ApplicationEvent继承了java.util.EventObject,也就是说它只是在EventObject上添加了其他功能或者属性而已。
从源码中可以看出,事件对象仅添加了一个timestamp的属性,和获取timestamp的方法。这不难理解,就是为事件对象赋予了事件的维度了,非常的简单。

2. 源码解读

开始读源码了,读源码的思路是:

Created with Raphaël 2.2.0监听者何时被实例化监听者被存放在了那里(如何管理)如何发布事件监听接口在哪里被调用

2.1. 实例化ApplicationListener的子类

这就开始吧,先看看监听者的实例化。在SpringApplication时,即run方法执行之前,Springboot实例化了配置在META-INF/spring.factories文件下配置好的实体类。看源码:

public class SpringApplication {
		@SuppressWarnings({ "unchecked", "rawtypes" })
		public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
			this.resourceLoader = resourceLoader; 
			Assert.notNull(primarySources, "PrimarySources must not be null"); 
			this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); 
			this.webApplicationType = deduceWebApplicationType(); 
			setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class));
			 在此处实例化了ApplicationListener的子类,并保存起来
			setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 
			this.mainApplicationClass = deduceMainApplicationClass(); 
		}
	}
	
	// 如此看来ApplicationListener被实例化了以后,赋值给了SpringApplication对象中的成员变量listeners
	// 而且是一个ArrayList集合,如此看来,可以有多个ApplicationListener的实体类,并且时有顺序的
	public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
		this.listeners = new ArrayList<>();
		this.listeners.addAll(listeners);
	}

在SpringApplication的构造方法中,对ApplicationListener的子类进行了实例化,但是这里实例化的仅是``文件中配置的子类。
(关于SpringApplication实例化的详细在另一文章中有描述,其中有关于读取META-INF/spring.factories文件进行实例化的源码解读,在此就不多做说明 链接

下图是截取了部分META-INF/spring.factories文件的内容:

在这里插入图片描述
spring.factories文件

2.1.1. 小结

那么,可以得出了以下结论:

  • 自定义的ApplicationListener子类,可以通过添加spring.factories文件到META-INF文件夹中(META-INF文件夹在src/main/resources下新建),并加入类似这样的配置 org.springframework.context.ApplicationListener=com.jwb.demo.listener.MyApplicationEventListener即可。
  • 子类必须是全限类名。
  • 可以配置多个子类,每个子类之间用英文的逗号分隔","。

那么除了这种方式可以配置以外,还有其他的方式吗?请继续看下文。

2.2. 有个特殊的ApplicationListener的子类——DelegatingApplicationListener

DelegatingApplicationListener是SpringBoot定义在META-INF/spring.factories文件中ApplicationListener的实现类之一,该类的作用是用于实例化我们定义在application.properties文件中的监听器,并将这些监听器管理起来。
那么,我们需要弄明白两件事情:

  • 一、DelegatingApplicationListener在什么时候监听了什么事件。
  • 二、application.properties中配置监听器的key是什么。

下面先来看看DelegatingApplicationListener在监听什么事件:

public class DelegatingApplicationListener implements ApplicationListener<ApplicationEvent>, Ordered {
		private static final String PROPERTY_NAME = "context.listener.classes";

		private int order = 0;
	
		private SimpleApplicationEventMulticaster multicaster;
	
		@Override
		public void onApplicationEvent(ApplicationEvent event) {
			if (event instanceof ApplicationEnvironmentPreparedEvent) {
				// 获取applicatio.properties中配置的ApplicationListener的子类
				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();
			}
			// PROPERTY_NAME 的值是 context.listener.classes
			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;
		}
	
		public void setOrder(int order) {
			this.order = order;
		}
	
		@Override
		public int getOrder() {
			return this.order;
		}
	
	}

源码分析:

  • 一、DelegatingApplicationListener监听了来自spring框架中的所有事件,因为接口的泛型是<ApplicationEvent>,但是代码中只有当event事件是ApplicationEnvironmentPreparedEvent类型的时候,才有代码逻辑可执行,ApplicationEnvironmentPreparedEvent是springboot启动时环境变量初始化完成以后产生的(这是springboot生命周期中的一部分,具体可以看关于生命周期的文章,链接)。
  • 二、看看代码中执行了什么。
    • 实例化了applicatio.properties中配置的ApplicationListener的子类。
    • 将所有的监听器放入广播对象中进行管理。
    • 将当前的事件(ApplicationEnvironmentPreparedEvent事件),广播发给刚刚实例化后的子类。

2.2.1. 小结:

从以上的代码解读中,可得出以下结论:

  • 可以通过配置application.propertie文件将ApplicationListener的实现类加入环境中,可以是context.listener.classes
  • 自定义一个监听ApplicationEnvironmentPreparedEvent事件的实现类并配置,可以监听spring启动后环境变量初始化完成的事件。

2.3. 发布事件

最后,使用spring提供的发布事件的方法,将事件发布出去:

	// 此处省略了获取context的方式,方法有多种,就不再此详细说了。
	ApplicationContext context;
	context.publishEvent(event);

3. 监听springboot生命周期

先创建一个ApplicationListener的实现类,如下:

package com.jwb.demo.listener;

import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.context.event.ContextStoppedEvent;

public class MyApplicationEventListener implements ApplicationListener<ApplicationEvent> {

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		// 在这里可以监听到Spring Boot的生命周期
		if (event instanceof ApplicationEnvironmentPreparedEvent) { // 初始化环境变量
			System.out.println("Spring Boot的生命周期:初始化环境变量");
		} else if (event instanceof ApplicationPreparedEvent) { // 初始化完成
			System.out.println("Spring Boot的生命周期:初始化完成");
		} else if (event instanceof ContextRefreshedEvent) { // 应用刷新
			System.out.println("Spring Boot的生命周期:应用刷新");
		} else if (event instanceof ApplicationReadyEvent) {// 应用已启动完成
			System.out.println("Spring Boot的生命周期:应用已启动完成");
		} else if (event instanceof ContextStartedEvent) { // 应用启动,需要在代码动态添加监听器才可捕获
			System.out.println("Spring Boot的生命周期:代码动态添加监听器");
		} else if (event instanceof ContextStoppedEvent) { // 应用停止
			System.out.println("Spring Boot的生命周期:应用停止");
		} else if (event instanceof ContextClosedEvent) { // 应用关闭
			System.out.println("Spring Boot的生命周期:应用关闭");
		} else {
			System.out.println("Spring Boot的生命周期:其他事件");
		}
	}
}

在application.properties配置中加入该实现类的配置
context.listener.classes=com.jwb.demo.listener.MyApplicationEventListener


版权声明:本文为qq_38845829原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。