Spring Framework 7.0.9 深度剖析-14-事件机制

3 阅读1小时+

Spring Framework 7.0.9 深度剖析-14-事件机制

14.1 本章导读

Spring 应用里有一种最常见也最容易被"误用而不自知"的能力:事件(Application Event)。一个领域对象在服务方法末尾写下 publisher.publishEvent(new OrderCreatedEvent(order)),另一个角落的 @EventListener 方法就被触发——看起来像是一行代码的事,但它背后串起了 Observer 模式抽象、ResolvableType 泛型解析、监听器注册表与缓存、BeanPostProcessor 装配、SpEL 条件求值、事务同步注册等一整条链。这条链上的每一个"默认行为",几乎都对应着一个线上事故的高发点:

  • 同步发布事件时,监听器里抛异常,发布者的事务直接回滚——这是默认行为,不是 bug;
  • 给多播器配上 taskExecutor 之后,监听器里读不到 ThreadLocal 里的事务上下文——因为异步分支切换了线程;
  • @EventListener 方法有返回值时,这个返回值会被当成新事件再次发布,新手往往浑然不觉;
  • @TransactionalEventListener 默认 fallbackExecution = false,在没有事务的方法里调用 publishEvent,监听器静默不执行;
  • 容器 refresh() 完成之前发布的事件不会丢,而是先被收进 earlyApplicationEvents,等监听器注册完毕后重放。

本章要做的,就是把这条链从入口到反射调用逐行拆开。涉及两个模块:事件核心在 spring-context(org.springframework.context 顶层包与 org.springframework.context.event 子包),事务事件在 spring-tx(org.springframework.transaction.event 包)。核心类包括 ApplicationEvent、ApplicationEventPublisher、ApplicationListener、ApplicationEventMulticaster 及其默认实现 SimpleApplicationEventMulticaster、注解驱动的 EventListenerMethodProcessor / ApplicationListenerMethodAdapter、以及事务侧的 TransactionalApplicationListenerMethodAdapter 与 TransactionalApplicationListenerSynchronization。

前置知识:读者应熟悉 Spring 容器 refresh() 的大致阶段(BeanFactory 准备、BeanFactoryPostProcessor、BeanPostProcessor、单例实例化)、ResolvableType 的基本用途(对泛型类型的封装)、以及 JDK 动态代理/CGLIB 代理的基本概念。读完本章,你应当能够:徒手画出一次 publishEvent 从方法调用到监听器反射执行的完整时序;说清 listenerCache(7.0.9 中是 retrieverCache)的 key/value 结构与失效时机;解释 @TransactionalEventListener(phase = AFTER_COMMIT) 在 7.0.9 中究竟挂在 TransactionSynchronization 的哪个回调上;并在排查"监听器为什么没执行/为什么抛异常炸了发布者"时,直接定位到本章对应的源码行。

版本事实先行:本章基于 spring-framework-7.0.9 仓库实际代码。与网上常见的 5.x/6.x 资料相比,7.0.9 有若干处值得注意的差异,后文会逐一点明,例如:ApplicationListenerDetector 在 7.0.9 中位于 spring-context 的 org.springframework.context.support 包而非 event 包;AFTER_COMMIT 阶段的实现挂在 afterCompletion(int status) 而非 afterCommit();ApplicationListener.supportsAsyncExecution()(6.1 引入)让"事务监听器必须跑在原线程"有了显式开关。


14.2 核心概念

14.2.1 ApplicationEvent:事件的抽象底座

所有 Spring 应用事件的父类是 ApplicationEvent,路径为 spring-context/src/main/java/org/springframework/context/ApplicationEvent.java。它本身极其克制——只比 JDK 的 java.util.EventObject 多了一个时间戳字段:

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;

	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}

	public ApplicationEvent(Object source, Clock clock) {
		super(source);
		this.timestamp = clock.millis();
	}

	public final long getTimestamp() {
		return this.timestamp;
	}
}

逐点说明:

  1. 继承 EventObject。java.util.EventObject 是 JDK 事件模型的根类,强制要求构造时传入 source(事件源,即"在谁身上发生了这件事"),并提供 getSource()。这就是 Observer 模式在 JDK 里的原生表达,Spring 没有另起炉灶。
  2. timestamp 是 final 不可变字段。构造即定格,getTimestamp() 返回事件发生的毫秒时刻。第二个构造器接受 Clock 参数(5.3.8 引入),专门为测试场景注入可控时钟——这是框架级代码"可测试性优先"的典型手笔。
  3. 类是 abstract 的。注释写明:通用事件直接发布没有意义,必须由用户定义子类携带具体载荷。

由此引出一个重要结论:事件的类型信息完全靠 Java 类型系统承载。一个 OrderCreatedEvent 之所以被"监听订单事件的人"收到而不是被"监听发货事件的人"收到,靠的就是后续 ResolvableType 对这个类型做 isAssignableFrom 判定。这是整个事件匹配机制的根。

14.2.2 PayloadApplicationEvent:任意对象都能当事件

从 4.2 开始,Spring 允许你 publishEvent(Object)——传一个根本不继承 ApplicationEvent 的普通对象。容器会把它包进 PayloadApplicationEvent<T>(路径 spring-context/src/main/java/org/springframework/context/PayloadApplicationEvent.java):

@SuppressWarnings("serial")
public class PayloadApplicationEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {

	private final T payload;

	private final ResolvableType payloadType;

	public PayloadApplicationEvent(Object source, T payload) {
		this(source, payload, null);
	}

	public PayloadApplicationEvent(Object source, T payload, @Nullable ResolvableType payloadType) {
		super(source);
		Assert.notNull(payload, "Payload must not be null");
		this.payload = payload;
		this.payloadType = (payloadType != null ? payloadType : ResolvableType.forInstance(payload));
	}

	@Override
	public ResolvableType getResolvableType() {
		return ResolvableType.forClassWithGenerics(getClass(), this.payloadType);
	}

	public T getPayload() {
		return this.payload;
	}
}

两个关键点:

  • implements ResolvableTypeProvider。普通 ApplicationEvent 的泛型信息在运行时会被擦除,但 PayloadApplicationEvent 通过 getResolvableType() 主动回报"我持有的 payload 到底是什么 ResolvableType"。这是后文"泛型事件匹配"能够成立的前提之一——payload 类型由 ResolvableType.forInstance(payload) 在包装时当场推断并固化下来,不依赖监听器一侧的泛型签名。
  • **三参构造器(6.0 引入)**允许发布方显式给出 payload 类型 hint。为什么需要?因为 payload.getClass() 拿到的是运行时实际类型,而泛型声明(比如监听器要听 List<OrderEvent>)匹配的是静态类型;当发布方手里的静态类型是 List<OrderEvent> 而运行时对象是 ArrayList 时,显式传入 ResolvableType.forClassWithGenerics(List.class, OrderEvent.class) 才能让匹配器拿到正确的泛型签名。

14.2.3 ApplicationEventPublisher:发布者的门面接口

发布侧的抽象只有一个接口 ApplicationEventPublisher(同包,ApplicationEventPublisher.java),它是 ApplicationContext 的父接口之一:

@FunctionalInterface
public interface ApplicationEventPublisher {

	default void publishEvent(ApplicationEvent event) {
		publishEvent((Object) event);
	}

	void publishEvent(Object event);
}

注意这里的三态在接口层就已经显形了:

  1. publishEvent(ApplicationEvent event):default 方法,直接转型成 Object 转调——也就是说在接口层,"事件对象"和"任意载荷"已经被统一收敛到同一个出口;
  2. publishEvent(Object event):唯一的抽象方法,接受任意对象;
  3. 真正带 ResolvableType 的三参变体(publishEvent(Object event, ResolvableType typeHint))不在这个公共接口上,而是在 AbstractApplicationContext 中以 protected 形式提供(见 14.3 节源码),供容器层次结构之间传播时复用已解析的类型。

@FunctionalInterface 注解说明:你可以把它当成 lambda 类型来用(例如测试里 ApplicationEventPublisher publisher = e -> ...),但真实环境里它的实现永远是那个"什么都知道"的 ApplicationContext。

14.2.4 ApplicationListener 与它的三级扩展

监听侧的根接口是 ApplicationListener<E extends ApplicationEvent>(spring-context/src/main/java/org/springframework/context/ApplicationListener.java):

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	void onApplicationEvent(E event);

	default boolean supportsAsyncExecution() {
		return true;
	}

	static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
		return event -> consumer.accept(event.getPayload());
	}
}

逐点解读:

  1. 泛型参数 E 就是监听器关心的事件类型。onApplicationEvent(E event) 里的 E 在编译期决定了这个监听器对什么事件感兴趣——但这只是给人看的签名,运行时靠的是框架对这个泛型声明做反射解析(GenericApplicationListenerAdapter.resolveDeclaredEventType)。
  2. **supportsAsyncExecution()(6.1 引入,默认 true)**是一个容易被忽略的开关:即使给多播器配了异步 taskExecutor,返回 false 的监听器也会被强制留在发布线程同步执行。事务事件监听器就靠这个方法硬编码返回 false(见 14.6 节),保证事务同步回调必须运行在事务线程里。
  3. **静态工厂 forPayload(5.3 引入)**把"只关心 payload 的 Consumer"包成一个监听 PayloadApplicationEvent<T> 的 lambda 监听器——配合 publishEvent(Object),用户连 ApplicationEvent 子类都不用写。

直接实现裸 ApplicationListener 时,事件类型过滤是"哑"的:你拿到所有事件,自己在方法里 instanceof 判断。为此 Spring 提供了两层更聪明的扩展接口:

第一层:SmartApplicationListener(spring-context/.../event/SmartApplicationListener.java,3.0 引入),它在裸接口之上暴露了两个元数据方法,让多播器不必把事件丢给监听器再由它自己忽略:

public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

	boolean supportsEventType(Class<? extends ApplicationEvent> eventType);

	default boolean supportsSourceType(@Nullable Class<?> sourceType) {
		return true;
	}

	@Override
	default int getOrder() {
		return LOWEST_PRECEDENCE;
	}

	default String getListenerId() {
		return "";
	}
}

它的泛型参数被钉死成 ApplicationEvent(因为类型判断改由 supportsEventType 以 Class 形式回答),同时继承 Ordered——getOrder() 默认 LOWEST_PRECEDENCE(Integer.MAX_VALUE,即排在最后)。getListenerId()(5.3.5 引入)给监听器一个字符串身份,配合 removeApplicationListeners(Predicate) 做按条件摘除。

第二层:GenericApplicationListener(同包,4.2 引入,5.3.5 起正式继承 SmartApplicationListener)把类型判定从 Class 升级为 ResolvableType,从而能区分 OrderCreatedEvent 与 OrderCreatedEvent<List<Item>> 这种泛型差异:

public interface GenericApplicationListener extends SmartApplicationListener {

	@Override
	default boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
		return supportsEventType(ResolvableType.forClass(eventType));
	}

	boolean supportsEventType(ResolvableType eventType);

	static <E extends ApplicationEvent> GenericApplicationListener forEventType(Class<E> eventType, Consumer<E> consumer) {
		return new GenericApplicationListenerDelegate<>(eventType, consumer);
	}
}

注意 default 方法把旧的 supportsEventType(Class) 桥接到新的 supportsEventType(ResolvableType)——这是接口演进的标准手法:老实现类不需要改代码,自动获得基于 ResolvableType 的入口。

对于那些只写了裸 ApplicationListener<SomeEvent> 的用户类,框架用 GenericApplicationListenerAdapter(同包)把它"包"成 GenericApplicationListener:在构造时反射解析出 declaredEventType(监听器泛型声明里的那个 E),之后 supportsEventType 就变成一句 this.declaredEventType.isAssignableFrom(eventType)。这个适配器是后文监听器筛选缓存的核心构件,14.5 节会完整剖析。

14.2.5 ApplicationEventMulticaster:事件的分发器

发布者(ApplicationContext)自己不直接决定"叫谁",它把这件事委托给 ApplicationEventMulticaster(spring-context/.../event/ApplicationEventMulticaster.java)。接口方法分两组:

  • 注册/摘除组:addApplicationListener(ApplicationListener)、addApplicationListenerBean(String)、removeApplicationListener(ApplicationListener)、removeApplicationListenerBean(String)、removeApplicationListeners(Predicate)、removeApplicationListenerBeans(Predicate)、removeAllListeners()。注意"实例"与"bean 名"是两套平行的注册通道——前者直接持有监听器对象,后者只记一个 bean 名字、等到真正分发时才向 BeanFactory 要实例(这是为了在容器还没实例化所有单例时就能登记监听器)。
  • 分发组:multicastEvent(ApplicationEvent) 与 multicastEvent(ApplicationEvent, ResolvableType)(4.2 引入,推荐使用,因为它带泛型类型信息)。

接口注释里有一句关键设计原则:"If a matching ApplicationListener does not support asynchronous execution, it must be run within the calling thread of this multicast call."——是否异步不是发布者决定的,而是监听器自己通过 supportsAsyncExecution() 表态、多播器执行时强制执行的。

至此,事件机制的五个核心抽象全部就位:ApplicationEvent(事件)、ApplicationEventPublisher(发布门面)、ApplicationListener 及其扩展(监听方)、ApplicationEventMulticaster(分发器)、以及把后两者粘进容器的 AbstractApplicationContext。下面进入源码剖析。

14.3 源码剖析(一):发布入口 publishEvent 的全流程

14.3.1 两个公开重载与一个 protected 三参方法

一切从 AbstractApplicationContext.publishEvent 开始(路径 spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java)。公开层只有两个薄封装:

@Override
public void publishEvent(ApplicationEvent event) {
	publishEvent(event, null);
}

@Override
public void publishEvent(Object event) {
	publishEvent(event, null);
}

真正干活的是 protected void publishEvent(Object event, @Nullable ResolvableType typeHint)(第 414-460 行,7.0.9 实际源码)。这是本章最重要的一段方法,逐段贴出并解读:

