浅谈Spring的事件驱动机制
前言:
每次去翻源码都是出于项目需要,越发觉得自己的主动性比较弱,但偶尔被逼着把源码翻一遍,也着实能收获不少。这次翻看Spring对事件处理机制的代码,主要是因为现在的项目将会话
Session
放在了ThreadLocal
里,而异步的事件处理对线程是不共享的,为了确认这一点,将整个过程DEBUG
了多遍,记录一些收获。
Spring对事件的支持
ApplicationEvent
Spring的所有事件的父类,Spring还提供了一个比较好用的子类
PayloadApplicationEvent
,发布事件的时候只需要发布一个定义好的对象,处理的时候接收这个对象即可。Spring自身的大部分事件是通过
SpringApplicationEvent
发生的,也是ApplicationEvent
的子类。ApplicationEventPublisher
Spring的
ApplicationContext
接口继承自ApplicationEventPublisher
,发布事件方法的默认实现在AbstractApplicationContext
中找到:/** * Publish the given event to all listeners. * @param event the event to publish (may be an {@link ApplicationEvent} * or a payload object to be turned into a {@link PayloadApplicationEvent}) * @param eventType the resolved event type, if known * @since 4.2 */ protected void publishEvent(Object event, @Nullable ResolvableType eventType) { Assert.notNull(event, "Event must not be null"); // Decorate event as an ApplicationEvent if necessary ApplicationEvent applicationEvent; if (event instanceof ApplicationEvent) { applicationEvent = (ApplicationEvent) event; } else { applicationEvent = new PayloadApplicationEvent<>(this, event); if (eventType == null) { eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType(); } } // Multicast right now if possible - or lazily once the multicaster is initialized if (this.earlyApplicationEvents != null) { this.earlyApplicationEvents.add(applicationEvent); } else { getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); // 广播事件,从这里进入事件发布和处理机制 } // Publish event via parent context as well... if (this.parent != null) { if (this.parent instanceof AbstractApplicationContext) { ((AbstractApplicationContext) this.parent).publishEvent(event, eventType); } else { this.parent.publishEvent(event); } } }
简单来讲,Spring允许你发布一个
ApplicationEvent
的子类,或者直接发布一个对象即可,它会帮你转成一个PayloadApplicationEvent
,然后发布到所有的EventListener
。ApplicationEventMulticaster
多路广播的作用就是将事件匹配到对应的事件监听器,然后通过监听器执行相应的处理逻辑。广播事件的代码在
SimpleApplicationEventMulticaster
中找到:/** * Multicast the given application event to appropriate listeners. * <p>If the {@code eventType} is {@code null}, a default type is built * based on the {@code event} instance. * @param event the event to multicast * @param eventType the type of event (can be null) * @since 4.2 */ @Override public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(() -> invokeListener(listener, event)); } else { invokeListener(listener, event); } } }
getApplicationListeners
方法获取事件相对应的ApplicationListener
。看到这里终于找到了异步处理机制的影子,广播的事件会先由Executor来执行,来异步处理我们的事件。
/** * Set a custom executor (typically a {@link org.springframework.core.task.TaskExecutor}) * to invoke each listener with. * <p>Default is equivalent to {@link org.springframework.core.task.SyncTaskExecutor}, * executing all listeners synchronously in the calling thread. * <p>Consider specifying an asynchronous task executor here to not block the * caller until all listeners have been executed. However, note that asynchronous * execution will not participate in the caller's thread context (class loader, * transaction association) unless the TaskExecutor explicitly supports this. * @see org.springframework.core.task.SyncTaskExecutor * @see org.springframework.core.task.SimpleAsyncTaskExecutor */ public void setTaskExecutor(@Nullable Executor taskExecutor) { this.taskExecutor = taskExecutor; }
但是实际上,这只是事件发布的异步机制,这和我想说的的事件异步处理机制还不是一回事儿。实际上,我们自定义发布的事件走到这里,
executor
是没有的(可能需要其它的配置),发布的过程完全是在一个线程中同步完成的,直到invokeListener
方法进入EventListener
的onApplicationEvent
方法。也就是说真正的异步处理并不是在这里,我们后面继续这个话题。ApplicationListener
这个类是事件监听执行器的父类,很多Spring的内部事件会通过这个类的子类去完成很多事件驱动的操作,比如:
BootstrapApplicationListener
、ConfigFileApplicationListener
等。而我们系统在使用事件的时候,Spring会为我们
new
一个ApplicationListenerMethodAdapter
的子类,这个子类是为适配@EventListener
注解的事件处理机制的实现。这个类在调用
onApplicationEvnet
方法的时候,会跳到这里的核心方法doInvoke
方法中去,通过反射机制去执行处理方法。/** * Invoke the event listener method with the given argument values. */ @Nullable protected Object doInvoke(Object... args) { Object bean = getTargetBean(); ReflectionUtils.makeAccessible(this.method); try { return this.method.invoke(bean, args); } catch (IllegalArgumentException ex) { // Exception handle, omitted } }
ApplicationListenerMethodAdapter
中注册了包含@EventListener
注解的所有方法和类。
关于异步
@Async
这是异步方法的统一注解,可以加在方法或类上。具体的实现是通过一个
Interceptor
拦截,获取异步执行的Executor
(可指定),实现异步线程处理。注意:如果不加这个注解,且
ApplicationEventMulticatser
也没有配置异步执行器,事件监听处理的过程就是同步的!AsyncConfigurer
通过实现
AsyncConfigurer
的getAsyncExecutor
方法可以指定自定义异步方法的执行器,要生效还需要在该配置上加@EnableAsync
注解来开启异步功能。TaskDecorator
任务装饰器,可以对执行器进行修饰,在任务执行前后做一些其余的操作。
/** * Decorate the given {@code Runnable}, returning a potentially wrapped * {@code Runnable} for actual execution. * @param runnable the original {@code Runnable} * @return the decorated {@code Runnable} */ Runnable decorate(Runnable runnable);
对Spring事件驱动机制的思考
事件驱动的设计是利用了观察者模式的设计方法,ApplicationListener
是观察者,ApplicationEvent
是被观察者,ApplicationEventPublisher
是主题发布者。观察者模式只是一种设计方法,它并没有强制要求同步还是异步,只是这样地设计可以很好地兼容异步方案的设计。
从源码的角度来看,有两个地方嵌入了异步地设计:
- 广播事件,交由监听器处理的时候;
- 监听器接收事件后,实际交由
AOP
代理去执行事件处理逻辑的时候;
明白了这一点,就可以有针对性地去设计我们自己的系统,避免异步线程不能共享线程本地变量带来地问题。
设计方案
通过让所有自定义事件继承自一个包含上下文的超类
SessionEvent
,然后定义一个包装类SessionEventPublisher
,内部调用ApplicationEventPublisher
来发布SessionEvent
,在发布前后做一些上下文嵌入的操作。@Component public class SessionEventPublisher { private final ApplicationEventPublisher publisher; public SessionEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } public void publishEvent(SessionEvent event) { // 设置上下文的 Session event.setTokenSession(SessionContext.getCurrentSession()); this.publisher.publishEvent(event); } }
考虑异步机制,所有的异步操作必定伴随着使用
Executor
调用新的线程,而Executor
可以使用装饰器Decorator
进行预处理和后处理,所以可以通过自定义配置异步执行的线程池调度器来实现异步上下文同步的问题。@EnableAsync @Configuration public class SpringAsyncConfig implements AsyncConfigurer { public SpringAsyncConfig() { } public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setTaskDecorator(new SessionTaskDecorator()); executor.initialize(); return executor; } }
public class SessionTaskDecorator implements TaskDecorator { public SessionTaskDecorator() { } public Runnable decorate(Runnable runnable) { TokenSession session = SessionContext.getCurrentSession(); return () -> { try { SessionContext.setCurrentSession(session); runnable.run(); } finally { SessionContext.clearSession(); } }; } }
相比之下,显然第二种方案更好。Spring在设计这个问题的时候已经给了可配置的解决方案,我们在使用Spring框架的时候,应该优先想到是否可以通过自动配置来解决问题,当然,这也必须建立在对问题背后的原理和对Spring框架有足够了解的前提下。