Spring 的事件发布工具
平时开发中会发现Spring提供了一系列的工具满足我们的业务场景,其中有一项是提供了事件的发布和订阅。
事件的发布主要是依靠ApplicationEventPublisher
来进行的。
要想实现事件发布主要需要下面几个对象:任务、事件、事件监听。
任务
任务就是一个普通的类,用来保存你要发布事件的内容。这个没有特殊限制,可以根据自己业务随意设置。
import lombok.Data;
/**
*
* @author daify
* @date 2019-08-19
**/
@Data
public class Task {
private Long id;
private String taskName;
private String taskContext;
private boolean finish;
}
事件
事件类需要继承org.springframework.context.ApplicationEvent
,这样发布的事件才能被Spring所识别
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEvent;
/**
*
* @author daify
* @date 2019-08-19
**/
@Slf4j
public class MyEvent extends ApplicationEvent {
private Task task;
public MyEvent(Task task) {
super(task);
this.task = task;
}
public Task getTask() {
return task;
}
}
事件监听
事件的监听器需要实现org.springframework.context.ApplicationListener
,并且需要注入到容器之中。
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
*
* @author daify
* @date 2019-08-19
**/
@Component
@Slf4j
public class MyEventListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent myEvent) {
if (Objects.isNull(myEvent)) {
return;
}
Task task = myEvent.getTask();
log.info("事件接收任务:{}",JSON.toJSONString(task));
task.setFinish(true);
log.info("此时完成任务");
}
}
测试
这样就可以通过下面测试内容完成事件发布的测试
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
*
* @author daify
* @date 2019-08-19
**/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = 此处改为项目启动类.class)
@Slf4j
public class EventTest {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Test
public void publishTest() {
Task task = new Task();
task.setId(1L);
task.setTaskName("测试任务");
task.setTaskContext("任务内容");
task.setFinish(false);
MyEvent event = new MyEvent(task);
log.info("开始发布任务");
eventPublisher.publishEvent(event);
log.info("发布任务完成");
}
}
Spring 事件发布流程
Spring的事件发布和订阅还是相当简单的,这里就简单的描述下,整个事件发布的流程。
ApplicationEventPublisher
使用其publishEvent
方法发布任务后,代码进入了
org.springframework.context.support.AbstractApplicationContext逻辑内。
AbstractApplicationContext
整个事件发布逻辑都在这个类以及其子类中,其最终发布事件的方法是publishEvent(Object event, @Nullable ResolvableType eventType)
。
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
// 无关内容
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
}
// 类型转换
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
}
}
// 在早期事件,容器初始化时候使用,可以忽略
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
// ② 进行任务广播的主要逻辑
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// 方便使用父类进行发布事件,非重点
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
整个事件发布的核心逻辑在getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
这一行。
内容被分为两部分:
-
getApplicationEventMulticaster()
获得容器中的ApplicationEventMulticaster
。此内容主要Spring用来辅助发布任务的工具类。 -
ApplicationEventMulticaster.multicastEvent(applicationEvent, eventType)
真正进行事件发布的内容。
ApplicationEventMulticaster
其为org.springframework.context.event.AbstractApplicationEventMulticaster
实现类,主要为了辅助事件进行发布,
其内部发布任务主要核心逻辑在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 {
invokeListener(listener, event);
}
}
}
其大概流程为:
- 获取事件类型,主要用来获得Spring Event的实际类型。
resolveDefaultEventType(event))
getApplicationListeners(event, type)
根据事件和事件类型去获得此事件和事件类型的监听器- 使用
invokeListener(listener, event)
去执行事件。
AbstractApplicationEventMulticaster
获取监听器的主要逻辑在org.springframework.context.event.AbstractApplicationEventMulticaster
中的getApplicationListeners(event, type)
protected Collection<ApplicationListener<?>> getApplicationListeners(
ApplicationEvent event, ResolvableType eventType) {
Object source = event.getSource();
Class<?> sourceType = (source != null ? source.getClass() : null);
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
// Quick check for existing entry on ConcurrentHashMap...
ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}
if (this.beanClassLoader == null ||
(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
// Fully synchronized building and caching of a ListenerRetriever
synchronized (this.retrievalMutex) {
retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}
retriever = new ListenerRetriever(true);
Collection<ApplicationListener<?>> listeners =
retrieveApplicationListeners(eventType, sourceType, retriever);
this.retrieverCache.put(cacheKey, retriever);
return listeners;
}
}
else {
// No ListenerRetriever caching -> no synchronization necessary
return retrieveApplicationListeners(eventType, sourceType, null);
}
}
大致流程:
通过时间类型和事件中的数据源类型,构建一个缓存key,先去缓存中获取有无此key对应的事件处理器。
如果不存在则构建一个新的ListenerRetriever
,然后调用retrieveApplicationListeners
方法获得监听的listener。
retrieveApplicationListeners
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {
List<ApplicationListener<?>> allListeners = new ArrayList<>();
Set<ApplicationListener<?>> listeners;
Set<String> listenerBeans;
synchronized (this.retrievalMutex) {
listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
}
for (ApplicationListener<?> listener : listeners) {
if (supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
retriever.applicationListeners.add(listener);
}
allListeners.add(listener);
}
}
if (!listenerBeans.isEmpty()) {
BeanFactory beanFactory = getBeanFactory();
for (String listenerBeanName : listenerBeans) {
try {
Class<?> listenerType = beanFactory.getType(listenerBeanName);
if (listenerType == null || supportsEvent(listenerType, eventType)) {
ApplicationListener<?> listener =
beanFactory.getBean(listenerBeanName, ApplicationListener.class);
if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
if (beanFactory.isSingleton(listenerBeanName)) {
retriever.applicationListeners.add(listener);
}
else {
retriever.applicationListenerBeans.add(listenerBeanName);
}
}
allListeners.add(listener);
}
}
}
catch (NoSuchBeanDefinitionException ex) {
// Singleton listener instance (without backing bean definition) disappeared -
// probably in the middle of the destruction phase
}
}
}
AnnotationAwareOrderComparator.sort(allListeners);
if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
retriever.applicationListeners.clear();
retriever.applicationListeners.addAll(allListeners);
}
return allListeners;
}
在这个方法中主要进行的逻辑就很简单了。主要通过循环Listeners来进行监听匹配。而Listeners的来源主要为两部分:
listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
一部分为容器中已经存在的监听器,一部分是监听器bean的字符串标识的名称。
applicationListenerBeans主要为了处理那些声明后还是还没有被添加进监听器集合中的bean。
事件发布的流程