protected void publishEvent(Object event, @Nullable ResolvableType typeHint) {
	Assert.notNull(event, "Event must not be null");
	ResolvableType eventType = null;

	// Decorate event as an ApplicationEvent if necessary
	ApplicationEvent applicationEvent;
	if (event instanceof ApplicationEvent applEvent) {
		applicationEvent = applEvent;
		eventType = typeHint;
	}
	else {
		ResolvableType payloadType = null;
		if (typeHint != null && ApplicationEvent.class.isAssignableFrom(typeHint.toClass())) {
			eventType = typeHint;
		}
		else {
			payloadType = typeHint;
		}
		applicationEvent = new PayloadApplicationEvent<>(this, event, payloadType);
	}

	// Determine event type only once (for multicast and parent publish)
	if (eventType == null) {
		eventType = ResolvableType.forInstance(applicationEvent);
		if (typeHint == null) {
			typeHint = eventType;
		}
	}

	// Multicast right now if possible - or lazily once the multicaster is initialized
	if (this.earlyApplicationEvents != null) {
		this.earlyApplicationEvents.add(applicationEvent);
	}
	else if (this.applicationEventMulticaster != null) {
		this.applicationEventMulticaster.multicastEvent(applicationEvent, eventType);
	}

	// Publish event via parent context as well...
	if (this.parent != null) {
		if (this.parent instanceof AbstractApplicationContext abstractApplicationContext) {
			abstractApplicationContext.publishEvent(event, typeHint);
		}
		else {
			this.parent.publishEvent(event);
		}
	}
}

第一段:事件包装(Decorate)。传入的 event 有三种身份:

  1. 它本身就是 ApplicationEvent——直接使用,eventType 直接采用调用方给的 typeHint(通常是 null);
  2. 它是个普通对象,且调用方给的 typeHint 恰好指向一个 ApplicationEvent 子类(这种情况来自父子容器传播路径,下面会讲)——直接把 hint 当事件类型;
  3. 最常见的情况:普通对象、无 hint——包装成 new PayloadApplicationEvent<>(this, event, payloadType)。注意构造的第一个参数 this 是事件源(source),即"事件由当前容器发出";payloadType 在无 hint 时为 null,由 PayloadApplicationEvent 内部 ResolvableType.forInstance(payload) 自行推断。

第二段:类型只解析一次。eventType 为 null 时,用 ResolvableType.forInstance(applicationEvent) 现场解析。这里把"为多播解析类型"和"为父容器传播解析类型"合并成一次计算——注释明说 "Determine event type only once (for multicast and parent publish)"。对 PayloadApplicationEvent 来说,forInstance 触发的正是 ResolvableTypeProvider.getResolvableType(),于是泛型 payload 的类型被完整保留(14.2.2 节埋的伏笔在此回收)。

第三段:立刻多播 or 进早期队列。这是整个容器事件生命周期最精妙的分支:

  • this.earlyApplicationEvents != null:说明容器还处在 prepareRefresh() 之后、registerListeners() 之前的窗口里——多播器刚建好但监听器一个都没注册,此时多播等于白发,于是把事件追加进 earlyApplicationEvents 这个 LinkedHashSet,留待监听器注册完后重放;
  • 否则,只要多播器存在,就立刻 multicastEvent(applicationEvent, eventType)。

earlyApplicationEvents 字段在 7.0.9 中的声明(第 243-244 行):

/** ApplicationEvents published before the multicaster setup. */
private @Nullable Set<ApplicationEvent> earlyApplicationEvents;

它是 @Nullable——这个 null 本身就是状态机开关:refresh 过程中被置为一个空 LinkedHashSet(非 null = "正在收集早期事件"),registerListeners() 里重放完毕后被显式置回 null("窗口关闭,此后发布立即多播")。用 null/非 null 兼任布尔标志位,是 Spring 容器的经典手法。

第四段:向父容器冒泡。this.parent != null 时,同一个事件会沿容器层次向上传播。注意细节:

  • 传下去的是**原始 event(不是包装后的 applicationEvent)**和已经解析好的 typeHint;
  • 若父容器也是 AbstractApplicationContext,递归调用 publishEvent(event, typeHint),复用刚才算好的类型,避免父容器重复解析;
  • 若父容器不是 AbstractApplicationContext(比如自定义实现),退化为 parent.publishEvent(event),不带类型提示。

这条冒泡链意味着:子容器发布的事件,父容器里的监听器也能收到,反之则不然(父容器不会反向通知子容器)。这在 Spring Boot 的"父子上下文 + 监听器"排障中是高频考点(见 14.9 节)。

14.3.2 与多播器之间的两个胶水方法

容器持有多播器的字段与访问方法(第 231-232 行、467-473 行):

/** Helper class used in event publishing. */
private @Nullable ApplicationEventMulticaster applicationEventMulticaster;
ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
	if (this.applicationEventMulticaster == null) {
		throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
				"call 'refresh' before multicasting events via the context: " + this);
	}
	return this.applicationEventMulticaster;
}

包级可见(package-private),供容器内部(如 registerListeners、ApplicationListenerDetector)调用;外部用户拿不到它。注意它在多播器为 null 时直接抛 IllegalStateException——这就是"refresh 之前调 publishEvent 却又没走早期事件队列"时的兜底异常。

对外的监听器登记入口 addApplicationListener(第 556-562 行)同样是双写:

@Override
public void addApplicationListener(ApplicationListener<?> listener) {
	Assert.notNull(listener, "ApplicationEvent must not be null");
	if (this.applicationEventMulticaster != null) {
		this.applicationEventMulticaster.addApplicationListener(listener);
	}
	this.applicationListeners.add(listener);
}

逐行解读:

  1. 多播器已存在 → 立刻登记进去(运行期动态加监听器生效);
  2. 无论多播器是否存在,都往 applicationListeners 这个 LinkedHashSet 里加一份。这个集合是容器级"静态监听器清单",在 prepareRefresh() 时会被快照到 earlyApplicationListeners,并在 registerListeners() 时逐批同步给多播器。

为什么要双写?因为 addApplicationListener 可能在 refresh 的任何阶段被调用:

  • 在 initApplicationEventMulticaster() 之前调用:多播器还是 null,只能先记在容器自己的 applicationListeners 里,等 registerListeners() 统一入账;
  • 在 refresh 完成之后调用:多播器已就绪,两边同时更新,保持"容器清单 = 多播器清单"的一致性,这样下一次 refresh()(ConfigurableApplicationContext 允许重启)还能凭 earlyApplicationListeners 重建现场。

applicationListeners 字段声明(第 237-238 行):

/** Statically specified listeners. */
private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();

LinkedHashSet 保证登记顺序可预测——这与后文 AnnotationAwareOrderComparator.sort 的排序行为配合时,"order 相同的监听器保持登记顺序"就是确定的。

14.3.3 框架自身发布的事件

容器自己在生命周期节点上发布 7 种标准事件,全部位于 spring-context/.../event/ 包,都继承自 ApplicationContextEvent(携带容器引用作为 source):

事件类发布时机(AbstractApplicationContext 中的调用点)
ContextRefreshedEventfinishRefresh() 末尾,容器就绪(第 1016 行)
ContextClosedEventdoClose() 中,关闭流程开始(第 1190 行附近)
ContextStartedEventstart()(第 1585 行)
ContextStoppedEventstop()(第 1591 行)
ContextRestartedEventrestart()(第 1597 行)
ContextPausedEventpause()(第 1603 行)
MethodFailureEventApplicationListenerMethodAdapter 反射调用失败时(14.7 节会提及其用途)

ContextRefreshedEvent 的发布点值得单独看一眼(第 1002-1017 行):

protected void finishRefresh() {
	// Reset common introspection caches in Spring's core infrastructure.
	resetCommonCaches();

	// Clear context-level resource caches (such as ASM metadata from scanning).
	clearResourceCaches();

	// Initialize lifecycle processor for this context.
	initLifecycleProcessor();

	// Propagate refresh to lifecycle processor first.
	getLifecycleProcessor().onRefresh();

	// Publish the final event.
	publishEvent(new ContextRefreshedEvent(this));
}

注意它在 onRefresh()(LifecycleProcessor 的启动回调)之后才发布——所以监听 ContextRefreshedEvent 的 bean 一定能看到所有 SmartLifecycle bean 已经启动完毕。这种"生命周期动作与事件发布的先后次序"是容器级设计的细节,用户代码里常常依赖它。

14.4 源码剖析(二):容器如何装配出事件系统

14.4.1 refresh() 中的三个事件相关节点

AbstractApplicationContext.refresh() 的主流程(第 597-625 行节选)里,与事件直接相关的有三步,顺序钉死:

// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);

// Initialize message source for this context.
initMessageSource();

// Initialize event multicaster for this context.
initApplicationEventMulticaster();

// Initialize other special beans in specific context subclasses.
onRefresh();

// Check for listener beans and register them.
registerListeners();

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

// Last step: publish corresponding event.
finishRefresh();

为什么多播器必须在 initMessageSource() 之后、registerListeners() 之前初始化?看 initMessageSource() 的注释就明白:监听器实现里可能想注入 MessageSource 做国际化日志,所以监听器必须排在 MessageSource 之后;而多播器是监听器的"收件箱",必须排在监听器之前。这是一条严格的拓扑序。

而整个"早期事件窗口"的打开,发生在更早的 prepareRefresh()(第 689-701 行):

// Store pre-refresh ApplicationListeners...
if (this.earlyApplicationListeners == null) {
	this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
else {
	// Reset local application listeners to pre-refresh state.
	this.applicationListeners.clear();
	this.applicationListeners.addAll(this.earlyApplicationListeners);
}

// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
this.earlyApplicationEvents = new LinkedHashSet<>();

逐行解读:

  1. earlyApplicationListeners 快照。第一次 refresh 时把当前 applicationListeners(用户在 context.refresh() 之前手动 addApplicationListener 进去的那些)快照保存;如果是第二次 refresh(容器重启),则把 applicationListeners 重置为快照内容——清除上一轮 refresh 期间动态加的监听器,回到"refresh 前的静态状态"。
  2. 打开早期事件收集窗口:earlyApplicationEvents = new LinkedHashSet<>()。从这一刻起,任何对 publishEvent 的调用(包括 BeanFactoryPostProcessor、BeanPostProcessor 注册过程中触发的事件)都会被收进这个集合而不是立刻多播——因为此刻多播器还没建、监听器也没注册,播了也没人听。

这个窗口一直开到 registerListeners() 末尾才关闭(下一节看到)。也就是说:从 prepareRefresh() 到 registerListeners() 之间发生的所有事件,都会被缓存并重放。

14.4.2 initApplicationEventMulticaster:默认多播器从哪来

protected void initApplicationEventMulticaster() {
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
		this.applicationEventMulticaster =
				beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
		if (logger.isTraceEnabled()) {
			logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
		}
	}
	else {
		this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
		beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
		if (logger.isTraceEnabled()) {
			logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
					"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
		}
	}
}

APPLICATION_EVENT_MULTICASTER_BEAN_NAME 的值(第 165 行)就是字符串 "applicationEventMulticaster"。逻辑分两路:

  1. 容器里有名为 applicationEventMulticaster 的 bean:getBean 拿到它作为多播器。这是用户替换默认实现的扩展点——你可以自己定义一个 ApplicationEventMulticaster 实现并注册成这个名字(注意必须是 local bean,containsLocalBean 不查父容器,所以父容器配的多播器不会被子容器继承)。
  2. 没有:new SimpleApplicationEventMulticaster(beanFactory) 现场构造一个,并通过 beanFactory.registerSingleton(...) 把它登记成单例——这样用户后续可以按名字 getBean("applicationEventMulticaster") 拿到它、给它 setTaskExecutor。

注意默认构造走的是 SimpleApplicationEventMulticaster(BeanFactory) 构造器(见 14.5 节),它立刻把 BeanFactory 关联上——因为多播器在按 bean 名解析监听器时需要它。此时 taskExecutor 与 errorHandler 都是 null,即默认同步、默认异常直接抛。

14.4.3 registerListeners:三条注册通道 + 早期事件重放

protected void registerListeners() {
	// Register statically specified listeners first.
	for (ApplicationListener<?> listener : getApplicationListeners()) {
		getApplicationEventMulticaster().addApplicationListener(listener);
	}

	// Do not initialize FactoryBeans here: We need to leave all regular beans
	// uninitialized to let post-processors apply to them!
	String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
	for (String listenerBeanName : listenerBeanNames) {
		getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
	}

	// Publish early application events now that we finally have a multicaster...
	Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
	this.earlyApplicationEvents = null;
	if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
		for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
			getApplicationEventMulticaster().multicastEvent(earlyEvent);
		}
	}
}

逐段拆解:

第一条通道:静态监听器实例。getApplicationListeners() 返回容器级 applicationListeners 集合(用户在 refresh 前手动加的、或 AbstractApplicationContext 子类构造时加的),逐个 addApplicationListener 进多播器。

第二条通道:监听器 bean 的名字。getBeanNamesForType(ApplicationListener.class, true, false)——第二个参数 allowFactoryBeanInit = true,第三个 includeNonSingletons = false。关键注释写得很清楚:"Do not initialize FactoryBeans here: We need to leave all regular beans uninitialized to let post-processors apply to them!"——这里只拿 bean 名字,绝不提前实例化任何监听器 bean(否则它们会绕过 BeanPostProcessor,@Transactional、@Async 这些代理都不会生效)。拿到名字后只调 addApplicationListenerBean(listenerBeanName)——多播器把名字存进 applicationListenerBeans 集合,等到真正分发事件时才 beanFactory.getBean(name, ApplicationListener.class) 现取。

这就是"实例"与"bean 名"双通道的意义:addApplicationListener 持有的是对象本身(如 @EventListener 方法被 EventListenerMethodProcessor 包装成的 ApplicationListenerMethodAdapter,它根本不是容器里的 bean);addApplicationListenerBean 持有的是名字(如用户写的 MyListener implements ApplicationListener 组件,它需要在分发时才被容器正确实例化、织入代理)。

第三步:关闭窗口、重放早期事件。先把集合引用取出来存到局部变量 earlyEventsToProcess,再把字段置 null——这个顺序很关键:置 null 之后,重放过程中如果又有事件被发布(比如某个早期监听器在重放时发布了新事件),那些新事件会走 else if (multicaster != null) 分支立即多播,而不是被再次吞进集合造成无限缓冲。

随后按 LinkedHashSet 的插入顺序逐个 multicastEvent(earlyEvent)。注意:重放时用的是单参 multicastEvent(event),不带 ResolvableType——类型由方法内部 ResolvableType.forInstance(event) 现场推断,对普通 ApplicationEvent 没有问题;对 payload 事件,PayloadApplicationEvent.getResolvableType() 已在包装时固化,也能正确还原。

