| 构件(Artifact) | io.github.microsphere-projects:microsphere-java-core(0.3.20-SNAPSHOT,release 分支) |
| 包 | io.microsphere.event —— 10 个主源码类型 · 被 io.microsphere.io.event(3 个类型)与 io.microsphere.io.StandardFileWatchService 使用 |
| JDK 级别 | 以 Java 8 编译;CI 在 JDK 8 / 11 / 17 / 21 / 25 上测试 |
依赖的其它 core 包 |
io.microsphere.lang(Prioritized)、io.microsphere.lang.invoke(LambdaUtils)、io.microsphere.reflect(TypeUtils)、io.microsphere.collection(MapUtils、ListUtils、SetUtils、MapUtils.newConcurrentHashMap)、io.microsphere.util(Assert、ClassLoaderUtils、ServiceLoaderUtils)、io.microsphere.lang.function(ThrowableAction、ThrowableConsumer、ThrowableFunction)、io.microsphere.logging(Logger、LoggerFactory)、io.microsphere.annotation(属于独立的 microsphere-java-annotations 构件) |
| 外部依赖 | 无 —— 纯 JDK(java.util.EventObject、java.util.ServiceLoader、java.util.concurrent.Executor) |
| 被谁使用 | io(StandardFileWatchService)、io.event(FileChangedEvent、FileChangedListener、LoggingFileChangedListener) |
本页所有 file:line 引用均相对于 microsphere-java-core/src/main/java/io/microsphere/(主源码)或
microsphere-java-core/src/test/java/io/microsphere/(测试),读取自 release 工作树。
首次完整出现之后,路径缩写为 event/EventDispatcher.java:40-55 / event/EventDispatcherTest.java:40-55。
当你想在一个普通 Java 对象内部做观察者式的通知,又不希望链路里出现容器、应用上下文或注解处理器时,就该用
io.microsphere.event。 整个引擎只有 10 个类型(event/Event.java、event/GenericEvent.java、
event/EventListener.java、event/ConditionalEventListener.java、event/GenericEventListener.java、
event/Listenable.java、event/EventDispatcher.java、event/AbstractEventDispatcher.java、
event/DirectEventDispatcher.java、event/ParallelEventDispatcher.java),你必须构造的东西只有一个分发器对象:
EventDispatcher dispatcher = EventDispatcher.newDefault(); // event/EventDispatcher.java:70-73
dispatcher.addEventListener(listener); // event/AbstractEventDispatcher.java:116-121
dispatcher.dispatch(new EchoEvent("Hello,World")); // event/AbstractEventDispatcher.java:162-180与 Spring 的 ApplicationEventPublisher 对比 —— 只陈述代码能够证明的差异:
io.microsphere.event |
Spring ApplicationEventPublisher |
|
|---|---|---|
| 谁持有监听器 | EventDispatcher 实例自身,保存在实例级的 ConcurrentMap 中(event/AbstractEventDispatcher.java:99) |
ApplicationContext |
| 监听器如何声明 | 实现 EventListener<E>(event/EventListener.java:71-72)或继承 GenericEventListener(event/GenericEventListener.java:80) |
在 bean 的方法上加注解 |
| 是否需要注解 | 本包根本没有任何注解类型 —— io.microsphere.event 声明的 @interface 数量为 0 |
@EventListener、@TransactionalEventListener、@ApplicationModuleListener |
| 监听器能收到哪些事件 | 由监听器的泛型参数推导,入口是 EventListener.findEventType(...)(event/EventListener.java:101-150) |
由被注解方法的参数类型推导 |
| 启动时的额外监听器 | Java ServiceLoader —— loadEventListenerInstances()(event/AbstractEventDispatcher.java:214-219) |
bean 扫描 |
| 线程模型 | 每个分发器一个 Executor:DIRECT_EXECUTOR(event/EventDispatcher.java:63)或调用方提供的实现(event/EventDispatcher.java:81-84) |
@Async / 配置的 ApplicationEventMulticaster |
| 否决单个监听器 | ConditionalEventListener.accept(E)(event/ConditionalEventListener.java:55) |
bean 上的 @Conditional / 方法内部 if |
| 中止传播 | 不支持 —— 见 §3.6 | 同样没有中止机制 |
如果你需要事务性事件、跨线程的顺序保证,或者父子上下文层级的事件总线,就不要用它 —— 这些能力在这里都不存在; 每个分发器只有一个扁平的监听器注册表。
Note
event 包同时也是 io.microsphere.io.event 的基础:FileChangedEvent extends Event
(io/event/FileChangedEvent.java:34)、FileChangedListener extends EventListener<FileChangedEvent>
(io/event/FileChangedListener.java:95)。StandardFileWatchService 为每个被监视目录用
EventDispatcher.parallel(...) 建立真正的分发器(io/StandardFileWatchService.java:278),把
FileChangedListener 实例注册上去(io/StandardFileWatchService.java:271),并在监视循环里分发事件
(io/StandardFileWatchService.java:247)。
<dependency>
<groupId>io.github.microsphere-projects</groupId>
<artifactId>microsphere-java-core</artifactId>
<version>0.3.20</version> <!-- 最新已发布标签;0.3.20-SNAPSHOT 是同一版本线的开发版本 -->
</dependency>| 事实 | 取值 | 证据(release) |
|---|---|---|
groupId |
io.github.microsphere-projects |
pom.xml:12 |
artifactId |
microsphere-java-core |
模块 pom.xml |
| 版本属性 | ${revision},构建时展平 |
pom.xml:14 |
release 上 ${revision} 的值 |
0.3.20-SNAPSHOT |
pom.xml:54 |
| 最新已发布标签 | 0.3.20 |
中文组索引中的版本线表(docs/current/user-guide/microsphere-java-core/README.zh.md:68) |
| 构建父工程 | microsphere-build:0.3.16 |
pom.xml:6-10 |
| Java 级别 | 8(reactor 中没有 maven.compiler.* / <release> 属性,级别继承自外部父工程) |
CI 矩阵 java: [ '8' , '11' , '17' , '21' , '25' ],.github/workflows/maven-build.yml:25,distribution: 'temurin' :33 |
| CI 命令 | mvn --batch-mode --update-snapshots --file pom.xml -Drevision=0.0.1-SNAPSHOT -Dsurefire.useSystemClassLoader=false test --activate-profiles test,coverage |
.github/workflows/maven-build.yml:38-45 |
| Spring Framework,JDK 17+ | 7.0.9 |
microsphere-java-parent/pom.xml:23 |
Spring Framework,JDK [1.8,17) |
5.3.39,来自 profile java8-16 |
microsphere-java-parent/pom.xml:54-62(激活条件 <jdk>[1.8,17)</jdk> 在 :57) |
| 导入的 BOM | microsphere-all-bom:0.3.13(microsphere-java-parent/pom.xml:22、:31-37)、spring-framework-bom(:39-46) |
microsphere-java-parent/pom.xml |
| 与本页相关的字节码/API 约束 | 不使用 record、不使用 var、编译期不使用 List.of、不使用 instanceof 模式匹配 |
EventDispatcher.parallel(@Nonnull Executor) 直接返回 new ParallelEventDispatcher(executor),是一个普通工厂方法(event/EventDispatcher.java:82-84) |
Tip
java8-16 是一个构建期属性开关,不是运行期开关;io.microsphere.event 完全不依赖 Spring,因此本页内容不会
在 Spring 5 与 Spring 7 之间发生变化。只有当你把面向 Spring 的其它 microsphere-java-* 模块加入同一个 reactor 时,
它才会产生影响。
| 条目 | 值 | 依据 |
|---|---|---|
| 文档所在线 | release 分支,${revision} = 0.3.20-SNAPSHOT |
根 pom.xml:54 |
| 最新已发布版本 | 0.3.20,标签日期 2026-10-16 |
标签 0.3.20 的提交信息为 chore: merge main into release [skip ci],提交时间 2026-10-16T03:43:20Z |
| 上一版本 | 0.3.19,标签日期 2026-08-30 |
标签 0.3.19,提交时间 2026-08-30T12:51:37Z |
| 变更日志来源 | 仓库根目录的 release-notes.md |
本项目不发布 CHANGELOG.md;release-notes.md 的 v0.3.16–v0.3.20 段落写着 Release notes generation failed 并保留原始提交列表,因此下文一律以提交主题引用,并逐条与 release 源码核对。 |
0.3.20 在这一区域没有新增 API 面——0.3.19...0.3.20 区间是一次加固性发布。它实际改变了什么、以及这些改动触及本页源码的程度如下:
- 空值契约。 本页覆盖范围内 2 / 10 个主源码在签名上声明了
@Nonnull/@Nullable。区间内点名本区域的主题:45c4b6b7 Add nullability annotations to event APIs。 - 该区间没有其它改动触及本页覆盖的区域。
Note
如何读这份提交清单:只有 EventDispatcher 与 Listenable 两个源码带注解;该区间没有其它改动触及 io.microsphere.event。
| 类型 | 种类 | @since |
职责 |
|---|---|---|---|
Event(event/Event.java:29) |
抽象类,extends java.util.EventObject |
1.0.0 | 携带 final long timestamp,在构造器里取 System.currentTimeMillis()(:36、:44-47),通过 getTimestamp() 暴露(:49-51) |
GenericEvent<S>(event/GenericEvent.java:42) |
具体类 | 1.0.0 | 带类型的 source 访问器:public S getSource() 对 super.getSource() 做转型(:48-50) |
EventListener<E extends Event>(event/EventListener.java:72) |
@FunctionalInterface,继承 java.util.EventListener + Prioritized |
1.0.0 | void onEvent(E event)(:205)+ 默认 int getPriority() 返回 NORMAL_PRIORITY(:214-216) |
ConditionalEventListener<E extends Event>(event/ConditionalEventListener.java:47) |
接口 | 1.0.0 | 增加 boolean accept(E event)(:55)—— 单个监听器级的否决 |
GenericEventListener(event/GenericEventListener.java:80) |
抽象类,implements EventListener<Event> |
1.0.0 | 一个监听器、多个处理方法,按事件的运行时类型分发 |
Listenable<E extends EventListener<?>>(event/Listenable.java:49) |
接口 | 1.0.0 | 注册 / 移除 / 查询契约,包含 assertListener(:80-90) |
EventDispatcher(event/EventDispatcher.java:58) |
接口,extends Listenable<EventListener<?>> |
1.0.0 | dispatch(Event)(:102)、DIRECT_EXECUTOR(:63)、工厂方法(:70-95)、默认 getExecutor()(:111-113) |
AbstractEventDispatcher(event/AbstractEventDispatcher.java:93) |
抽象类 | 1.0.0 | 注册表、缓存、匹配循环与 SPI 引导 |
DirectEventDispatcher(event/DirectEventDispatcher.java:56) |
final 类 |
1.0.0 | super(DIRECT_EXECUTOR)(:58-60)—— 同步、顺序执行 |
ParallelEventDispatcher(event/ParallelEventDispatcher.java:33) |
类 | 1.0.0 | 无参构造器使用 ForkJoinPool.commonPool()(:35-37);ParallelEventDispatcher(Executor)(:39-41) |
本包中没有 CancelDispatcherOperationException,没有 EventDispatcher 构建器,也没有任何 @interface。
Listenable 的 javadoc 仍然宣称会拒绝 final 类(event/Listenable.java:67-75),但该检查已被注释掉
(event/Listenable.java:83-89)—— 见 §6。
| 签名 | 声明位置 | null / 重复值行为 |
|---|---|---|
void addEventListener(E listener) throws NullPointerException, IllegalArgumentException |
event/Listenable.java:100;实现 event/AbstractEventDispatcher.java:116-121 |
null → IllegalArgumentException(不是 NPE),来自 assertListener → Assert.assertNotNull(event/Listenable.java:81、util/Assert.java:149-153);重复注册被 addIfAbsent 丢弃(event/AbstractEventDispatcher.java:119、:155-159) |
default void addEventListeners(E listener, E... others) |
event/Listenable.java:110-116 |
拼接后委托给 Iterable 重载 |
default void addEventListeners(Iterable<E> listeners) |
event/Listenable.java:125-127 |
每个元素一次 addEventListener 调用 |
void removeEventListener(E listener) |
event/Listenable.java:137;实现 event/AbstractEventDispatcher.java:124-127 |
listeners.remove(listener) —— 基于 equals() |
default void removeEventListeners(Iterable<E> listeners) |
event/Listenable.java:148-150 |
同上,逐元素执行 |
default void removeAllEventListeners() |
event/Listenable.java:157-159 |
javadoc 写了 @return a amount of removed listeners,但方法返回类型是 void |
@Nonnull List<E> getAllEventListeners() |
event/Listenable.java:167-168;实现 event/AbstractEventDispatcher.java:130-139 |
@Immutable;去重、按优先级排序的快照,外层包了 Collections.unmodifiableList |
| 签名 | 声明位置 | 行为 |
|---|---|---|
void dispatch(Event event) |
event/EventDispatcher.java:102;实现 event/AbstractEventDispatcher.java:162-180 |
向分发器的 Executor 提交一个任务,该任务内部顺序遍历匹配的监听器 |
@Nonnull static EventDispatcher newDefault() |
event/EventDispatcher.java:70-73 |
new DirectEventDispatcher() |
@Nonnull static EventDispatcher parallel(@Nonnull Executor executor) |
event/EventDispatcher.java:81-84 |
new ParallelEventDispatcher(executor);@Nonnull 只是声明性约定 —— 工厂内部没有运行期 null 检查 |
@Nonnull static EventDispatcher of(@Nullable Executor executor) |
event/EventDispatcher.java:92-95 |
executor == null ? newDefault() : parallel(executor) —— 文档化的容错入口 |
Executor DIRECT_EXECUTOR = Runnable::run |
event/EventDispatcher.java:63 |
在调用线程上直接运行任务 |
default Executor getExecutor() |
event/EventDispatcher.java:111-113 |
对临时实现的 EventDispatcher 返回 DIRECT_EXECUTOR |
public final Executor getExecutor() |
event/AbstractEventDispatcher.java:186-188 |
final —— AbstractEventDispatcher 的子类无法更换自己的 executor;构造器传入的值即固定值(:109-113) |
protected AbstractEventDispatcher(Executor executor) |
event/AbstractEventDispatcher.java:109-113 |
null → IllegalArgumentException(:110、util/Assert.java:149-153)—— 由 event/AbstractEventDispatcherTest.java:36-40 证明 |
一次 dispatch 只有在下面两条规则同时成立时才会把事件交给某个监听器:
| # | 规则 | 位置 | 说明 |
|---|---|---|---|
| 1 | 注册键:监听器被存放在由泛型参数解析出的那一个 Class<? extends Event> 键下 |
event/AbstractEventDispatcher.java:190-192(doInListener → findEventType(listener)),键表在 :99 |
无法解析时 findEventType 返回 null,监听器随后被静默地不注册(event/AbstractEventDispatcher.java:194-195)—— 这是降级策略,而不是抛异常 |
| 2 | 投递判定:entry.getKey().isAssignableFrom(event.getClass()) |
event/AbstractEventDispatcher.java:168 |
因此注册在 Event 下的监听器会收到所有子类事件;注册在 EchoEvent 下的监听器收不到普通 Event —— event/DirectEventDispatcherTest.java:111-124 |
| 3 | 否决(仅 ConditionalEventListener) |
event/AbstractEventDispatcher.java:170-175 |
accept(...) 为 false ⇒ 只跳过该监听器,其余照常执行 |
findEventType 的解析顺序(event/EventListener.java:131-150):
| 场景 | 机制 | 位置 |
|---|---|---|
| lambda / 匿名函数式形式 | LambdaUtils.resolveLambdaMethodParameterTypes(listenerClass, EventListener.class);解析出唯一参数类型时采纳 |
event/EventListener.java:135-138 |
| 普通类 | 遍历 TypeUtils.getAllParameterizedTypes(...),第一个非 null 的 ParameterizedType 匹配胜出,否则递归父类 |
event/EventListener.java:141-146 |
ParameterizedType 检查 |
原始类型可赋给 EventListener,第一个可赋给 Event 的实际类型参数胜出 |
event/EventListener.java:182-198 |
null / 非监听器类 |
返回 null(:134 的 isAssignableFrom 守卫) |
由 event/EventListenerTest.java:58-61 证明 |
Important
io.microsphere.event 声明的注解类型数量为 0 —— 该包 10 个主源码文件里没有任何 @interface,因此无法给出
逐注解的条件表,也没有任何成员携带 @Since 注解。@since 只以 Javadoc 标签形式出现,并且 10 个类型全部标注
1.0.0(event/Event.java:27、event/GenericEvent.java:40、event/EventListener.java:69、
event/ConditionalEventListener.java:45、event/GenericEventListener.java:78、event/Listenable.java:47、
event/EventDispatcher.java:56、event/AbstractEventDispatcher.java:91、event/DirectEventDispatcher.java:54、
event/ParallelEventDispatcher.java:31)。条件过滤改用接口与方法签名来表达:
| 机制 | 签名 | 语义 |
|---|---|---|
ConditionalEventListener |
boolean accept(E event)(event/ConditionalEventListener.java:55) |
对每个匹配的监听器,在 onEvent 之前按优先级顺序调用(event/AbstractEventDispatcher.java:170-177);返回 false 只跳过该监听器 |
GenericEventListener |
public final void onEvent(Event event)(event/GenericEventListener.java:106) |
扇出到构造器中收集到的处理方法(:86-89、:95-104) |
子类方法必须同时满足下面五条才会被当作处理方法(event/GenericEventListener.java:128-151):
| # | 判定条件 | 行号 |
|---|---|---|
| 1 | 不是 onEvent(Event) 方法本身 |
:130 |
| 2 | 返回 void |
:134 |
| 3 | 不声明任何异常类型 | :138-141 |
| 4 | 恰好一个参数 | :144-146 |
| 5 | 该参数是 Event 或其子类 |
:150 |
public 可访问性是隐式强制的:候选集合来自 getClass().getMethods()(event/GenericEventListener.java:98),
它只返回 public 成员 —— 因此 event/GenericEventListenerTest.java:73-75 里的
protected void notEvent(EchoEvent) 永远不会被收集。方法名不重要(event/GenericEventListenerTest.java:65)。
而进入该 Map 的查找依据是精确的运行时类型,不是可赋值性:
handleEventMethods.getOrDefault(event.getClass(), emptySet())(event/GenericEventListener.java:108)。
EventListener 是 Prioritized(event/EventListener.java:72),而 Prioritized.compareTo 对
getPriority() 的原始 int 做升序比较(lang/Prioritized.java:103-105),所以数值更小的先执行:
| 常量 | 取值 | 含义 | 行号 |
|---|---|---|---|
MAX_PRIORITY |
Integer.MIN_VALUE |
最高优先级(最先执行) | lang/Prioritized.java:81 |
NORMAL_PRIORITY |
0 |
未覆写 getPriority() 的监听器的默认值 |
lang/Prioritized.java:91、event/EventListener.java:214-216 |
MIN_PRIORITY |
Integer.MAX_VALUE |
最低优先级(最后执行) | lang/Prioritized.java:86 |
排序会发生两次:一次在注册表内部,每次结构变更后(Collections.sort(listeners),
event/AbstractEventDispatcher.java:201);另一次在每次分发时(Stream.sorted(),:152,依赖
lang/Prioritized.java:103-105 实现的 Comparable)。SPI 加载出来的监听器在注册前也排过序
(event/AbstractEventDispatcher.java:215-217),与全仓库“不信任 ServiceLoader 文件顺序”的规则一致。
取消语义 —— 精确说明存在什么: 什么都不存在。
release工作树中不存在CancelDispatcherOperationException,也不存在任何其它取消/中止类型(event/*.java就是 §3.1 列出的那 10 个类型)。- 监听器无法中止传播。
onEvent返回void(event/EventListener.java:205),分发循环忽略所有返回值且从不 break(event/AbstractEventDispatcher.java:168-178)。 - 真正存在的短路只有两处:
accept(...) == false恰好跳过一个监听器 ——forEach内部的return;, 位于event/AbstractEventDispatcher.java:172-174;- 某个监听器抛异常会中止本次分发中剩余的监听器,因为
event/AbstractEventDispatcher.java:177的listener.onEvent(event)没有被任何try/catch包裹。使用DirectEventDispatcher时异常会沿着调用线程穿出dispatch()(Runnable::run,event/EventDispatcher.java:63);使用真实Executor时异常在工作线程上抛出, 调用方无法捕获。
StandardFileWatchService.stop()里取消事件循环(io/StandardFileWatchService.java:192)是对监视循环的Future.cancel,与本包的监听器契约无关。
event/DirectEventDispatcherTest.java:63-80 —— EchoEventListener 监听 EchoEvent;EchoEventListener2 监听
Event 且 getPriority() 返回 -1(event/EchoEventListener2.java:43-46),因此排在前面:
dispatcher.addEventListener(echoEventListener);
dispatcher.addEventListener(echoEventListener2);
assertEquals(ofList(echoEventListener2, echoEventListener), dispatcher.getAllEventListeners()); // :68
dispatcher.addEventListener(echoEventListener); // duplicate add
assertEquals(ofList(echoEventListener2, echoEventListener), dispatcher.getAllEventListeners()); // :71-72
dispatcher.removeEventListener(echoEventListener);
assertEquals(ofList((EventListener) echoEventListener2), dispatcher.getAllEventListeners()); // :75-76event/DirectEventDispatcherTest.java:108-124:
dispatcher.addEventListener(echoEventListener); // registered for EchoEvent
dispatcher.dispatch(new Event("Test") {}); // :114-115
assertEquals(0, echoEventListener.getEventOccurs()); // :118 no listener ran
dispatcher.dispatch(new EchoEvent("Hello,World")); // :121
assertEquals(1, echoEventListener.getEventOccurs()); // :124同一测试(:133-145)表明注册在 Event 下的监听器会同时收到普通 Event 和 EchoEvent,即一次分发可以命中多个键
下的监听器。
event/ConditionalEventListenerTest.java:39-76 —— accept 比较 source,onEvent 记录 source:
OnlyHelloWorldEventListener listener = new OnlyHelloWorldEventListener();
eventDispatcher.addEventListener(listener); // :44
eventDispatcher.dispatch(new EchoEvent("1")); // :46
assertNull(listener.getSource()); // :48 accept() returned false
eventDispatcher.dispatch(new EchoEvent("Hello,World")); // :50
assertEquals("Hello,World", listener.getSource()); // :52
static class OnlyHelloWorldEventListener implements ConditionalEventListener<EchoEvent> {
public boolean accept(EchoEvent event) { return "Hello,World".equals(event.getSource()); } // :64-66
public void onEvent(EchoEvent event) { source = (String) event.getSource(); } // :69-71
}event/GenericEventListenerTest.java:48-83 —— 注意名为 event(...) 的处理方法、双参数重载以及 protected 方法:
class MyGenericEventListener extends GenericEventListener {
public void onEvent(EchoEvent echoEvent) { this.echoEvent = echoEvent; } // :61-63 collected
public void event(EchoEvent echoEvent) { assertEquals("Hello,World", echoEvent.getSource()); } // :65-67 collected
public void event(EchoEvent echoEvent, Object arg) { this.echoEvent = echoEvent; } // :69-71 rejected (2 params)
protected void notEvent(EchoEvent echoEvent) { this.echoEvent = echoEvent; } // :73-75 rejected (not public)
}
eventDispatcher.dispatch(new EchoEvent("Hello,World"));
assertEquals(echoEvent, listener.getEchoEvent()); // :52-54event/DirectEventDispatcherTest.java:153-183:
EventListener<?> eventListener = event -> { eventHolder.setValue(event); counter.incrementAndGet(); }; // :159-162
EventListener<EchoEvent> echoEventEventListener = event -> { ... }; // :164-167
dispatcher.dispatch(new Event("Test") {}); // :175 only the Event-typed lambda fires
assertEquals(1, counter.get()); // :177
dispatcher.dispatch(new EchoEvent("Hello,World")); // :180 both fire
assertEquals(3, counter.get()); // :182event/EventListenerTest.java:43-56 证明了目标分别为 Event.class、EchoEvent.class、FileChangedEvent.class 的
lambda 都能被解析;:34-41 证明两个 EchoEventListener 类同样可以,其中包含继承自
Vector<EventListener<Event>> 的情形。
assertThrows(IllegalArgumentException.class, () -> assertListener(null)); // event/ListenableTest.java:19
assertThrows(IllegalArgumentException.class, () -> new AbstractEventDispatcher(null) {}); // event/AbstractEventDispatcherTest.java:38
assertNull(findEventType((Class) null)); // event/EventListenerTest.java:60
assertDoesNotThrow(() -> dispatcher.doInListener((Class) null, eventListeners -> {})); // event/AbstractEventDispatcherTest.java:68-69
assertThrows(IllegalArgumentException.class, () -> new GenericEvent<>(null)); // event/GenericEventTest.java:46-48 (EventObject's own guard)event/EventDispatcherTest.java:47-51 断言 newDefault().getAllEventListeners().size() == 2 —— 这两个来自
src/test/resources/META-INF/services/io.microsphere.event.EventListener,其中列出了 EchoEventListener 与
EchoEventListener2。event/AbstractEventDispatcherTest.java:42-47 断言加载结果非空;:49-59 断言在 class loader
损坏时行为降级为 no-op 而非抛异常(因为 loadEventListenerInstances() 把 Throwable 吞进 logger.trace,
event/AbstractEventDispatcher.java:215-218)。
event/ParallelEventDispatcherTest.java:46-52:
eventDispatcher.dispatch(new EchoEvent("Hello,World"));
ForkJoinPool.commonPool().awaitTermination(1, SECONDS); // :49 — the test must give the worker time
assertEquals(1, listener.getEventOccurs()); // :51- 序列化:
Event extends java.util.EventObject(event/Event.java:29)并固定了serialVersionUID = -1704315605423947137L(:31);timestamp字段是final且非 transient(:36),因此会随事件 一起序列化。测试里的EchoEventListener实现了Serializable(event/EchoEventListener.java:26),但库本身对EventListener没有任何Serializable要求。 - JDK 8 到 25:引擎只用到
ServiceLoader、Executor、ConcurrentHashMap、LinkedList、Stream 和java.lang.reflect,没有访问任何 JDK 内部实现,因此本包不需要--add-opens。findEventType的 lambda 分支 (event/EventListener.java:135-138)确实会通过LambdaUtils反射读取生成的 lambda 类的声明方法 (lang/invoke/LambdaUtils.java:98-128),那是对合成类的反射读取,并非受限的sun.*访问。 - 与 Spring 零耦合:
event中没有任何类型引用org.springframework。java8-16profile 的 Spring 版本切换 (microsphere-java-parent/pom.xml:54-62)对本页描述的行为没有影响。 - SPI 文件:
release上core附带 13 个META-INF/services文件,其中包括META-INF/services/io.microsphere.event.EventDispatcher,内容为DirectEventDispatcher和ParallelEventDispatcher。本仓库中没有任何代码通过ServiceLoader加载EventDispatcher—— 除event/EventDispatcherTest.java:44的类身份断言之外不存在EventDispatcher.class的服务查找;该文件是为下游使用者 发布的。主源码资源里没有META-INF/services/io.microsphere.event.EventListener,只有测试资源里有(§4.7)。 - 日志的依赖方向:
AbstractEventDispatcher持有Logger(event/AbstractEventDispatcher.java:21、:39、:97),来自io.microsphere.logging.LoggerFactory,因此分层上event位于logging之上;logging自身并不 importevent。
- 注册表只有一半是线程安全的。
listenersCache是ConcurrentHashMap(event/AbstractEventDispatcher.java:99,创建于collection/MapUtils.java:705-707),但它的 value 是普通LinkedList(event/AbstractEventDispatcher.java:197、collection/ListUtils.java:448-450)。写操作持有synchronized (mutex)(:196-202);分发时的读路径(:146-152)完全不加锁。因此在某次分发正在抽取监听器流 时注册新监听器并不安全,而且没有测试覆盖这种场景。安全用法:启动阶段完成全部注册,之后再开始分发。 - 每次分发取的是快照。
Stream.sorted()(event/AbstractEventDispatcher.java:152)是有状态操作,会在forEach运行之前把上游全部抽取完,所以监听器集合在分发开始时就已固定。在监听器里再注册一个监听器不会影响进行中的这次分发, 只影响下一次。 - 异常不会被隔离。
listener.onEvent(event)(event/AbstractEventDispatcher.java:177)周围没有try/catch; 一个抛异常的监听器会“取消”其后所有监听器(见 §3.6)。这是引擎中唯一的中止传播手段,而且属于意外效果而非设计。 GenericEventListener的反射调用会重新抛成RuntimeException。onEvent通过ThrowableConsumer.execute调用每个处理方法(event/GenericEventListener.java:109-111),其默认处理器是throw new RuntimeException(failure)(lang/function/ThrowableConsumer.java:104-106、:151-153)—— 因此处理方法 抛出的InvocationTargetException会被包装后上抛,并按第 3 条中止循环。- 处理方法按精确类型匹配。
handleEventMethods.getOrDefault(event.getClass(), emptySet())(event/GenericEventListener.java:108)不会向上查找父类:GenericEventListener子类里声明的public void onEvent(Event event)遇到EchoEvent时不会被调用,尽管分发器层面的规则(§3.4 第 2 条)是基于 可赋值性的。 - 无法解析的监听器会被静默丢弃。 若
findEventType返回null—— 例如没有泛型信息的裸EventListener——doInListener会不带任何警告地跳过注册(event/AbstractEventDispatcher.java:194-195),该监听器永远不触发。如果发现 某个监听器“失灵”,请打印getAllEventListeners()。 assertListener已不再拒绝 final 类。 final 检查被注释掉了(event/Listenable.java:83-89),而 javadoc 仍然 宣称会抛异常(:67-75)。真正的拒绝只有null→IllegalArgumentException(:81),由event/ListenableTest.java:18-20断言。不要依据 javadoc 编写代码。removeEventListener依赖equals。listeners.remove(listener)(event/AbstractEventDispatcher.java:126) 与addIfAbsent里的contains判断(:156)都使用equals,因此一个忽略对象身份的equals覆写 ——event/EchoEventListener2.java:52-60只比较类 —— 会让两个不同实例互相顶替,第二次注册被视为重复。- “并行”指的是一个任务,而不是 N 个任务。
dispatch只向 executor 提交一个Runnable(event/AbstractEventDispatcher.java:167),监听器循环在该任务内部完成,所以ParallelEventDispatcher的并行发生在 多次 dispatch 之间,永远不是同一次 dispatch 的监听器之间。监听器仍然顺序执行,而且dispatch()在任何监听器运行 之前就已返回(§4.8)。 EventDispatcher.parallel(...)不做 null 检查(event/EventDispatcher.java:82-84);失败会稍后在AbstractEventDispatcher构造器里以IllegalArgumentException暴露(:110)。当 executor 可能缺省时请改用of(@Nullable Executor)(:92-95)—— 那才是文档化的降级路径,StandardFileWatchService也体现了这一意图:默认值 就是DIRECT_EXECUTOR(io/StandardFileWatchService.java:151-153)。removeAllEventListeners()没有返回值。 方法声明为default void,而 javadoc 却承诺 “a amount of removed listeners”(event/Listenable.java:152-159)。- 没有事件层级、没有载荷抽象、跨键没有顺序保证。 注册在不同事件类下的监听器由
ConcurrentHashMap的 stream 合并 (event/AbstractEventDispatcher.java:146-152),随后只按优先级排序;不同键下优先级相同的监听器之间没有确定性的相对 顺序。
- 语言抽象 ——
Prioritized的排序算术、LambdaUtils,以及包装监听器异常的Throwable*工具。 - I/O、NIO、扫描与文件过滤器 ——
io.microsphere.io.event与StandardFileWatchService, 本引擎在模块内唯一的消费者。 - 日志 ——
AbstractEventDispatcher用来吞掉 SPI 失败的Logger。 - 集合与过滤器 ——
MapUtils.newConcurrentHashMap与ListUtils.newLinkedList, 监听器注册表正是由这两个集合搭成。 - 事件分发 —— 开发者指南 —— 分发循环、缓存失效、 设计模式、线程安全分析与测试策略。