还有一个微妙点:重放发生在 finishBeanFactoryInitialization(实例化所有单例)之前。也就是说,早期事件重放时,绝大多数业务单例还没创建,按 bean 名注册的监听器此刻被 getBean 触发实例化——它们会走完整的 bean 创建流程(包括 BeanPostProcessor)。而 @EventListener 方法是在 afterSingletonsInstantiated() 阶段(所有单例就绪后)才被 EventListenerMethodProcessor 收集成监听器的——所以早期事件重放时,注解监听器大概率还没登记,收不到这些事件。这是早期事件生命周期里最重要的边界,14.8 节时序图会再次标注。

14.4.4 ApplicationListenerDetector:第三条通道的运行时兜底

还有一类监听器,getBeanNamesForType 在 registerListeners() 那个时点查不出来——比如内层 bean(inner bean)、或者类型在那时还不可知的监听器。框架用一个 BeanPostProcessor 在每个 bean 初始化完成的瞬间做兜底捕获。这个检测器在 7.0.9 中的位置是 spring-context/src/main/java/org/springframework/context/support/ApplicationListenerDetector.java(注意:在 support 包,不在 event 包):

class ApplicationListenerDetector implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor {

	private static final Log logger = LogFactory.getLog(ApplicationListenerDetector.class);

	private final transient AbstractApplicationContext applicationContext;

	private final transient Map<String, Boolean> singletonNames = new ConcurrentHashMap<>(256);

	@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
		if (ApplicationListener.class.isAssignableFrom(beanType)) {
			this.singletonNames.put(beanName, beanDefinition.isSingleton());
		}
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) {
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		if (bean instanceof ApplicationListener<?> applicationListener) {
			// potentially not detected as a listener by getBeanNamesForType retrieval
			Boolean flag = this.singletonNames.get(beanName);
			if (Boolean.TRUE.equals(flag)) {
				// singleton bean (top-level or inner): register on the fly
				this.applicationContext.addApplicationListener(applicationListener);
			}
			else if (Boolean.FALSE.equals(flag)) {
				if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
					// inner bean with other scope - can't reliably process events
					logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
							"but is not reachable for event multicasting by its containing ApplicationContext " +
							"because it does not have singleton scope. Only top-level listener beans are allowed " +
							"to be of non-singleton scope.");
				}
				this.singletonNames.remove(beanName);
			}
		}
		return bean;
	}

逐段解读:

  1. postProcessMergedBeanDefinition 预埋标记。在 bean 定义合并完成后(早于实例化),凡是类型实现了 ApplicationListener 的 bean,记下它是不是 singleton。这张 singletonNames 表是后面判定的依据。
  2. postProcessAfterInitialization 捕获。bean 走完初始化(含 @PostConstruct)之后,如果它确实是个 ApplicationListener:标记为 TRUE 的(单例)→ 立刻 addApplicationListener 登记进容器;标记为 FALSE 的(非单例,典型如 request/session 域的内层 bean)→ 只打一条警告"它无法可靠地参与事件多播",然后从表里摘除。
  3. 为什么非单例监听器不能登记?因为多播器按 bean 名缓存监听器时,非单例 bean 每次 getBean 都返回新实例——事件分发时取到的是一个临时实例,处理完就丢弃,状态毫无意义。框架的选择是:非单例监听器只允许顶层 bean 存在(那时 getBeanNamesForType 已经把它登记成 bean 名监听器,分发时每次现取新实例),内层的非单例监听器直接警告并放弃。
  4. 销毁时摘除(postProcessBeforeDestruction):监听器 bean 销毁时从多播器移除实例和对应 bean 名,避免容器关闭阶段还往一个已销毁的 bean 上发事件。

至此,监听器的三条登记通道全部清晰:

通道一:context.addApplicationListener(实例)        —— 静态/编程式,进 applicationListeners + 多播器
通道二:实现 ApplicationListener 的组件 bean       —— registerListeners() 按名字登记,分发时 getBean
通道三:@EventListener 方法                          —— EventListenerMethodProcessor 包装成适配器后走通道一
(兜底):ApplicationListenerDetector               —— bean 初始化后捕获漏网的单例监听器

14.5 源码剖析(三):监听器的解析与缓存——AbstractApplicationEventMulticaster

多播的核心问题不是"调用监听器",而是"哪些监听器该被调用"。这个问题在 7.0.9 中由 AbstractApplicationEventMulticaster(路径 spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java)解决,它的 retrieverCache 就是任务描述中提到的 listenerCache。

14.5.1 两个 Retriever 与一张缓存表

先看字段区(第 70-76 行):

private final DefaultListenerRetriever defaultRetriever = new DefaultListenerRetriever();

final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);

private @Nullable ClassLoader beanClassLoader;

private @Nullable ConfigurableBeanFactory beanFactory;
  • defaultRetriever:权威注册表。内部两个 LinkedHashSet——applicationListeners(监听器实例集合)与 applicationListenerBeans(监听器 bean 名集合)。所有 add/remove 都在 synchronized (this.defaultRetriever) 保护下写这两个集合,并在变更后 this.retrieverCache.clear()(缓存整体失效)。
  • retrieverCache:结果缓存。key 是 ListenerCacheKey(事件类型 + 事件源类型),value 是 CachedListenerRetriever(已经过滤好的监听器子集)。用 ConcurrentHashMap,说明读多写少、并发读取无锁。

注册方法的写法(第 105-116 行)很能说明问题:

@Override
public void addApplicationListener(ApplicationListener<?> listener) {
	synchronized (this.defaultRetriever) {
		// Explicitly remove target for a proxy, if registered already,
		// in order to avoid double invocations of the same listener.
		Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
		if (singletonTarget instanceof ApplicationListener) {
			this.defaultRetriever.applicationListeners.remove(singletonTarget);
		}
		this.defaultRetriever.applicationListeners.add(listener);
		this.retrieverCache.clear();
	}
}

注意 AOP 去重:如果传入的是个代理对象(比如监听器类被 @Transactional 织入了 CGLIB 代理),getSingletonTarget 剥出原始目标;若目标本身已在集合里,先移除目标——避免"代理 + 裸目标"两个实例同时登记导致同一个监听器逻辑被执行两遍。任何注册变更都清空整张 retrieverCache——因为缓存的是"某事件类型下该叫谁"的预过滤结果,注册集合一变,所有预过滤结果都作废。这是保守但正确的选择:监听器注册是低频操作,清空缓存的成本远低于维持缓存一致性的成本。

14.5.2 getApplicationListeners:缓存查找的三段式

分发时调用的入口是 getApplicationListeners(ApplicationEvent event, ResolvableType eventType)(第 188-223 行):

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);

	// Potential new retriever to populate
	CachedListenerRetriever newRetriever = null;

	// Quick check for existing entry on ConcurrentHashMap
	CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
	if (existingRetriever == null) {
		// Caching a new ListenerRetriever if possible
		if (this.beanClassLoader == null ||
				(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
						(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
			newRetriever = new CachedListenerRetriever();
			existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
			if (existingRetriever != null) {
				newRetriever = null;  // no need to populate it in retrieveApplicationListeners
			}
		}
	}

	if (existingRetriever != null) {
		Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
		if (result != null) {
			return result;
		}
		// If result is null, the existing retriever is not fully populated yet by another thread.
		// Proceed like caching wasn't possible for this current local attempt.
	}

	return retrieveApplicationListeners(eventType, sourceType, newRetriever);
}

逐段解读:

  1. 构造 cacheKey:key 由两部分组成——事件的 ResolvableType(含泛型)与事件源的 Class(source.getClass(),注意是运行时 source 类型,不是 null)。ListenerCacheKey(第 405-448 行)实现了 equals/hashCode:事件类型逐字段相等 + source 类型 nullSafe 相等。这意味着"同一事件类型、同一 source 类型"的多次发布命中同一条缓存。
  2. 快路径查缓存:retrieverCache.get(cacheKey)。
  3. miss 时的并发保护:只有"类加载安全"(ClassUtils.isCacheSafe——事件类与 source 类都由 bean ClassLoader 或其父加载,不会在热部署/多类加载器场景下造成类泄漏)时才建 CachedListenerRetriever,用 putIfAbsent 占位。两个并发线程同时 miss 同一 key 时,只有一个线程的 retriever 会留下(existingRetriever != null 时把 newRetriever 置 null,表示"我不需要填充它了")。
  4. 双检取值:拿到 existingRetriever 后调它的 getApplicationListeners()。这个方法返回 null 表示"另一个线程还没填充完"——此时当前线程不阻塞,而是退化为现场计算(把 newRetriever 当 null 传,结果不入缓存)。这是典型的"缓存未就绪时降级直算"策略,避免锁竞争。

14.5.3 retrieveApplicationListeners:真实的过滤与排序

未命中缓存时走私有方法 retrieveApplicationListeners(第 233-327 行),这是监听器筛选的完整逻辑:

private Collection<ApplicationListener<?>> retrieveApplicationListeners(
		ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {

	List<ApplicationListener<?>> allListeners = new ArrayList<>();
	Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
	Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);

	Set<ApplicationListener<?>> listeners;
	Set<String> listenerBeans;
	synchronized (this.defaultRetriever) {
		listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
		listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
	}

	// Add programmatically registered listeners, including ones coming
	// from ApplicationListenerDetector (singleton beans and inner beans).
	for (ApplicationListener<?> listener : listeners) {
		if (supportsEvent(listener, eventType, sourceType)) {
			if (retriever != null) {
				filteredListeners.add(listener);
			}
			allListeners.add(listener);
		}
	}

	// Add listeners by bean name, potentially overlapping with programmatically
	// registered listeners above - but here potentially with additional metadata.
	if (!listenerBeans.isEmpty()) {
		ConfigurableBeanFactory beanFactory = getBeanFactory();
		for (String listenerBeanName : listenerBeans) {
			try {
				if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
					ApplicationListener<?> listener =
							beanFactory.getBean(listenerBeanName, ApplicationListener.class);
					// ...代理去重逻辑...
					if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
						if (retriever != null) {
							if (beanFactory.isSingleton(listenerBeanName)) {
								filteredListeners.add(listener);
							}
							else {
								filteredListenerBeans.add(listenerBeanName);
							}
						}
						allListeners.add(listener);
					}
				}
				else {
					// Remove non-matching listeners that originally came from
					// ApplicationListenerDetector, possibly ruled out by additional
					// BeanDefinition metadata (for example, factory method generics) above.
					Object listener = beanFactory.getSingleton(listenerBeanName);
					if (retriever != null) {
						filteredListeners.remove(listener);
					}
					allListeners.remove(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) {
		if (CollectionUtils.isEmpty(filteredListenerBeans)) {
			retriever.applicationListeners = new LinkedHashSet<>(allListeners);
			retriever.applicationListenerBeans = filteredListenerBeans;
		}
		else {
			retriever.applicationListeners = filteredListeners;
			retriever.applicationListenerBeans = filteredListenerBeans;
		}
	}
	return allListeners;
}

逐段拆解:

  1. 快照注册表。在 synchronized (this.defaultRetriever) 里把两个集合复制出来——锁只保护这一次复制,后面的过滤、getBean 都在锁外做,避免"持有注册表锁去实例化 bean"造成死锁(实例化 bean 又可能反过来注册监听器)。
  2. 实例监听器的一轮过滤:逐个 supportsEvent(listener, eventType, sourceType) 判断。命中就进 allListeners;若本次要建缓存(retriever != null),同时进 filteredListeners。
  3. bean 名监听器的两轮过滤:
    • 第一轮是免实例化的预检 supportsEvent(beanFactory, listenerBeanName, eventType)(第 343-363 行):先 beanFactory.getType(name) 拿到监听器类;如果它是 GenericApplicationListener/SmartApplicationListener 或者类型拿不到,直接放行(留到实例化后细判);否则用监听器类的声明事件泛型与事件类型做一次 isAssignableFrom,不匹配的直接跳过、连 bean 都不实例化。这是关键优化:按名字登记的监听器 bean 在大多数事件下连 getBean 都省了。
    • 第二轮是实例化后细判:beanFactory.getBean(name, ApplicationListener.class) 拿到实例,再跑一遍 supportsEvent(listener, eventType, sourceType)。
    • 中间还夹着一段 AOP 去重:AopProxyUtils.getSingletonTarget(listener) 剥出代理背后的裸目标,如果裸目标已在 allListeners 里(它可能是被 ApplicationListenerDetector 捕获登记的),就用代理替换裸目标——防止同一逻辑执行两遍。
    • 非单例监听器 bean 特殊处理:缓存时只记它的名字(filteredListenerBeans)而不记实例——因为非单例每次都得现取新实例,缓存实例没有意义;这也是 CachedListenerRetriever 为什么同时持有 applicationListeners 与 applicationListenerBeans 两个字段的原因。
  4. 排序:AnnotationAwareOrderComparator.sort(allListeners)——把 @Order/Ordered 的语义落到分发顺序上(14.7 节详述)。
  5. 写回缓存:如果存在非单例 bean 监听器,缓存只保存实例子集(filteredListeners)+ bean 名子集;否则缓存保存完整结果。CachedListenerRetriever 的两个字段都是 volatile——写一次、之后只读,volatile 保证其他线程立即可见。

14.5.4 supportsEvent 的三层判定

supportsEvent(listener, eventType, sourceType)(第 393-399 行)极简:

protected boolean supportsEvent(
		ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {

	GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener gal ? gal :
			new GenericApplicationListenerAdapter(listener));
	return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

任何监听器,只要它本身不是 GenericApplicationListener,就当场包一个 GenericApplicationListenerAdapter。适配器内部(GenericApplicationListenerAdapter.java 第 69-80 行)三选一:

@Override
public boolean supportsEventType(ResolvableType eventType) {
	if (this.delegate instanceof GenericApplicationListener gal) {
		return gal.supportsEventType(eventType);
	}
	else if (this.delegate instanceof SmartApplicationListener sal) {
		Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.resolve();
		return (eventClass != null && sal.supportsEventType(eventClass));
	}
	else {
		return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
	}
}
  • 目标是 GenericApplicationListener:直接用它自己的 ResolvableType 判定;
  • 目标是老式 SmartApplicationListener:把 ResolvableType 解析成 Class 喂给旧接口;
  • 目标是裸 ApplicationListener:用构造时反射解析好的 declaredEventType(泛型 E)做 isAssignableFrom。

declaredEventType 的解析(第 109-116 行)用 ResolvableType.forClass(listenerType).as(ApplicationListener.class).getGeneric()——取"这个类实现 ApplicationListener 时声明的那个泛型参数",并用 eventTypeCache(ConcurrentReferenceHashMap,软引用值)按监听器类缓存解析结果。若解析出来还是 ApplicationEvent 本身(比如写了 ApplicationListener raw 裸类型),再通过 AopUtils.getTargetClass 剥一层代理重新解析。

这套机制解释了一个经典问题:为什么裸 ApplicationListener<OrderCreatedEvent> 能自动收到 OrderCreatedEvent——就是 declaredEventType.isAssignableFrom(eventType) 在起作用。

14.6 源码剖析(四):SimpleApplicationEventMulticaster 的分发动作

筛选出该叫谁之后,真正"挨个叫人"的是 SimpleApplicationEventMulticaster(路径 spring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java)。它继承 AbstractApplicationEventMulticaster,只负责两件事:决定同步还是异步执行、决定异常如何处理。

14.6.1 两个可配字段:taskExecutor 与 errorHandler

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

	private @Nullable Executor taskExecutor;

	private @Nullable ErrorHandler errorHandler;

	private volatile @Nullable Log lazyLogger;
  • taskExecutor:默认 null。javadoc 明确说默认等价于 SyncTaskExecutor——在调用线程里同步跑完所有监听器。配上 ThreadPoolTaskExecutor 之类,监听器就进线程池跑。
  • errorHandler:默认 null。javadoc 说得更直白:默认情况下"监听器异常会中断当前多播、并传播给事件发布者";配了 taskExecutor 之后,单个监听器的异常传播给执行器,但不必然中断其他监听器。
  • lazyLogger:日志对象延迟初始化(volatile),避免构造期就绑定日志系统。

两个 setter(setTaskExecutor/setErrorHandler)就是全部扩展点——用户拿到容器里那个名为 applicationEventMulticaster 的 bean 后调它们即可:

applicationContext.getBean("applicationEventMulticaster", SimpleApplicationEventMulticaster.class)
		.setTaskExecutor(myExecutor);

14.6.2 multicastEvent:同步/异步分支的完整判定

单参重载直接转双参(第 132-134 行):

@Override
public void multicastEvent(ApplicationEvent event) {
	multicastEvent(event, null);
}

核心双参方法(第 137-154 行,7.0.9 实际源码):

@Override
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event));
	Executor executor = getTaskExecutor();
	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		if (executor != null && listener.supportsAsyncExecution()) {
			try {
				executor.execute(() -> invokeListener(listener, event));
			}
			catch (RejectedExecutionException ex) {
				// Probably on shutdown -> invoke listener locally instead
				invokeListener(listener, event);
			}
		}
		else {
			invokeListener(listener, event);
		}
	}
}

逐段解读:

  1. 补全类型:eventType 为 null 时 ResolvableType.forInstance(event) 现场解析——与 publishEvent 里的逻辑呼应(走 publishEvent 主路径时类型早已算好传进来,这里只是兜底)。
  2. 取出筛选结果:getApplicationListeners(event, type)——上一节详解的带缓存方法。
  3. 逐监听器分发,两道闸门决定同步还是异步:
    • 闸门一 executor != null:多播器整体配了线程池;
    • 闸门二 listener.supportsAsyncExecution():这个监听器自己表态同意异步。 两个条件同时满足才进 executor.execute(...);否则一律同步 invokeListener(listener, event)。
  4. 异步拒绝的兜底:线程池关闭中抛 RejectedExecutionException 时,注释写得很直白——"Probably on shutdown",此时退化为本地同步执行该监听器,保证"容器关闭阶段发布的关闭事件,监听器至少不会丢"。
  5. 事务监听器为何强制同步:TransactionalApplicationListener.supportsAsyncExecution() 硬编码返回 false(14.7 节会看到)——哪怕用户配了全局 taskExecutor,事务事件监听器的"注册事务同步"动作也必须发生在发布线程(否则 TransactionSynchronizationManager 这个 ThreadLocal 里根本没有当前事务)。

14.6.3 invokeListener:异常处理的两道分支

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
	ErrorHandler errorHandler = getErrorHandler();
	if (errorHandler != null) {
		try {
			doInvokeListener(listener, event);
		}
		catch (Throwable err) {
			errorHandler.handleError(err);
		}
	}
	else {
		doInvokeListener(listener, event);
	}
}

逻辑对称:配了 errorHandler——任何 Throwable 都交给它处置(常见实现 TaskUtils.LOG_AND_SUPPRESS_ERROR_HANDLER 就是打日志后吞掉);没配——doInvokeListener 裸调,异常原样向外抛。注意无 errorHandler 时抛异常会怎样:它会直接逃出 multicastEvent 的 for 循环——排在后面的监听器不再执行,异常继续沿调用栈传回 publishEvent、传回发布者业务方法。这就是"同步监听器抛异常导致发布者中断"的源码出处。

14.6.4 doInvokeListener:ClassCastException 的智能容错

真正的反射/直接调用在 doInvokeListener(第 178-202 行):

@SuppressWarnings({"rawtypes", "unchecked"})
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
	try {
		listener.onApplicationEvent(event);
	}
	catch (ClassCastException ex) {
		String msg = ex.getMessage();
		if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||
				(event instanceof PayloadApplicationEvent payloadEvent &&
						matchesClassCastMessage(msg, payloadEvent.getPayload().getClass()))) {
			// Possibly a lambda-defined listener which we could not resolve the generic event type for
			// -> let's suppress the exception.
			Log loggerToUse = this.lazyLogger;
			if (loggerToUse == null) {
				loggerToUse = LogFactory.getLog(getClass());
				this.lazyLogger = loggerToUse = loggerToUse;
			}
			if (loggerToUse.isTraceEnabled()) {
				loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
			}
		}
		else {
			throw ex;
		}
	}
}

这段是整个事件机制里最容易被忽略、但最实用的防御设计:

  1. 为什么会有 ClassCastException? 考虑 lambda 监听器:ApplicationListener<OrderCreatedEvent> l = e -> ...。lambda 在编译后没有"泛型父类"可反射——GenericApplicationListenerAdapter.resolveDeclaredEventType 解析它的 class 时拿不到 OrderCreatedEvent,只能放行所有事件(declaredEventType == null 时 supportsEventType 返回 true)。于是一个本应只处理 OrderCreatedEvent 的 lambda,可能被丢进来一个 UserDeletedEvent,方法体里强转时炸出 ClassCastException。
  2. 容错策略:框架去匹配异常消息文本(matchesClassCastMessage),判断这个 CCE 是不是"事件类型本身不匹配"引起的——是,则降级为 trace 日志吞掉(相当于这个事件"不该我听");不是(比如是监听器内部别的类型转换炸了),原样抛出。
  3. 消息匹配(matchesClassCastMessage,第 204-220 行)要兼容三个 JDK 版本的异常文案格式:Java 8 的 "java.lang.String cannot be cast..."、Java 11 的 "class ..."、Java 9 带模块名的 "java.base/java.lang.String cannot be cast..."——分别用 startsWith 类名、startsWith 类 toString、以及 / 分隔符后再比对类名来识别。

这个设计的实践含义很重要:你写的裸 lambda 监听器永远不会因为"收到了不该收的事件"而炸出可见异常——不匹配的事件会被 trace 级日志静默吞掉。但反过来说,如果你在监听器里看到业务逻辑压根没跑、日志里又只有一条 trace,别怀疑框架——先检查自己是不是 lambda 丢了泛型签名。

14.6.5 分发小结:一次 multicastEvent 的动作清单

把 14.5 与本节串起来,multicastEvent(event, type) 完成的动作是:

multicastEvent(event, type)
  ├─ ResolvableType type 补全(如缺失)
  ├─ getApplicationListeners(event, type)
  │    ├─ ListenerCacheKey(eventType, sourceType) 查 retrieverCache
  │    ├─ 命中 → CachedListenerRetriever.getApplicationListeners()(非单例 bean 现取 + 排序)
  │    └─ 未命中 → 快照注册表 → supportsEvent 三层过滤 → AnnotationAwareOrderComparator.sort → 回填缓存
  └─ for each listener:
       ├─ executor && listener.supportsAsyncExecution() → executor.execute(() -> invokeListener)
       │     └─ RejectedExecutionException → 本地兜底 invokeListener
       └─ 否则 → 同步 invokeListener
            └─ errorHandler != null ? try/catch(Throwable).handleError : doInvokeListener
                 └─ listener.onApplicationEvent(event) + CCE 智能容错

这套设计的性能特征也清楚了:稳态下(无注册变更、事件类型重复出现),第二次发布同一类型事件的开销约等于一次 ConcurrentHashMap 查找 + 遍历已排好序的监听器列表——泛型匹配、排序全部被缓存,这就是 Spring 事件在高频发布场景下依然够用的原因。

14.7 源码剖析(五):@EventListener 注解监听器的装配链

14.7.1 注解本身:EventListener

@EventListener 的定义(spring-context/src/main/java/org/springframework/context/event/EventListener.java)很短,但每个属性都对应一条运行时行为:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Reflective
public @interface EventListener {

	@AliasFor("classes")
	Class<?>[] value() default {};

	@AliasFor("value")
	Class<?>[] classes() default {};

	String condition() default "";

	boolean defaultExecution() default true;

	String id() default "";
}
  • classes/value:显式声明要监听的事件类型数组。指定后,方法参数必须是这些类的公共基类;不指定时,事件类型从方法唯一的参数签名推断。
  • condition:SpEL 条件,求值为 true(或 "true"/"on"/"yes"/"1")才处理。可用变量见 javadoc:#root.event、#root.args、#a0/#p0、参数名。
  • defaultExecution(6.2 引入):"没有事务等前置条件时是否默认执行"——这个属性是为 @TransactionalEventListener 的 fallbackExecution 做桥接而存在的。
  • id:监听器身份,默认是全限定方法签名。
  • 元注解 @Reflective:AOT 处理用,提示这个注解需要反射保留。
  • 最重要的一条设计决策写在类级 javadoc 里:"Annotated methods may have a non-void return type. When they do, the result of the method invocation is sent as a new event. If the return type is either an array or a collection, each element is sent as a new individual event."——返回值即新事件,这是 14.9 节常见问题的源码出处。

14.7.2 EventListenerMethodProcessor:谁来扫描注解

@EventListener 不是被 BeanPostProcessor 处理的,而是由一个 SmartInitializingSingleton 在所有单例就绪后统一扫描。类路径 spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java,它同时实现了 BeanFactoryPostProcessor、ApplicationContextAware:

public class EventListenerMethodProcessor
		implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor {

	protected final Log logger = LogFactory.getLog(getClass());

	private @Nullable ConfigurableApplicationContext applicationContext;

	private @Nullable ConfigurableListableBeanFactory beanFactory;

	private @Nullable List<EventListenerFactory> eventListenerFactories;

	private final StandardEvaluationContext originalEvaluationContext;

	private final @Nullable EventExpressionEvaluator evaluator;

	private final Set<Class<?>> nonAnnotatedClasses = ConcurrentHashMap.newKeySet(64);

nonAnnotatedClasses 是个去重缓存:扫描过、确认没有 @EventListener 方法的类直接记入,下次遇到同类型 bean 跳过——这是高频循环里的短路优化。

为什么要实现 BeanFactoryPostProcessor? 类注释给了答案:"primarily for early retrieval, avoiding AOP checks for this processor bean and its EventListenerFactory delegates"——它在 postProcessBeanFactory(第 96-104 行)里抢先收集所有 EventListenerFactory:

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	this.beanFactory = beanFactory;
	this.originalEvaluationContext.setBeanResolver(new BeanFactoryResolver(this.beanFactory));

	Map<String, EventListenerFactory> beans = beanFactory.getBeansOfType(EventListenerFactory.class, false, false);
	List<EventListenerFactory> factories = new ArrayList<>(beans.values());
	AnnotationAwareOrderComparator.sort(factories);
	this.eventListenerFactories = factories;
}

注意 getBeansOfType(..., false, false)——不初始化非单例、也不允许早期引用,只为拿 factory bean 定义。收集到的 factory 列表按 @Order 排序——后面要按这个顺序逐个问"你支持这个方法吗"。BeanFactoryResolver 挂进 SpEL 上下文,让 condition 里能写 @myBean.isReady()。

扫描发生在 afterSingletonsInstantiated()(第 108-150 行):

@Override
public void afterSingletonsInstantiated() {
	ConfigurableListableBeanFactory beanFactory = this.beanFactory;
	Assert.state(beanFactory != null, "No ConfigurableListableBeanFactory set");
	String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
	for (String beanName : beanNames) {
		if (!ScopedProxyUtils.isScopedTarget(beanName)) {
			Class<?> type = null;
			try {
				type = AutoProxyUtils.determineTargetClass(beanFactory, beanName);
			}
			catch (Throwable ex) {
				// An unresolvable bean type, probably from a lazy bean - let's ignore it.
			}
			if (type != null) {
				if (ScopedObject.class.isAssignableFrom(type)) {
					// 作用域代理:转向真实 target bean 类型
				}
				try {
					processBean(beanName, type);
				}
				catch (Throwable ex) {
					throw new BeanInitializationException("Failed to process @EventListener " +
							"annotation on bean with name '" + beanName + "': " + ex.getMessage(), ex);
				}
			}
		}
	}
}

逐点说明:

  1. 遍历所有 bean:getBeanNamesForType(Object.class) 等于全量 bean 名(不实例化)。跳过 scoped target(作用域代理的内部目标,避免重复处理)。
  2. 解析目标类:AutoProxyUtils.determineTargetClass 拿到代理背后的真实类(这很重要——@EventListener 注解必须从用户类上读,不能从 CGLIB 代理类上读)。解析不了的(懒加载 bean 的未解析类型)debug 级跳过。
  3. processBean 是核心,下一节看。

14.7.3 processBean:一个方法对应一个 Factory,产出一个 ApplicationListener

private void processBean(final String beanName, final Class<?> targetType) {
	if (!this.nonAnnotatedClasses.contains(targetType) &&
			AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
			!isSpringContainerClass(targetType)) {

		Map<Method, EventListener> annotatedMethods = null;
		try {
			annotatedMethods = MethodIntrospector.selectMethods(targetType,
					(MethodIntrospector.MetadataLookup<EventListener>) method ->
							AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
		}
		catch (Throwable ex) {
			// An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
		}

		if (CollectionUtils.isEmpty(annotatedMethods)) {
			this.nonAnnotatedClasses.add(targetType);
		}
		else {
			ConfigurableApplicationContext context = this.applicationContext;
			Assert.state(context != null, "No ApplicationContext set");
			List<EventListenerFactory> factories = this.eventListenerFactories;
			Assert.state(factories != null, "EventListenerFactory List not initialized");
			for (Method method : annotatedMethods.keySet()) {
				for (EventListenerFactory factory : factories) {
					if (factory.supportsMethod(method)) {
						Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
						ApplicationListener<?> applicationListener =
								factory.createApplicationListener(beanName, targetType, methodToUse);
						if (applicationListener instanceof ApplicationListenerMethodAdapter alma) {
							alma.init(context, this.evaluator);
						}
						context.addApplicationListener(applicationListener);
						break;
					}
				}
			}
		}
	}
}

逐段解读:

  1. 三层短路:类已在 nonAnnotatedClasses 里 → 直接返回;isCandidateClass 判断这个类是否可能带注解(ASM 层面的快速筛选,避免对 JDK 类做反射);isSpringContainerClass 排除 Spring 自己的内部 bean(容器自身组件上不会写用户监听器)。
  2. 方法发现:MethodIntrospector.selectMethods 遍历类的所有方法,AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class)——用"合并注解"查找,意味着组合注解(meta-annotated)也能识别,@TransactionalEventListener 就是靠这个被认作 @EventListener 的。
  3. 空结果入缓存:一个 @EventListener 方法都没有,把类记入 nonAnnotatedClasses,永久短路。
  4. 方法 × Factory 的双重循环:对每个带注解的方法,按 order 顺序问每个 factory:supportsMethod(method) 返回 true 的那个负责创建——命中即 break,一个方法只会被一个 factory 处理。这就是扩展点:事务监听器用 TransactionalEventListenerFactory(supportsMethod 检查 @TransactionalEventListener)抢走自己的方法,剩下的全部落到兜底的 DefaultEventListenerFactory。
  5. 创建后两件事:AopUtils.selectInvocableMethod 选出可调用方法;若是 ApplicationListenerMethodAdapter,调 alma.init(context, evaluator) 注入 ApplicationContext 与 SpEL 求值器(构造时它们还是 null,事件真正分发时要用);最后 context.addApplicationListener(applicationListener)——走 14.3.2 的双写通道登记进多播器。

注意:EventListenerMethodProcessor 本身和 DefaultEventListenerFactory 这两个 bean 是谁注册的?是 AnnotationConfigUtils.registerAnnotationConfigProcessors(spring-context/.../annotation/AnnotationConfigUtils.java 第 192-202 行)在 Java 配置类解析时自动注册的基础设施 bean:

if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
	RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
	def.setSource(source);
	beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}

if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
	RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
	def.setSource(source);
	beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
}

bean 名分别是 org.springframework.context.event.internalEventListenerProcessor 与 org.springframework.context.event.internalEventListenerFactory。只要你用了注解驱动(@Configuration/组件扫描),这两个 bean 就自动存在——这就是 @EventListener "开箱即用"的原因。

14.7.4 DefaultEventListenerFactory:兜底工厂

public class DefaultEventListenerFactory implements EventListenerFactory, Ordered {

	private int order = LOWEST_PRECEDENCE;

	@Override
	public boolean supportsMethod(Method method) {
		return true;
	}

	@Override
	public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
		return new ApplicationListenerMethodAdapter(beanName, type, method);
	}
}

supportsMethod 恒 true——它是"catch-all"(兜底);order 默认 LOWEST_PRECEDENCE,保证其他 factory(如事务 factory 的 order=50)先被询问。创建出来的就是本章后半的主角 ApplicationListenerMethodAdapter。

14.8 源码剖析(六):ApplicationListenerMethodAdapter——注解方法的"监听器化身"

ApplicationListenerMethodAdapter(路径 spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java)实现 GenericApplicationListener,把一个普通 Java 方法包装成多播器认识的监听器。它是整个注解事件体系的运行时核心。

14.8.1 构造期:把方法元数据全部固化

public class ApplicationListenerMethodAdapter implements GenericApplicationListener {

	protected final Log logger = LogFactory.getLog(getClass());

	private final String beanName;

	private final Method method;

	private final Method targetMethod;

	private final AnnotatedElementKey methodKey;

	private final List<ResolvableType> declaredEventTypes;

	private final @Nullable String condition;

	private final boolean defaultExecution;

	private final int order;

	private volatile @Nullable String listenerId;

	private @Nullable ApplicationContext applicationContext;

	private @Nullable EventExpressionEvaluator evaluator;

构造器(第 112-126 行):

public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
	this.beanName = beanName;
	this.method = BridgeMethodResolver.findBridgedMethod(method);
	this.targetMethod = (!Proxy.isProxyClass(targetClass) ?
			AopUtils.getMostSpecificMethod(method, targetClass) : this.method);
	this.methodKey = new AnnotatedElementKey(this.targetMethod, targetClass);

	EventListener ann = AnnotatedElementUtils.findMergedAnnotation(this.targetMethod, EventListener.class);
	this.declaredEventTypes = resolveDeclaredEventTypes(method, ann);
	this.condition = (ann != null ? ann.condition() : null);
	this.defaultExecution = (ann == null || ann.defaultExecution());
	this.order = resolveOrder(this.targetMethod);
	String id = (ann != null ? ann.id() : "");
	this.listenerId = (!id.isEmpty() ? id : null);
}

逐点解读:

  1. 桥接方法解析:BridgeMethodResolver.findBridgedMethod——泛型实现里编译器会生成桥接方法,必须拿到用户真正写的那个。
  2. 最具体方法:AopUtils.getMostSpecificMethod——目标类是代理时,找到用户类上声明的原始方法。
  3. methodKey:把方法 + 目标类包成 AnnotatedElementKey,作为 SpEL 表达式缓存的 key(同一个方法的 condition 表达式只解析一次)。
  4. 读注解:findMergedAnnotation(targetMethod, EventListener.class)——注意是从 targetMethod 上读,这样 @TransactionalEventListener 这种组合注解里的 @EventListener 属性(condition、classes、id)都能被合并读到。

14.8.2 resolveDeclaredEventTypes:方法签名的硬约束

private static List<ResolvableType> resolveDeclaredEventTypes(Method method, @Nullable EventListener ann) {
	int count = (KotlinDetector.isSuspendingFunction(method) ? method.getParameterCount() - 1 :
			method.getParameterCount());
	if (count > 1) {
		throw new IllegalStateException("Maximum one parameter is allowed for event listener method: " + method);
	}

	if (ann != null) {
		Class<?>[] classes = ann.classes();
		if (classes.length > 0) {
			List<ResolvableType> types = new ArrayList<>(classes.length);
			for (Class<?> eventType : classes) {
				types.add(ResolvableType.forClass(eventType));
			}
			return types;
		}
	}

	if (count == 0) {
		throw new IllegalStateException(
				"Event parameter is mandatory for event listener method: " + method);
	}
	return Collections.singletonList(ResolvableType.forMethodParameter(method, 0));
}

这就是"方法签名必须单参数"的源码出处:

  • 参数个数 > 1 → 直接 IllegalStateException(Kotlin 挂起函数扣掉 Continuation 参数后再数);
  • 注解上显式声明了 classes → 每个类包成一个 ResolvableType 组成列表(一个方法可同时声明监听多种事件);
  • 没声明 classes 且 0 参 → 抛异常:@EventListener 方法必须有事件参数;
  • 正常情况:取第一个参数的 ResolvableType(forMethodParameter——它保留泛型信息,如 List<OrderEvent> 整体作为类型)。

14.8.3 supportsEventType:泛型事件与 Payload 的双层匹配

@Override
public boolean supportsEventType(ResolvableType eventType) {
	for (ResolvableType declaredEventType : this.declaredEventTypes) {
		if (eventType.hasUnresolvableGenerics() ?
				declaredEventType.toClass().isAssignableFrom(eventType.toClass()) :
				declaredEventType.isAssignableFrom(eventType)) {
			return true;
		}
		if (PayloadApplicationEvent.class.isAssignableFrom(eventType.toClass())) {
			ResolvableType payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric();
			if (declaredEventType.isAssignableFrom(payloadType)) {
				return true;
			}
			if (payloadType.resolve() == null) {
				// Always accept such event when the type is erased
				return true;
			}
		}
	}
	return false;
}

逐段解读:

  1. 第一轮:直接类型匹配。如果事件类型的泛型不可解析(hasUnresolvableGenerics,比如从 Object.class 现场 forInstance 出来的),退化成原始 Class 的 isAssignableFrom;否则做完整泛型级别的 isAssignableFrom。
  2. 第二轮:Payload 拆包匹配。如果收到的是 PayloadApplicationEvent,剥出 payload 的类型,再问"我声明的事件类型能不能接受这个 payload 类型"——这就是 @EventListener public void handle(OrderCreatedEvent e) 能收到 publishEvent(orderCreatedPayload) 的原因:事件被包装成 PayloadApplicationEvent<OrderCreatedEvent>,剥包后类型对得上。
  3. 类型擦除兜底:如果 payload 类型解析出来是 null(发布方没给类型、对象又是泛型擦除场景),无条件接受——避免"类型信息丢失导致监听器永远收不到事件"。

14.8.4 processEvent:一次注解监听器的执行五步法

public void processEvent(ApplicationEvent event) {
	@Nullable Object[] args = resolveArguments(event);
	if (shouldHandle(event, args)) {
		Object result = doInvoke(args);
		if (result != null) {
			handleResult(result);
		}
		else {
			logger.trace("No result object given - no result to handle");
		}
	}
}

五步:解析参数 → 条件判断 → 反射调用 → 处理返回值。逐个拆:

第一步 resolveArguments(第 309-326 行):

protected @Nullable Object @Nullable [] resolveArguments(ApplicationEvent event) {
	ResolvableType declaredEventType = getResolvableType(event);
	if (declaredEventType == null) {
		return null;
	}
	if (this.method.getParameterCount() == 0) {
		return new Object[0];
	}
	Class<?> declaredEventClass = declaredEventType.toClass();
	if (!ApplicationEvent.class.isAssignableFrom(declaredEventClass) &&
			event instanceof PayloadApplicationEvent<?> payloadEvent) {
		Object payload = payloadEvent.getPayload();
		if (declaredEventClass.isInstance(payload)) {
			return new Object[] {payload};
		}
	}
	return new Object[] {event};
}
  • getResolvableType(event) 找出"这个事件命中了我声明的哪个事件类型"(遍历 declaredEventTypes,支持 payload 拆包);找不到 → 返回 null,后续 shouldHandle 直接 false(双保险)。
  • 方法 0 参(声明了 classes 但不接参数的情况)→ 空参数数组。
  • 关键分支:如果声明的参数类型不是 ApplicationEvent 的子类(说明用户写的是"裸载荷类型",如 handle(String s)),且事件是 PayloadApplicationEvent,就把 payload 拿出来作为方法参数——用户方法看到的是字符串/领域对象本身,不是事件包装。否则把整个 event 对象传进去。

第二步 shouldHandle + SpEL 条件(第 291-301 行):

@Contract("_, null -> false")
private boolean shouldHandle(ApplicationEvent event, @Nullable Object @Nullable [] args) {
	if (args == null) {
		return false;
	}
	if (StringUtils.hasText(this.condition)) {
		Assert.notNull(this.evaluator, "EventExpressionEvaluator must not be null");
		return this.evaluator.condition(
				this.condition, event, this.targetMethod, this.methodKey, args);
	}
	return true;
}

args 为 null 直接不执行;有 condition 才走求值器。求值器 EventExpressionEvaluator(event/EventExpressionEvaluator.java)的 condition 方法:

public boolean condition(String conditionExpression, ApplicationEvent event, Method targetMethod,
		AnnotatedElementKey methodKey, @Nullable Object[] args) {

	EventExpressionRootObject rootObject = new EventExpressionRootObject(event, args);
	EvaluationContext evaluationContext = createEvaluationContext(rootObject, targetMethod, args);
	return (Boolean.TRUE.equals(getExpression(this.conditionCache, methodKey, conditionExpression).getValue(
			evaluationContext, Boolean.class)));
}
  • rootObject 包装事件 + 参数数组——这就是 #root.event、#root.args 的来源;
  • MethodBasedEvaluationContext 负责把参数按名字/索引暴露成变量(#a0、#p0、参数名);
  • conditionCache 按 methodKey + 表达式文本缓存编译后的 Expression——同一条 condition 只解析一次 SpEL AST。

第三步 doInvoke 反射调用(第 380-412 行):

protected @Nullable Object doInvoke(@Nullable Object... args) {
	Object bean = getTargetBean();
	// Detect package-protected NullBean instance through equals(null) check
	if (bean.equals(null)) {
		return null;
	}

	try {
		ReflectionUtils.makeAccessible(this.method);
		if (KotlinDetector.isSuspendingFunction(this.method)) {
			return CoroutinesUtils.invokeSuspendingFunction(this.method, bean, args);
		}
		return this.method.invoke(bean, args);
	}
	catch (IllegalArgumentException ex) {
		assertTargetBean(this.method, bean, args);
		throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);
	}
	catch (IllegalAccessException | InaccessibleObjectException ex) {
		throw new IllegalStateException(getInvocationErrorMessage(bean, ex.getMessage(), args), ex);
	}
	catch (InvocationTargetException ex) {
		// Throw underlying exception
		Throwable targetException = ex.getTargetException();
		if (targetException instanceof RuntimeException runtimeException) {
			throw runtimeException;
		}
		else {
			String msg = getInvocationErrorMessage(bean, "Failed to invoke event listener method", args);
			throw new UndeclaredThrowableException(targetException, msg);
		}
	}
}

要点:

  1. getTargetBean() = applicationContext.getBean(this.beanName)——注意!每次事件分发都按 bean 名向容器要一次 bean。单例 bean 拿到的永远是同一个实例;但如果 bean 是非单例(作用域 bean),每次拿新实例。
  2. 方法反射调用:ReflectionUtils.makeAccessible 处理 private 方法;Kotlin 挂起函数走 CoroutinesUtils。
  3. 异常解包:InvocationTargetException 里包着监听器方法真正抛出的异常——是 RuntimeException 就原样抛给多播器(于是回到 14.6.3:无 errorHandler 时传播给发布者);受检异常包成 UndeclaredThrowableException。
  4. 代理类型检查:assertTargetBean 在 IllegalArgumentException 时检查方法声明类是否是目标 bean 的类——若 bean 是 JDK 动态代理而方法在用户类上,给出"请用类代理(CGLIB)"的明确报错。

第四步 handleResult:返回值再发布(第 328-371 行):

protected void handleResult(Object result) {
	if (REACTIVE_STREAMS_PRESENT && new ReactiveResultHandler().subscribeToPublisher(result)) {
		if (logger.isTraceEnabled()) {
			logger.trace("Adapted to reactive result: " + result);
		}
	}
	else if (result instanceof CompletionStage<?> completionStage) {
		completionStage.whenComplete((event, ex) -> {
			if (ex != null) {
				handleAsyncError(ex);
			}
			else if (event != null) {
				publishEvents(event);
			}
		});
	}
	else {
		publishEvents(result);
	}
}

private void publishEvents(@Nullable Object result) {
	if (result != null && result.getClass().isArray()) {
		Object[] events = ObjectUtils.toObjectArray(result);
		for (Object event : events) {
			publishEvent(event);
		}
	}
	else if (result instanceof Collection<?> events) {
		for (Object event : events) {
			publishEvent(event);
		}
	}
	else {
		publishEvent(result);
	}
}

private void publishEvent(@Nullable Object event) {
	if (event != null) {
		Assert.notNull(this.applicationContext, "ApplicationContext must not be null");
		this.applicationContext.publishEvent(event);
	}
}
  • 普通对象 → 整个作为新事件 applicationContext.publishEvent(result);
  • 数组 → 每个元素单独发布;
  • Collection → 每个元素单独发布;
  • CompletionStage(如 CompletableFuture)→ 异步完成后把结果作为事件发布;
  • Reactive Streams Publisher → 订阅,每个 onNext 元素都发布。

这就是"@EventListener 方法返回值意外发布新事件"的完整源码链:processEvent 里 result != null → handleResult → publishEvents → applicationContext.publishEvent(...)——新事件会再次走完整的多播流程,可能触发别的监听器,甚至形成"事件链"。写监听器时若不打算产出新事件,必须返回 void——这不是风格建议,是被源码强制的行为。

最后,onApplicationEvent 的入口(第 198-202 行)还有一道闸门:

@Override
public void onApplicationEvent(ApplicationEvent event) {
	if (this.defaultExecution) {
		processEvent(event);
	}
}

defaultExecution 来自注解的 defaultExecution() 属性。普通 @EventListener 它是 true,直接 processEvent;而 @TransactionalEventListener 把 fallbackExecution 桥接到这个属性上——为 false 时,这个 onApplicationEvent 变成空操作,真正的处理被推迟到事务同步回调里(下一节)。

14.9 源码剖析(七):事务事件——把监听器挂到事务的时间轴上

业务里最常见的事件诉求是:"数据库事务提交成功之后,再发 MQ / 调外部接口"。@TransactionalEventListener 就是这个诉求的官方答案。它在 7.0.9 中位于 spring-tx/src/main/java/org/springframework/transaction/event/,是注解事件体系的一个特化分支。

14.9.1 注解与 phase 枚举

@TransactionalEventListener(spring-tx/.../event/TransactionalEventListener.java)的本质是一个被 @EventListener 元注解标注的组合注解:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {

	TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;

	@AliasFor(annotation = EventListener.class, attribute = "classes")
	Class<?>[] value() default {};

	@AliasFor(annotation = EventListener.class, attribute = "classes")
	Class<?>[] classes() default {};

	@AliasFor(annotation = EventListener.class, attribute = "condition")
	String condition() default "";

	/**
	 * Whether the event should be handled if no transaction is running.
	 */
	@AliasFor(annotation = EventListener.class, attribute = "defaultExecution")
	boolean fallbackExecution() default false;

	@AliasFor(annotation = EventListener.class, attribute = "id")
	String id() default "";
}

三个关键设计:

  1. @EventListener 元注解:EventListenerMethodProcessor 的 findMergedAnnotation(method, EventListener.class) 能同时认出它——所以事务监听器走的是与普通监听器完全相同的扫描通道,只是被 TransactionalEventListenerFactory 抢走处理权。
  2. phase 默认 AFTER_COMMIT:事务提交后才处理。
  3. fallbackExecution 默认 false,且 @AliasFor 桥接到 @EventListener.defaultExecution:这就是 14.8.4 末尾那道 if (this.defaultExecution) 闸门的语义来源——无事务时,默认不执行;设为 true 才降级为普通监听器行为。

TransactionPhase 枚举(TransactionPhase.java)四个值,javadoc 里藏着一个版本级事实:

public enum TransactionPhase {

	BEFORE_COMMIT,

	/**
	 * Note: This is a specialization of {@link #AFTER_COMPLETION} and therefore
	 * executes in the same sequence of events as {@code AFTER_COMPLETION}
	 * (and not in {@link TransactionSynchronization#afterCommit()}).
	 */
	AFTER_COMMIT,

	AFTER_ROLLBACK,

	AFTER_COMPLETION
}

注意 AFTER_COMMIT 明确不是挂在 afterCommit() 回调上,而是挂在 afterCompletion(int status) 上。这与很多老资料的描述不同,源码见下。

14.9.2 TransactionalEventListenerFactory 与它的受限子类

事务侧的 EventListenerFactory 实现(spring-tx/.../event/TransactionalEventListenerFactory.java):

public class TransactionalEventListenerFactory implements EventListenerFactory, Ordered {

	private int order = 50;

	@Override
	public int getOrder() {
		return this.order;
	}

	@Override
	public boolean supportsMethod(Method method) {
		return AnnotatedElementUtils.hasAnnotation(method, TransactionalEventListener.class);
	}

	@Override
	public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
		return new TransactionalApplicationListenerMethodAdapter(beanName, type, method);
	}
}

order=50,远高于默认工厂的 LOWEST_PRECEDENCE——它先抢。supportsMethod 检查方法上有没有 @TransactionalEventListener。

实际注册到容器里的不是它本身,而是 RestrictedTransactionalEventListenerFactory(spring-tx/.../annotation/RestrictedTransactionalEventListenerFactory.java,6.1 引入),由 AbstractTransactionManagementConfiguration.transactionalEventListenerFactory() 方法(spring-tx/.../annotation/AbstractTransactionManagementConfiguration.java 第 96-97 行)注册:

public static TransactionalEventListenerFactory transactionalEventListenerFactory() {
	return new RestrictedTransactionalEventListenerFactory();
}

受限子类在创建适配器后多做一步合法性检查(第 41-58 行):

@Override
public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
	TransactionalApplicationListenerMethodAdapter adapter =
			new TransactionalApplicationListenerMethodAdapter(beanName, type, method);
	if (adapter.getTransactionPhase() != TransactionPhase.BEFORE_COMMIT) {
		Transactional txAnn = AnnotatedElementUtils.findMergedAnnotation(method, Transactional.class);
		if (txAnn == null) {
			txAnn = AnnotatedElementUtils.findMergedAnnotation(type, Transactional.class);
		}
		if (txAnn != null) {
			Propagation propagation = txAnn.propagation();
			if (propagation != Propagation.REQUIRES_NEW && propagation != Propagation.NOT_SUPPORTED) {
				throw new IllegalStateException("@TransactionalEventListener method must not be annotated with " +
						"@Transactional unless when declared as REQUIRES_NEW or NOT_SUPPORTED: " + method);
			}
		}
	}
	return adapter;
}

为什么 AFTER_COMMIT 类阶段的监听器方法不能加普通 @Transactional? 因为 AFTER_COMMIT 时外层事务已经结束——方法上再来一个 PROPAGATION_REQUIRED 事务会立即新开一个独立事务,与"主事务成功后再做事"的语义脱节,极易造成数据不一致。6.1 起框架直接在启动期抛出 IllegalStateException 禁止这种配置(除非传播行为本身就是 REQUIRES_NEW 或 NOT_SUPPORTED,那是开发者显式有意为之)。这是 7.0.9 中一个"fail-fast"的版本差异点,老资料不会提到。

14.9.3 TransactionalApplicationListenerMethodAdapter:把执行推迟到事务回调

public class TransactionalApplicationListenerMethodAdapter extends ApplicationListenerMethodAdapter
		implements TransactionalApplicationListener<ApplicationEvent> {

	private final TransactionPhase transactionPhase;

	private final List<SynchronizationCallback> callbacks = new CopyOnWriteArrayList<>();

	public TransactionalApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
		super(beanName, targetClass, method);
		TransactionalEventListener eventAnn =
				AnnotatedElementUtils.findMergedAnnotation(getTargetMethod(), TransactionalEventListener.class);
		if (eventAnn == null) {
			throw new IllegalStateException("No TransactionalEventListener annotation found on method: " + method);
		}
		this.transactionPhase = eventAnn.phase();
	}

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (TransactionalApplicationListenerSynchronization.register(event, this, this.callbacks)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Registered transaction synchronization for " + event);
			}
		}
		else if (isDefaultExecution()) {
			if (getTransactionPhase() == TransactionPhase.AFTER_ROLLBACK && logger.isWarnEnabled()) {
				logger.warn("Processing " + event + " as a fallback execution on AFTER_ROLLBACK phase");
			}
			processEvent(event);
		}
		else {
			// No transactional event execution at all
			if (logger.isDebugEnabled()) {
				logger.debug("No transaction is active - skipping " + event);
			}
		}
	}
}

这是事务事件的总开关,三分支语义:

  1. register 返回 true:当前线程绑定着活跃事务——同步已注册成功,事件的处理被推迟到事务的对应阶段,本次 onApplicationEvent 立即返回(什么都没做)。
  2. register 返回 false 且 isDefaultExecution() 为 true(即 fallbackExecution = true):无事务也立即 processEvent(event) 降级执行;如果 phase 是 AFTER_ROLLBACK 还打一条 warn——"在没有回滚的世界里按回滚语义处理了事件",提醒开发者这不是预期行为。
  3. 否则:静默跳过(debug 日志)。这就是"无事务时 @TransactionalEventListener 默认不触发"的源码出处。

14.9.4 TransactionalApplicationListenerSynchronization:真正的事务挂接点

注册动作在 TransactionalApplicationListenerSynchronization.register(spring-tx/.../event/TransactionalApplicationListenerSynchronization.java 第 75-94 行):

public static <E extends ApplicationEvent> boolean register(
		E event, TransactionalApplicationListener<E> listener,
		List<TransactionalApplicationListener.SynchronizationCallback> callbacks) {

	if (org.springframework.transaction.support.TransactionSynchronizationManager.isSynchronizationActive() &&
			org.springframework.transaction.support.TransactionSynchronizationManager.isActualTransactionActive()) {
		org.springframework.transaction.support.TransactionSynchronizationManager.registerSynchronization(
				new PlatformSynchronization<>(event, listener, callbacks));
		return true;
	}
	else if (event.getSource() instanceof TransactionContext txContext) {
		org.springframework.transaction.reactive.TransactionSynchronizationManager rtsm =
				new org.springframework.transaction.reactive.TransactionSynchronizationManager(txContext);
		if (rtsm.isSynchronizationActive() && rtsm.isActualTransactionActive()) {
			rtsm.registerSynchronization(new ReactiveSynchronization<>(event, listener, callbacks));
			return true;
		}
	}
	return false;
}

两道闸门判断"当前是否真有事务":isSynchronizationActive()(事务同步已开启)且 isActualTransactionActive()(存在真实事务,而非空的同步上下文)。通过后,把一个 PlatformSynchronization 注册进 TransactionSynchronizationManager——事务管理器在提交/回滚时会回调它。第二分支处理响应式事务(Reactor TransactionContext 作为事件 source 的场景)。

回调本体 PlatformSynchronization(第 97-127 行):

private static class PlatformSynchronization<AE extends ApplicationEvent>
		extends TransactionalApplicationListenerSynchronization<AE>
		implements org.springframework.transaction.support.TransactionSynchronization {

	@Override
	public void beforeCommit(boolean readOnly) {
		if (getTransactionPhase() == TransactionPhase.BEFORE_COMMIT) {
			processEventWithCallbacks();
		}
	}

	@Override
	public void afterCompletion(int status) {
		TransactionPhase phase = getTransactionPhase();
		if (phase == TransactionPhase.AFTER_COMMIT && status == STATUS_COMMITTED) {
			processEventWithCallbacks();
		}
		else if (phase == TransactionPhase.AFTER_ROLLBACK && status == STATUS_ROLLED_BACK) {
			processEventWithCallbacks();
		}
		else if (phase == TransactionPhase.AFTER_COMPLETION) {
			processEventWithCallbacks();
		}
	}
}

逐阶段对应:

phase挂接的 TransactionSynchronization 回调触发条件
BEFORE_COMMITbeforeCommit(readOnly)事务提交之前(还可抛异常阻止提交)
AFTER_COMMITafterCompletion(status)且 status == STATUS_COMMITTED(提交成功)
AFTER_ROLLBACKafterCompletion(status)且 status == STATUS_ROLLED_BACK(回滚)
AFTER_COMPLETIONafterCompletion(status)无论提交还是回滚都执行

再次强调版本事实:AFTER_COMMIT 不在 afterCommit() 里执行。从 Spring 5.3 起,所有"事务结束后"的 phase 统一收敛到 afterCompletion(int),用 status 参数区分提交/回滚。afterCommit() 与 afterCompletion() 的时序差异(afterCommit 在事务资源完全清理之前调用)在 5.3 前曾带来"在 AFTER_COMMIT 里还能写库"的意外行为,统一到 afterCompletion 后语义更干净。

processEventWithCallbacks(第 62-72 行)则是实际执行点:

public void processEventWithCallbacks() {
	this.callbacks.forEach(callback -> callback.preProcessEvent(this.event));
	try {
		this.listener.processEvent(this.event);
	}
	catch (RuntimeException | Error ex) {
		this.callbacks.forEach(callback -> callback.postProcessEvent(this.event, ex));
		throw ex;
	}
	this.callbacks.forEach(callback -> callback.postProcessEvent(this.event, null));
}

processEvent(event) 在 TransactionalApplicationListenerMethodAdapter 中是从父类 ApplicationListenerMethodAdapter 继承来的——也就是说,真正的 SpEL 条件求值、反射调用、返回值再发布,全部复用普通监听器那套逻辑,事务侧只负责"在正确的事务时点调用它"。

最后补一个接口级事实:TransactionalApplicationListener.supportsAsyncExecution() 硬编码返回 false(TransactionalApplicationListener.java 第 70-73 行):

/**
 * Transaction-synchronized listeners do not support asynchronous execution,
 * only their target listener ({@link #processEvent}) potentially does.
 */
@Override
default boolean supportsAsyncExecution() {
	return false;
}

这就是 14.6.2 两道闸门中第二道闸门的语义闭环:哪怕用户给多播器配了全局异步 executor,事务事件监听器的 onApplicationEvent(即"注册事务同步"这个动作)也必须在发布线程同步执行——否则注册动作根本找不到 TransactionSynchronizationManager 里的事务。而同步回调真正执行 processEvent 的时点,已经是事务管理器的线程上了。

14.10 源码剖析(八):泛型事件匹配的边界

14.10.1 ResolvableType.isAssignableFrom 在事件匹配中的位置

整套事件匹配最终都收敛到 ResolvableType.isAssignableFrom(ResolvableType) 这一个判定上,出现的位置有三处:

  1. GenericApplicationListenerAdapter.supportsEventType:this.declaredEventType.isAssignableFrom(eventType)——裸 ApplicationListener<X> 与实际事件类型的匹配;
  2. AbstractApplicationEventMulticaster.supportsEvent(Class, ResolvableType):declaredEventType.isAssignableFrom(eventType)——按 bean 名预检时用;
  3. ApplicationListenerMethodAdapter.supportsEventType:declaredEventType.isAssignableFrom(eventType)——注解监听器的匹配,含 Payload 拆包。

ResolvableType(spring-core)对泛型的判定规则与 Java 类型系统一致:List<String> 不能接受 List<Integer>(泛型不型变),但 List 原始类型可以接受一切 List。理解了这一点,下面两个经典边界就有答案了。

14.10.2 边界一:泛型事件类可以,泛型监听器参数受限

可以的写法(事件类本身带泛型):

public class OrderEvent<T> extends ApplicationEvent { ... }

@EventListener
public void handle(OrderEvent<OrderCreated> e) { ... }

发布端如果 new OrderEvent<>(this, payload) 直接构造,运行时 ResolvableType.forInstance(event) 拿到的是 OrderEvent 的原始类(泛型参数被擦除)——此时 ApplicationListenerMethodAdapter.supportsEventType 里 eventType.hasUnresolvableGenerics() 为 true,退化为 declaredEventType.toClass().isAssignableFrom(eventType.toClass()),即只按原始类匹配,泛型参数被忽略。要让泛型参数精确匹配,事件类必须实现 ResolvableTypeProvider(像 PayloadApplicationEvent 那样),在运行时显式回报泛型签名。

受限的写法(泛型监听器):

@EventListener
public void handle(List<OrderEvent> events) { ... }

这种写法的行为取决于发布端是否给出 payload 的 ResolvableType:

  • publishEvent(List) 普通调用:PayloadApplicationEvent 构造时 ResolvableType.forInstance(payload) 只能推断出 ArrayList(运行时类),监听器声明的 List<OrderEvent> 与 ArrayList 做 isAssignableFrom 时,泛型部分对不上——不匹配;
  • 发布端显式构造 new PayloadApplicationEvent<>(this, list, ResolvableType.forClassWithGenerics(List.class, OrderEvent.class)):类型信息完整,匹配成功。

源码佐证就在 ApplicationListenerMethodAdapter.supportsEventType 的 Payload 分支:

if (PayloadApplicationEvent.class.isAssignableFrom(eventType.toClass())) {
	ResolvableType payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric();
	if (declaredEventType.isAssignableFrom(payloadType)) {
		return true;
	}
	if (payloadType.resolve() == null) {
		// Always accept such event when the type is erased
		return true;
	}
}

payloadType.resolve() == null 时无条件接受——这是"类型擦除场景下宁可错收不可漏收"的宽容策略。

14.10.3 边界二:lambda 监听器的泛型丢失

14.6.4 节讲过:lambda ApplicationListener<OrderCreatedEvent> e -> ... 的 class 是合成类,GenericApplicationListenerAdapter.resolveDeclaredEventType 反射不出 OrderCreatedEvent,declaredEventType 为 null——supportsEventType 永远返回 true(放行所有事件),真正的类型过滤退化为运行时 onApplicationEvent 里的 ClassCastException 智能容错。

实践结论:lambda 监听器永远不要写复杂泛型签名;要精确按事件类型过滤,就用 @EventListener 方法(编译后类保留泛型签名,forMethodParameter 拿得到)或 GenericApplicationListener.forEventType(Class, Consumer) 显式声明事件类。

14.11 运行机制:把源码串成时序

14.11.1 一次同步发布的完整调用栈

以用户在 service 方法里 publisher.publishEvent(new OrderCreatedEvent(order))、容器已 refresh 完成、无事务、无 taskExecutor 为例:

业务方法
 └─ ApplicationContext.publishEvent(OrderCreatedEvent)                       [AbstractApplicationContext:414]
     ├─ event 已是 ApplicationEvent,直接使用
     ├─ eventType = ResolvableType.forInstance(event)(OrderCreatedEvent)
     ├─ earlyApplicationEvents == null(窗口已关)
     ├─ applicationEventMulticaster.multicastEvent(event, eventType)          [SimpleApplicationEventMulticaster:137]
     │    ├─ getApplicationListeners(event, type)                             [AbstractApplicationEventMulticaster:188]
     │    │    ├─ ListenerCacheKey(OrderCreatedEvent, source=appContext)
     │    │    ├─ retrieverCache 命中(稳态)→ CachedListenerRetriever.getApplicationListeners()
     │    │    │    ├─ 实例子集直接取出
     │    │    │    └─ bean 名子集 → beanFactory.getBean(name) 现取单例
     │    │    └─ AnnotationAwareOrderComparator.sort(已排好序)
     │    ├─ executor == null → 同步分支
     │    └─ for each listener:
     │         invokeListener(listener, event)                                 [:162]
     │          ├─ errorHandler == null → doInvokeListener                     [:178]
     │          │    └─ listener.onApplicationEvent(event)
     │          │         (注解监听器 → ApplicationListenerMethodAdapter.onApplicationEvent
     │          │           → defaultExecution=true → processEvent
     │          │           → resolveArguments → shouldHandle(无 condition 直接 true)
     │          │           → doInvoke → getBean(beanName) → method.invoke(bean, args)
     │          │           → result != null → handleResult → publishEvent(...) 再次多播)
     │          └─ 监听器抛 RuntimeException → 异常向上穿透 invokeListener → 穿透 for 循环
     │             → 传播回 multicastEvent → 传播回 publishEvent → 传播回业务方法
     └─ parent != null → 父容器 publishEvent(event, typeHint) 递归冒泡

这条栈里最需要记住的三个性质:

  1. 同步:业务线程一直阻塞到所有监听器执行完(无异步 executor 时);
  2. 故障全开:任何一个监听器抛 RuntimeException,后续监听器不再执行,异常直接炸回业务方法;
  3. 冒泡:同一事件沿父子容器链向上广播。

14.11.2 异步发布的两种形态与行为差异

形态 A:多播器级异步(setTaskExecutor(pool))。14.6.2 的分支把每个"同意异步"的监听器包成 executor.execute(() -> invokeListener(...))。差异点:

  • 发布者线程立即返回,不等待监听器;
  • 线程上下文丢失:事务同步、MDC、ThreadLocal 用户上下文全部不在新线程里——这就是为什么 TransactionalApplicationListener.supportsAsyncExecution() 必须返回 false;
  • 异常不再传播给发布者(异常被丢进线程池,配合 errorHandler 才能被观察到);
  • RejectedExecutionException 时同步兜底执行该监听器。

形态 B:监听器级 @Async。监听器方法上加 @Async,由 AOP 代理在调用 onApplicationEvent 时切到异步线程。它与形态 A 可叠加:多播器同步调到代理的 onApplicationEvent,代理再异步切线程。注意 @Async 监听器的异常由 AsyncUncaughtExceptionHandler 处理,与多播器 errorHandler 是两套体系。

14.11.3 早期事件的生命周期状态机

把 earlyApplicationEvents 的 null/非 null 当作状态变量,整个 refresh 窗口里事件的去向是:

prepareRefresh()
  earlyApplicationEvents = new LinkedHashSet<>()     → 状态:COLLECTING
  │
  ├─ 此期间任何 publishEvent → 只进集合,不多播
  │   (包括 BeanFactoryPostProcessor、BeanPostProcessor 注册过程中的事件)
  │
  initApplicationEventMulticaster()                  → 多播器就绪
  registerListeners()
  ├─ 静态监听器实例入账
  ├─ 监听器 bean 名入账
  ├─ earlyEventsToProcess = 集合; earlyApplicationEvents = null  → 状态:REPLAYING→LIVE
  └─ 按插入顺序重放每个早期事件 multicastEvent(earlyEvent)
      (重放期间新事件因字段为 null 而立即多播,不会二次缓冲)
  │
  finishBeanFactoryInitialization()
  └─ 所有单例就绪 → EventListenerMethodProcessor.afterSingletonsInstantiated()
       → @EventListener 方法才包装成监听器入账(此时早期事件早已重放完毕)
  │
  finishRefresh() → publishEvent(ContextRefreshedEvent)  → 状态:LIVE

由此推出一个高频排障结论:在 BeanFactoryPostProcessor 阶段发布的事件,所有 @EventListener 方法都收不到——重放时注解监听器还没被创建;能收到的只有静态注册的 ApplicationListener 实例与按 bean 名登记(且此刻被 getBean 提前实例化)的监听器 bean。反之,ContextRefreshedEvent 发布时,包括注解监听器在内的全部监听器都已就位。

14.11.4 事件的注册-失效周期

缓存的失效时机只有一个:AbstractApplicationEventMulticaster 的任何注册变更方法(add/remove/clear)都会 retrieverCache.clear()。结合注册时点:

  • refresh 期间注册密集(registerListeners 批量加 bean 名、Detector 逐个捕获单例、Processor 收尾加注解适配器),缓存反复清空——这是预期内的,启动期不追求匹配性能;
  • 容器就绪后,注册变更变成低频事件(用户运行期动态加监听器、bean 销毁摘除),每次清空缓存后第一次发布某类型事件会重新过滤+排序,之后稳态命中。

数据结构上的线程安全:retrieverCache 本身是 ConcurrentHashMap 并发读;CachedListenerRetriever 的两个监听器集合字段用 volatile 发布;注册表快照在 synchronized 块内复制。整个设计是"读写分离 + 最终一致",没有一把大锁贯穿分发路径。

14.12 扩展用法与最佳实践

14.12.1 三种自定义事件的姿势与取舍

姿势一:继承 ApplicationEvent(经典写法)。

public class OrderCreatedEvent extends ApplicationEvent {
	private final Order order;
	public OrderCreatedEvent(Object source, Order order) {
		super(source);
		this.order = order;
	}
	public Order getOrder() { return this.order; }
}
  • 优点:类型语义清晰,ApplicationListener<OrderCreatedEvent> 精确匹配;事件自带 timestamp;框架事件(ContextRefreshedEvent 等)都是这个套路。
  • 缺点:每个事件一个类。

姿势二:publishEvent(Object) + PayloadApplicationEvent 自动包装。

applicationEventPublisher.publishEvent(order);   // order 是个普通 POJO

@EventListener
public void handle(Order order) { ... }          // 直接接 POJO
  • 原理见 14.3.1:普通对象被包成 PayloadApplicationEvent,监听器参数声明 POJO 类型时由 resolveArguments 自动剥包。
  • 适用:领域事件快速原型、不想建事件类的场景。代价:丢失"事件类型"这一层语义(所有 POJO 共用 PayloadApplicationEvent 壳,靠 payload 类型区分),且事件源统一是容器。

姿势三:实现 ResolvableTypeProvider 的泛型事件。要让监听器写 @EventListener public void handle(GenericDomainEvent<OrderCreated> e) 这种泛型签名并精确匹配,事件类必须自己实现 ResolvableTypeProvider(参考 PayloadApplicationEvent.getResolvableType() 的写法),在运行时回报泛型签名。否则泛型参数被擦除,框架只能按原始类匹配。

14.12.2 事件驱动架构(领域事件)的标准用法

DDD 风格的领域事件在 Spring 里的落地套路:

  1. 聚合根/领域服务在业务方法内 applicationEventPublisher.publishEvent(new OrderPaidEvent(orderId))——事件是"事实",载荷只放 ID 与必要快照;
  2. 应用服务层用 @TransactionalEventListener(phase = AFTER_COMMIT) 监听,事务提交成功后再发 MQ、调外部 HTTP、写搜索索引;
  3. 需要"事件后再发事件"的,利用注解监听器返回值机制(return new SendMqCommand(...))形成处理链——但要克制,链式事件超过两层就应改回显式编排;
  4. 跨限界上下文的集成,不要用同步事件直接调——事件监听器里同步调外部服务会把"发布者的事务"拖垮(回忆 14.11.1:同步监听器抛异常会炸回发布者)。

14.12.3 @TransactionalEventListener 的标准姿势

"事务成功后发消息"是官方推荐的场景,源码依据在 14.9.4:

@Service
public class OrderService {
	@Transactional
	public void createOrder(Order order) {
		orderRepository.save(order);
		publisher.publishEvent(new OrderCreatedEvent(order.getId()));
	}
}

@Component
public class OrderNotificationListener {
	@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
	@Async                                    // 需要异步发 MQ 时自己加 @Async
	public void on(OrderCreatedEvent event) {
		mqClient.send("order.created", event.getOrderId());
	}
}

要点:

  • 发布必须在事务方法内——register 判断的是"发布那一刻当前线程有没有活跃事务"。在事务方法外发布,fallbackExecution=false(默认)时监听器直接不执行;
  • AFTER_COMMIT 阶段操作数据库要谨慎:事务已提交,但连接资源可能还在(javadoc 警告"any data access code triggered at this point will still participate in the original transaction, but changes will not be committed")。该阶段写库等于白写——要写新数据请开 REQUIRES_NEW 或用 AFTER_COMMIT 之后的异步线程;
  • 需要"没事务也照常执行"的后台任务场景,显式 fallbackExecution = true;
  • 需要"回滚时补偿"的场景用 AFTER_ROLLBACK,注意此时主事务数据已回滚,不要指望读到刚写的数据。

14.12.4 替换与增强多播器

两个官方扩展点:

  1. 自定义多播器 bean:定义一个 bean 名字正好是 applicationEventMulticaster(类型 ApplicationEventMulticaster),initApplicationEventMulticaster 会优先用它——可以做"事件总线外发到 MQ""按租户路由"等定制;
  2. 运行期改默认多播器行为:getBean("applicationEventMulticaster", SimpleApplicationEventMulticaster.class) 后 setTaskExecutor(...) / setErrorHandler(...)。推荐 errorHandler 用 TaskUtils.LOG_AND_SUPPRESS_ERROR_HANDLER(打日志吞掉)或 LOG_AND_PROPAGATE_ERROR_HANDLER——否则同步发布下一个监听器的异常会中断整条链。

14.12.5 自定义 EventListenerFactory

@EventListener 的处理逻辑可以整体替换:实现 EventListenerFactory(supportsMethod + createApplicationListener),注册成容器 bean。EventListenerMethodProcessor.postProcessBeanFactory 会自动收集所有 factory 并按 order 排序——TransactionalEventListenerFactory(order=50)就是范本:它拦截带 @TransactionalEventListener 的方法,产出事务版适配器。自定义 factory 的典型用途:给某类注解方法统一加日志/监控埋点、把方法包装成带特殊上下文的适配器。

14.12.6 性能与线程安全注意点

  1. 监听器数量与匹配开销:稳态下每次发布是 retrieverCache 命中后遍历已排序列表(O(n),n 为匹配该类型的监听器数)。真正的代价只发生在缓存 miss(注册变更后首次):遍历全部注册表 + 泛型匹配 + 排序。因此避免在运行期频繁 add/remove 监听器——每次变更都清空整张缓存。
  2. bean 名监听器的现取:CachedListenerRetriever.getApplicationListeners() 对缓存里只存名字的非单例监听器,每次都 beanFactory.getBean(name)——高频事件 + 原型监听器 = 高频创建 bean,应尽量用单例。
  3. SpEL condition 开销:表达式 AST 有缓存(conditionCache),但每次求值仍要构造 MethodBasedEvaluationContext——高频事件里谨慎写复杂 condition,能不写就不写(类型过滤已经够精确)。
  4. 线程安全:事件发布本身在同步路径上是线程安全的(缓存并发读、注册表快照);但监听器实现自己要保证线程安全——异步执行时多个事件可能并发进入同一个单例监听器方法。
  5. 排序稳定性:AnnotationAwareOrderComparator.sort 对 order 相同的监听器保持注册顺序(LinkedHashSet 语义),所以"同 order 谁先谁后"是可预测的,但不要依赖它做业务编排——有依赖关系的监听器请显式分事件。

14.13 常见问题与坑

14.13.1 同步监听器抛异常,发布者的事务回滚了——是 bug 吗?

不是。源码链:doInvokeListener 里 listener.onApplicationEvent(event) 抛出 RuntimeException → invokeListener(无 errorHandler 分支)原样抛出 → 逃出 multicastEvent 的 for 循环(排在后面的监听器也不再执行)→ 沿 publishEvent 传回业务方法 → @Transactional 代理看到异常,回滚。

这是 SimpleApplicationEventMulticaster javadoc 明说的默认语义:默认不设 errorHandler 时,"监听器异常会中断当前多播,并传播给事件发布者"。

解法按场景选:

  • 监听器内部自己 try-catch 消化异常(边界清晰的做法);
  • 给多播器 setErrorHandler 为 TaskUtils.LOG_AND_SUPPRESS_ERROR_HANDLER(全局吞异常,注意别掩盖问题);
  • 把该监听器改成 @Async(异常进异步体系,不影响发布者)——但代价是丢了同步事务上下文。

14.13.2 异步监听器的异常为什么"消失"了?

两种异步路径,两套异常体系:

  • 多播器 taskExecutor 异步:executor.execute(() -> invokeListener(...)) 抛出的异常进线程池。无 errorHandler 时,invokeListener 里的 throw 发生在 worker 线程,发布者线程早就返回了——表现为"异常打在控制台日志里,业务毫无感知"。
  • @Async 监听器:异常由 AsyncUncaughtExceptionHandler 处理,同样与发布者无关。

排障时先确认监听器走的是哪条异步路径,再去对应体系里找异常日志。

14.13.3 @EventListener 方法有返回值,结果被当成新事件发出去了

源码出处是 ApplicationListenerMethodAdapter.processEvent:result != null → handleResult(result) → publishEvents(result) → applicationContext.publishEvent(event)。数组/集合逐元素发布,CompletionStage 异步完成后发布。

最隐蔽的翻车场景:监听器方法本来只想 return "处理完成" 打个日志,结果这个字符串被包成 PayloadApplicationEvent<String> 再次多播——另一个监听 String 载荷的监听器(或一个万能的 @EventListener public void on(Object o))被意外触发,形成诡异循环。铁律:不想产出新事件就必须返回 void。注解 javadoc 专门写了提醒:异步事件监听器方法不能靠返回值发布后续事件,要发请注入 ApplicationEventPublisher 手动发。

14.13.4 @TransactionalEventListener 在没有事务时静默不执行

源码出处:TransactionalApplicationListenerMethodAdapter.onApplicationEvent——TransactionalApplicationListenerSynchronization.register 在 TransactionSynchronizationManager.isActualTransactionActive() 为 false 时返回 false,fallbackExecution(默认 false)又把 defaultExecution 桥成 false → 进入"debug 级 skip"分支。

典型翻车:单元测试里直接调 service 方法发布事件(测试方法上没 @Transactional),监听器死活不跑,误以为是类型匹配问题。解法:无事务场景显式 fallbackExecution = true,或在测试里挂上事务。

14.13.5 子/父容器中的事件隔离与冒泡

规则(源码见 14.3.1 第四段):子容器发布的事件会冒泡到父容器,父容器的监听器收得到;父容器发布的事件不会通知子容器。且冒泡时传的是原始 event 与已解析 typeHint,沿 parent instanceof AbstractApplicationContext 递归向上。

Web 场景高频坑:Spring Boot 父子上下文(root + servlet)里,把"只该给 web 层用"的监听器注册进了父容器,结果 root 上下文发布的所有事件都触发了它;或者反过来,监听器注册在了子上下文,却以为 root 发布的事件也能收到。排查时先问:这个监听器 bean 在哪个上下文里?

14.13.6 循环依赖/启动期发布事件,监听器没收到

两个时点问题:

  1. refresh 完成前发布:事件进 earlyApplicationEvents 队列,在 registerListeners() 时重放(14.11.3 状态机)。但重放发生在 finishBeanFactoryInitialization 之前——@EventListener 方法由 EventListenerMethodProcessor.afterSingletonsInstantiated 在所有单例就绪后才登记,启动期事件它们注定收不到。需要在启动早期干活的逻辑,请实现 ApplicationListener<ContextRefreshedEvent> 接口或用 SmartInitializingSingleton,而不是 @EventListener。
  2. bean 创建过程中(构造/@PostConstruct)发布事件:此时该 bean 自己还没初始化完,如果事件的监听器又依赖这个 bean,就是循环依赖——Spring 会提前暴露引用,但事件同步多播的调用栈可能让你拿到一个半成品 bean。规范做法:发布事件放在业务方法里,不要放在 bean 生命周期回调里。

14.13.7 AFTER_COMMIT 里写数据库为什么"不生效"

@TransactionalEventListener(AFTER_COMMIT) 的监听器跑在 TransactionSynchronization.afterCompletion 回调里——主事务已提交、连接虽可能还挂着但事务边界已结束。此时监听器方法里 xxxRepository.save(...) 的变更:要么参与不到任何事务(自动提交模式下语句各自提交,看似正常),要么参与到"已结束的事务资源"中(注解 javadoc 警告的坑:语句执行了但不会被提交)。需要在事件监听器里写库,方法上自己加 @Transactional(propagation = REQUIRES_NEW) 开新事务——且记住 RestrictedTransactionalEventListenerFactory 的限制:phase 不是 BEFORE_COMMIT 时,监听器方法上的 @Transactional 只允许 REQUIRES_NEW/NOT_SUPPORTED,否则启动期直接抛 IllegalStateException。

14.13.8 一个监听器被执行了两次

按源码,重复执行只有两个来源:

  1. 代理与裸目标同时登记:AbstractApplicationEventMulticaster.addApplicationListener 与 retrieveApplicationListeners 里专门有 AopProxyUtils.getSingletonTarget 去重逻辑防这个;如果你的 bean 被手动 addApplicationListener(代理) 又被 Detector 捕获了裸目标,框架会用代理替换裸目标。仍重复时,检查是不是同一个监听器类既被组件扫描注册、又被你手动 addApplicationListener 了一次。
  2. 父子容器双注册:bean 在父子两个上下文里都存在,事件冒泡导致两边各执行一次。

14.13.9 为什么监听器的顺序不像我想的那样

排序发生在两处:retrieveApplicationListeners 末尾与 CachedListenerRetriever.getApplicationListeners() 末尾,都是 AnnotationAwareOrderComparator.sort。规则:@Order 值小的先执行;裸 ApplicationListener 没实现 Ordered/没注解 = LOWEST_PRECEDENCE(排最后);SmartApplicationListener.getOrder() 默认也是 LOWEST_PRECEDENCE;ApplicationListenerMethodAdapter 的 order 来自方法上的 @Order。注意:排序只对"匹配同一事件的监听器集合"生效——不同事件类型的监听器之间没有顺序概念。 line1 line2

14.14 本章小结

本章沿着四个问题把 Spring 7.0.9 事件机制的源码链路走了一遍:

  1. 抽象层:ApplicationEvent 与 PayloadApplicationEvent 承载事件,三层监听接口把类型判定从 Class 升级到 ResolvableType。
  2. 发布层:AbstractApplicationContext.publishEvent 完成包装、早期事件入队、立即多播、父容器冒泡。
  3. 装配层:initApplicationEventMulticaster、registerListeners、ApplicationListenerDetector、EventListenerMethodProcessor 串成装配链。
  4. 分发层:ListenerCacheKey 缓存预过滤结果,supportsEvent 三层判定,AnnotationAwareOrderComparator 排序。
  5. 注解层:EventListenerFactory 策略链到 ApplicationListenerMethodAdapter,SpEL 求值、反射调用、返回值再发布。
  6. 事务层:TransactionalEventListener 桥接 defaultExecution,发布点注册事务同步并推迟到事务回调执行。

本章关键类清单

类名模块与相对路径职责一句话
ApplicationEventspring-context/src/main/java/org/springframework/context/ApplicationEvent.java事件抽象基类:source + 不可变 timestamp
PayloadApplicationEventspring-context/src/main/java/org/springframework/context/PayloadApplicationEvent.java任意对象包装为事件,保留泛型
ApplicationEventPublisherspring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java发布门面接口
ApplicationListenerspring-context/src/main/java/org/springframework/context/ApplicationListener.java监听根接口 onApplicationEvent
SmartApplicationListenerspring-context/src/main/java/org/springframework/context/event/SmartApplicationListener.java暴露事件/源类型判定与 order 元数据
GenericApplicationListenerspring-context/src/main/java/org/springframework/context/event/GenericApplicationListener.java基于 ResolvableType 的事件类型判定接口
GenericApplicationListenerAdapterspring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java包装裸监听器,缓存 declaredEventType
ApplicationEventMulticasterspring-context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java多播器接口:注册管理 + multicastEvent
AbstractApplicationEventMulticasterspring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java注册表 + retrieverCache + 三层过滤排序
SimpleApplicationEventMulticasterspring-context/src/main/java/org/springframework/context/event/SimpleApplicationEventMulticaster.java默认多播:同步/异步、errorHandler、CCE 容错
AbstractApplicationContextspring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.javapublishEvent 全流程与容器事件装配
ApplicationListenerDetectorspring-context/src/main/java/org/springframework/context/support/ApplicationListenerDetector.javaBeanPostProcessor 兜底捕获单例监听器
EventListenerspring-context/src/main/java/org/springframework/context/event/EventListener.java注解:classes/condition/defaultExecution/id
EventListenerMethodProcessorspring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java扫描 @EventListener 方法并注册适配器
EventListenerFactoryspring-context/src/main/java/org/springframework/context/event/EventListenerFactory.java策略接口:supportsMethod + createApplicationListener
DefaultEventListenerFactoryspring-context/src/main/java/org/springframework/context/event/DefaultEventListenerFactory.java兜底工厂,产出 ApplicationListenerMethodAdapter
ApplicationListenerMethodAdapterspring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java注解方法的监听器化身,返回值再发布
EventExpressionEvaluatorspring-context/src/main/java/org/springframework/context/event/EventExpressionEvaluator.javaSpEL condition 求值与表达式缓存
TransactionalEventListenerspring-tx/src/main/java/org/springframework/transaction/event/TransactionalEventListener.java组合注解,phase 默认 AFTER_COMMIT
TransactionPhasespring-tx/src/main/java/org/springframework/transaction/event/TransactionPhase.javaBEFORE_COMMIT/AFTER_COMMIT/AFTER_ROLLBACK/AFTER_COMPLETION
TransactionalEventListenerFactoryspring-tx/src/main/java/org/springframework/transaction/event/TransactionalEventListenerFactory.java识别事务监听器方法,order=50
RestrictedTransactionalEventListenerFactoryspring-tx/src/main/java/org/springframework/transaction/annotation/RestrictedTransactionalEventListenerFactory.java拦截监听器方法上的违规 @Transactional 配置
TransactionalApplicationListenerMethodAdapterspring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapter.java注册事务同步,决定降级或推迟执行
TransactionalApplicationListenerSynchronizationspring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListenerSynchronization.javabeforeCommit/afterCompletion(status) 分发
TransactionalApplicationListenerspring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListener.java编程式事务监听器接口,不支持异步
SourceFilteringListenerspring-context/src/main/java/org/springframework/context/event/SourceFilteringListener.java按事件 source 身份过滤的委托监听器
ContextRefreshedEvent 等spring-context/src/main/java/org/springframework/context/event/容器生命周期事件