Spring Listener 之 喂,在吗?
ShawJie

监听者模式,大家应该已经再熟悉不过了。会计Q和销售员A说:”你那边卖了点儿啥就通知我一声,我这边得记上一笔”,描述很简单,这也就是监听者模式的核心,触发者 + 源事件 + 监听器。Spring在应用内部也对事件监听提供了相关的实现,那么这回我就就来看看Spring的事件组件,聊点…你熟系的陌生的,知道的,不知道的。

先吃个栗子

​ Java对事件相关的构建进行了一些抽象去规范行为,包括源事件EventObject,监听器类EventListener,而Spring对此进行了实现,所以在内容开始之前,我们得先知道怎么创建一个事件并触发它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 定义事件
class SampleEvent extends ApplicationEvent {
private Object someData;
public SampleEvent(Object source, Object someData) {
super(source);
}
}

// 事件SampleEvent的监听者
class SampleListener implements ApplicationListener<SampleEvent> {
@Override
public void onApplication(SampleEvent event) {
// event processing
}
}

public static void main(String[] args) {
// 通过Spring的Publisher进行事件触发动作
new ApplicationEventPublisher().publish(new SampleEvent(getClass(), "data"));
}

​ 一个标准的事件定义触发流程并不复杂,但是其中监听器的注册逻辑以及在事件触发时Spring是如何找到对应的监听器的呢?别急,跟着我慢慢看,说不定看着的同时,还会收获一些关于Spring ApplicationListener小小的新玩法。

​ Ps: 这回的内容相对于之前我们聊过的Spring AOP以及Spring Transcation会简单一些,虽然会依赖到一点点AOP相关的逻辑,想了解的同学也可以点这里去看看,这次也不需要依赖某个smoke-test项目。我们可以稍微放松一下,新建一个Spring Boot项目,和我一起慢慢的去理解ApplicationListener的设计逻辑。

那个发言人呢?

​ 消息实例也好,监听器也罢,中间总得有个玩意把消息发布出来,让监听器能知道事件发生了这才有意义。Spring抽象出了事件发布器ApplicationEventListener并规定了事件发布行为方法publishEvent。我们可以看一下实现了ApplicationEventListener接口的AbstractApplicationContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// org.springframework.context.support.AbstractApplicationContext
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {

...

@Override
public void publishEvent(ApplicationEvent event) {
// 通过标准ApplicationEvent触发事件
publishEvent(event, null);
}

@Override
public void publishEvent(Object event) {
// 通过任意类型对象触发事件
publishEvent(event, null);
}

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

/*
* 如果消息对象不是标准事件类型
* 则需要通过payloadApplicationEvent对象
* 将其包装为标准事件类型对象
*/
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
}

/*
* 我们触发事件的主角,ApplicationEventMulticaster
* 再Multicaster对象还没初始化之前被触发的事件会先存储在
* earlyApplicationEvents集合中,等待广播器初始化后在进行触发
*/
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}

// 同时若ApplicationContext还有父级上下文,则也会再父级上下文中进行一次事件的触发
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
}

找到一个大喇叭

​ 从AbstractApplicationContext中触发事件的逻辑可以知道触发的核心是ApplicationEventMulticaster,那这个对象是哪来的呢?这就得复习一下我们Spring应用启动的refresh方法了。在完成SpringBean扫描之后会通过initApplicationEventMulticaster方法进行广播器的初始化动作,我们可以看一下它的初始化逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
/*
* 确定目前IoC容器内是否已经存在beanName为applicationEventMulticaster的广播器对象
* 若不存在,则进行默认初始化操作,反之,则会将存在的广播器配置进上下文
* 依据这部分逻辑 我们可以自定义自己的广播器
* 为什么需要自定义? 我们先稍一稍 过会儿再说
*/
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
}
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
}
}

Tips

​ 由于我们应用中的上下文AnnotationConfigApplicationContext(标准Spring应用上下文对象)或是AnnotationConfigServletWebServerApplicationContext(应用上下文对象),都基于AbstractApplicationContext进行实现,因此我们也可以直接使用ApplcationContext对象进行消息的发布。换句话说,在需要进行消息推送的时候我们既可以通过自动注入的ApplicationEventPublisher对象进行消息发布,也可以直接通过ApplicationContext对象进行消息发布,毕竟从核心来说,两者是同一个对象。

​ 我们可以通过一个简单的注入证明这一点。但是以语义的方面来看,还是建议使用ApplicationEventPublisher进行消息发布。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@SpringBootApplication
public class SampleApplication implements ApplicationRunner, ApplicationContextAware, ApplicationEventPublisherAware {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}

private ApplicationContext applicationContext;
private ApplicationEventPublisher eventPublisher;

/*
* 通过ApplicationContextAware接口注入ApplicationContext对象
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

/*
* 通过ApplicationEventPublisherAware接口注入ApplicationEventPublisher对象
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) throws BeansException {
this.eventPublisher = eventPublisher;
}

@Override
public void run(ApplicationArguments args) throws Exception {
// 结果为true
Assert.isTrue(this.applicationContext == this.eventPublisher, "not equals");
}
}

发言人有了,听众也得有

作为Spring的应用组件,当然也与IoC容器脱离不开干系,我们需要把我们的监听器注册为Bean交付予IoC容器托管。但是只是单纯的注册为Bean自然是不够的,我们还需要把监听器添加进事件发布器也就是我们的应用上下文中,以在有消息发生时知道有哪些候选监听器。

Spring的监听器定义有两种方式:

  • 类实现ApplicationListener接口,并标记为SpringBean声明为监听器
  • 在被标记为SpringBean的类上,通过对方法标记@EventListener注解声明为监听器

两种声明方式当然也对应着两种扫描时机和注册时机,我们一个一个来看。

标准的监听器

​ 标准模式的监听器我们可以通过实现接口进行定义,譬如通过实现ApplicationListener接口定义一个标准的监听器,或实现SmartApplicationListener接口去根据事件类型和事件源进行事件处理,或实现GenericApplicationListener接口根据消息对象类型(不再局限于事件类型)和消息源进行事件的处理。感兴趣的同学也可以去上面这些接口看看接口可以实现的行为。

​ 在我们完成一个标准监听器定义并将其标记为SpringBean之后,Spring在启动时会扫描将其装载进IoC容器,这时候…如果你是Spring Listener的责任开发,你会通过什么方式把监听器塞进应用上下文的监听器列表中。

​ 要是我的话,可能会通过这两种方式实现装载:

  • 通过定义一个BeanPostProcessor对监听器相关的SpringBean进行处理并将其装载到应用上下文中
    • 这样实现需要注意要在其它BeanPostProcessor完成bean后处理逻辑处理/增强完后再进行装载操作,保证装载的bean已经是后处理的终态
  • 在所有Bean初始化完成之后,通过Spring的回调(实现SmartInitializingSingletonInitializingBean等回调接口类),并注入BeanFactory,在beanFactory中扫描符合条件的监听器类,进行处理并将其装载到应用上下文中

​ Spring对标准的监听器装配采用了第一种方法,至于第二种方法,咱们先撂一撂过会再说。在Spring的应用启动方法refresh()中,有这么一个方法调用,registerBeanPostProcessors(beanFactory),该方法会扫描应用内的beanPostProcessor并将其配置进BeanFactory,我们看一下里面的大体逻辑(我会删掉一些与本文不太重要的逻辑内容)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// org.springframework.context.support.PostProcessorRegistrationDelegate#registerBeanPostProcessors
public static void registerBeanPostProcessors(
ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {

// 从beanFactory中择出所有beanPostProcessor
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

/*
* 扫描/处理 根据优先级将应用内的后处理器配置进BeanFactory
*/

/*
* 在最后将ApplicationListenerDetector后处理器配置进beanFactory中
* 配置在后处理器序列末尾保证监听器在被添加进应用上下文时已经完成了所有必要的处理(譬如Async方法增强)
*/
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
}

​ 诶嘿嘿,好像被忽略掉的方法内容有点多,但是不打紧,我们找到了我们装配标准监听器的主角ApplicationListenerDetector。其会在所有后处理其都处理完成之后,通过beanPostProcessor的钩子方法postProcessAfterInitialization将监听器装载进上下文,我们可以看看它的具体逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// org.springframework.context.support.ApplicationListenerDetector#postProcessAfterInitialization
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
// 确定当前bean是监听器实例
if (bean instanceof ApplicationListener) {
// 判断当前bean的模式是单例的
Boolean flag = this.singletonNames.get(beanName);
if (Boolean.TRUE.equals(flag)) {
// 将bean装配进应用上下文中
this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
}
else if (Boolean.FALSE.equals(flag)) {
/*
* 若监听器模式非单例,因为无法通过事件传播达到多播目的
* 因此无法作为监听器被添加进上下文中
*/
this.singletonNames.remove(beanName);
}
}
// 不需要对bean本身做任何处理 直接返回
return bean;
}

​ 从以上逻辑我们大致能获取到两个信息:

  1. ApplicationListenerDetector只能装载类实例为ApplicationListener的bean,不会处理通过@EventListener注解标记的方法模式的监听器
  2. 非单例SpringBean无法作为监听器被配置进应用上下文中

那么既然ApplicationListenerDetector无法处理@EventListener注解,那这部分注解模式的监听器又是如何被添加进应用上下文的呢?不急,下面就说它了。

@EventListener标注的监听器

​ 关于通过@EventListener注解进行标记监听器的装配方式,诶,那咱么就得回头看看我们应用上下文ApplicationContext在初始化的时候都做了些什么了。

​ 我们都知道SpringBoot应用在启动时会根据类路径中是否存在WebFlux或者Servlet相关类对象进行应用的类型推断(不知道的你现在知道了ヽ(✿゚▽゚)ノ),并根据应用类型选择对应的上下文进行初始化,但是无论是哪种上下文,在构造函数内都会创建一个AnnotatedBeanDefinitionReader的实例。

1
2
3
4
5
// AnnotationConfigServletWebServerApplicationContext#<init>()
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}

​ 而在AnnotatedBeanDefinitionReader的初始化过程中,会通过AnnotationConfigUtils#registerAnnotationConfigProcessors方法向我们的IoC容器中注册一些对象,而我们要关注扫描装配类也就在这里。让我们来看看其向IoC容器中注册bean的逻辑(我还是会忽略掉一些与本文关系不大代码)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {

// 包装BeanDefinitionRegistry
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
if (beanFactory != null) {
if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
// 配置beanFactory的依赖比较器
beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
}
if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
// 配置beanFactory的自动装配候选解析器
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
}
}

Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

// 中间注册了一些其它的Bean,包括JSR支持类,JPA支持类,Configuration配置类后处理器等

/*
* 当IoC容器中不存在eventListenerProcessor时
* 向容器内注册类型为EventListenerMethodProcessor的监听器方法处理器
* 也就是我们所寻找的处理@EventListener注解的玩意
*/
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));
}

/*
* 当IoC容器内不存在eventListenerFactory时
* 向容器内注册类型为DefaultEventListenerFactory的监听器包装工厂类
* 它用于把标记了@EventListener注解的方法包装成标准的监听器类对象
*/
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));
}

return beanDefs;
}

​ 监听器方法处理器EventListenerMethodProcessor,实现了BeanFactoryAware接口和SmartInitializingSingleton接口,前者用于获取beanFactory对象,后者用于通过Spring的钩子回调对IoC容器中的bean进行扫描,并将标记了@EventListener的方法通过eventListenerFactory封装为监听器包装类并添加进应用上下文中。这段描述是不是有点耳熟,没错….在标准监听器小节中提到的第二种监听器装配方式,就是给注解模式的监听器所使用的。我们可以具体看看它的扫描装配逻辑,可能有点点长。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// 实例化后钩子方法
@Override
public void afterSingletonsInstantiated() {
// 获取IoC容器对象BeanFactory
ConfigurableListableBeanFactory beanFactory = this.beanFactory;
// 从容器中捞出所有的Bean
String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
/*
* 获取Bean目标类型逻辑...
* 若获取成功 则进入bean的判断处理阶段
*/
processBean(beanName, type);
}
}

private void processBean(final String beanName, final Class<?> targetType) {
/*
* 由于一个类可以创建多个实例并添加进Spring
* 所以当类上不存在标记@EventListener方法时会被缓存进set
* 若后续有相同类进入 可减少筛选工作量
*/
if (!this.nonAnnotatedClasses.contains(targetType) &&
// 判断目标类是否付和存在EventListener注解的条件
AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
// 判断目标类不是Spring的内部类
!isSpringContainerClass(targetType)) {

Map<Method, EventListener> annotatedMethods = null;
try {
// 在目标类上查找标记了@EventListener注解的方法
annotatedMethods =
MethodIntrospector.selectMethods(
targetType,
(MethodIntrospector.MetadataLookup<EventListener>) method -> AnnotatedElementUtils
.findMergedAnnotation(method, EventListener.class)
);
}
catch (Throwable ex) {
// 类方法无法解析异常
}

// 如果没有找到目标方法,则将目标类塞进无注解缓存
if (CollectionUtils.isEmpty(annotatedMethods)) {
this.nonAnnotatedClasses.add(targetType);
}
else {
// 获取应用上下文对象
ConfigurableApplicationContext context = this.applicationContext;
// 获取监听器包装工厂类
List<EventListenerFactory> factories = this.eventListenerFactories;
for (Method method : annotatedMethods.keySet()) {
for (EventListenerFactory factory : factories) {
// 判断工厂是否支持处理当前方法(默认为true)
if (factory.supportsMethod(method)) {
/*
* 获取可调用方法的实例
* 由于此时目标类实例已经完成增强逻辑 因此获取到的方法实例也是增强后的方法实例
* 即方法调用时包含增强的业务逻辑 譬如标记@Async的异步方法逻辑
*/
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
// 通过工厂类将方法包装为标准监听器格式
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
}
// 将包装后的监听器添加进应用上下文中
context.addApplicationListener(applicationListener);
break;
}
}
}
}
}
}

​ 至此,我们所定义的两种模式的监听器,都被顺利的装在进了应用上下文中。那么万事俱备,现在就拿起我们前边儿准备好的大喇叭喊一嗓子(指发布事件),看看后续的故事又是怎么发展的吧。

Tips - 2

​ 默认的监听器包装工厂类会将@EventListener标注的方法封装进ApplicationListenerMethodAdapter对象中,而包装类在构造时会解析监听器方法的其它属性,包括@Order@EventListener(condition),在事件触发时通过调用method.invoke(args)方法进行本身方法的调用。标准的监听器方法返回值是Void,但通过注解标注的监听器方法可以拥有返回值,我们可看一下包装类对方法返回值的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ApplicationListenerMethodAdapter#handleResult
protected void handleResult(Object result) {
/*
* 若方法返回值为CompletionStage实例
* 如: CompleteFuture,则会在其执行完之后处理其结果
* 若结果不为空 则会作为一个的事件进行发布
*/
if (result instanceof CompletionStage) {
((CompletionStage<?>) result).whenComplete((event, ex) -> {
if (ex != null) {
handleAsyncError(ex);
}
else if (event != null) {
publishEvent(event);
}
});
}
else if (result instanceof ListenableFuture) {
// 逻辑同CompletionStage,若返回值不为空则作为新事件发布
((ListenableFuture<?>) result).addCallback(this::publishEvents, this::handleAsyncError);
}
else {
// 直接进行事件发布
publishEvents(result);
}
/*
* 由于会将返回值作为新的事件进行发布
* 若返回值类型和方法接收的事件类型一致会出现无限循环的情况
* 这点需要格外注意
*/
}

​ 我们可以通过这个特性完成一些异步的操作,如通过事件A触发监听器A进行逻辑处理,监听器A完成逻辑处理后可以返回事件B,让监听器B进行后续操作,完成一个事件驱动的逻辑设计。

发布事件 - 喊一嗓子

​ 监听器和广播器已经全部准备好了,上下文中publishEvent逻辑我们在找广播器的阶段也已经看过了,那么现在剩下的…就是在一个事件被发布时,要如何找到对应的监听该事件的监听器呢?我们直接进入到ApplicationEvnetMulticastermulticastEvent方法,看看发布事件时的实际操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// SimpleApplicationEventMulticaster#multicastEvent
/*
* 标准事件类型按照默认逻辑流通 但若发布的事件非标准事件类型
* 如Integer 则会在上层被封装进PayloadApplicationEvent进行后续流程处理
*/
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
// 获取事件类型
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
// 获取任务执行器
Executor executor = getTaskExecutor();
// 通过getApplicationListeners获取匹配的监听器
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
/*
* 若任务执行器存在,则通过执行器异步执行事件处理逻辑
* 反之则以串行的方式进行事件逻辑处理
*/
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}

​ 在这里阿肖要插一段,在实际开发过程中主流程在完成一些业务逻辑后发布了事件,而隐藏在事件广播器身后的监听器对于主流程是不可知的,若此时事件广播器的执行流程是串行的,那么监听器的处理逻辑要是稍有纰漏抛出异常,就会导致主流程原本的逻辑无法继续进行,这当然不是我们所期望的,所以在进行事件驱动逻辑开发的时候,一定要做好异常的捕获处理,保证主流程的正常流转。

​ 当然,我们可也以用一些别的小手段,还记得我们在找事件广播器ApplicationEventMulticaster的时候提到过我们可以自定义广播器么,根据广播器初始化逻辑,会在IoC容器中查找bean名称为applicationEventMulticaster的bean,若bean不存在,则进行初始化,反之则会使用已存在的广播器。我们可以利用这部分逻辑定义我们自己的广播器并在配置类中注册为bean,SimpleApplicationEventMilticaster的类上有两个字段taskExecutorerrorHandler,在注册时我们可以配置上时间的异步任务执行器和通用异常处理器,类似这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class MulticasterConfiguration {
@Bean
public ApplicationEventMulticaster applicationEventMulticaster(BeanFactory beanFactory) {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster(beanFactory);
// 设置任务线程池(实际开发场景建议根据配置线程池)
multicaster.setTaskExecutor(Executors.newSingleThreadExecutor());
// 配置通用的任务异常处理逻辑
multicaster.setErrorHandler((ex) -> {
logger.warn("has something error occur", ex);
});
return multicaster;
}
}

​ 通过事件监听器逻辑的异步执行,我们能保证监听器逻辑不会影响主流程的继续进行。但是由于广播器所配置的任务执行器是固定的,若是线程池的配置不当,可能会导致线程池任务溢出。其次会导致有前后依赖的监听器逻辑执行有误(譬如事件A有两个监听器,监听器2号的执行逻辑需要监听器1号执行完成,异步执行无法保证完成后再执行2号监听器逻辑)。所以我们也可以通过对监听器方法标记@Async注解实现方法的异步执行,同时可以指明使用的线程池,避免所有事件监听器依赖同一个线程池的问题。当然两种方式各有利弊,还是根据实际的业务开发场景细细斟酌会更好一点。

​ 回到我们事件发布的逻辑上,广播器通过getApplicationListeners方法获取与之匹配的监听器列表,我们一起来看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// AbstractApplicationEventMulticaster#getApplicationListeners
protected Collection<ApplicationListener<?>> getApplicationListeners(
ApplicationEvent event, ResolvableType eventType) {
// 获取event的source字段 用于SmartApplicationListener的源匹配逻辑
Object source = event.getSource();
Class<?> sourceType = (source != null ? source.getClass() : null);
// 构建以事件类型 + 源类型的缓存
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

/*
* 由于ListenerCacheKey重写了equals和hashcode方法
* 所以可以直接以对象作为缓存的Key
* 若能通过key获取到ListenerRetriever对象
* 说明之前已经处理过相同的事件类型和事件源 直接从缓存拉取监听器列表就行
*/
ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}

// 加锁保证添加缓存的操作是串行执行的
synchronized (this.retrievalMutex) {
// 双重检查保证缓存还没有被添加
retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}
retriever = new ListenerRetriever(true);
Collection<ApplicationListener<?>> listeners =
// 从监听器列表中筛选出目标监听器
retrieveApplicationListeners(eventType, sourceType, retriever);
// 将目标监听器列表进行缓存
this.retrieverCache.put(cacheKey, retriever);
return listeners;
}
}

​ 在事件触发时,广播器会根据EventType + Source在监听器列表里查找符合条件的监听器并将其缓存起来。实现SmartApplicationListener接口或GenericApplicationListener接口的监听器可以通过supportsEventType方法和supportsSourceType方法支持多种事件类型,虽匹配类型可以根据逻辑动态变动,但若在第一次事件触发时没有成功匹配,则不会有第二次匹配机会,监听器也不会按期望被加入监听器列表中。

​ 在retrieveApplicationListeners方法中,会取出所有的ApplicationListener实例,并通过supportsEvent方法判断是是否是事件的目标监听器,在找齐了监听器之后,会根据Order配置对监听器进行排序,并循环调用各个监听器的onApplication方法触发事件处理逻辑。由于retrieveApplicationListeners方法整体逻辑较多,我们就单独把判断类型支持的supportsEvent方法择出来看看,想知道方法整体逻辑的同学也可以线性的看一看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// AbstractApplicationEventMulticaster#supportsEvent
protected boolean supportsEvent(
ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
// 于此会对普通的ApplicationListener包装为GenericApplicationListener便于之后的类型检测
GenericApplicationListener smartListener =
(listener instanceof GenericApplicationListener ?
(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

@Override
public boolean supportsEventType(ResolvableType eventType) {
// 若监听器是SmartApplicationListener 则交予其自身对类型进行支持判定
if (this.delegate instanceof SmartApplicationListener) {
Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.resolve();
return (eventClass != null && ((SmartApplicationListener) this.delegate).supportsEventType(eventClass));
}
else {
/*
* 确定触发的事件类型可以被监听器所支持
* 即 父类监听器可以监听子类事件
*/
return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
}
}

小结

​ 至此,我们找到了广播器,找到了监听器,然后把他们组合在了一起,好好的利用它们能极大的解除系统内逻辑的耦合,使系统可扩展性和可维护性大大提升。我们开发过程中引入的Mq中间件也是在分布式应用的情况下实现了这些模式,只不过中间件所要考虑的问题就会偏向于RPC、消息丢失、持久化等逻辑。Spring Listener组件的整体架构用到了很多设计模式,监听器模式(根本),装饰模式(各处的Adapter),代理模式(Cglib的类增强)等等…

​ 还是那句话,Spring是个大家伙,我也不知道什么时候能把它啃完,但是其中很多的组件开发思路也值得我们参考,譬如@EventListener标记方法的的扫描装配方法,我们也可以用这个思路在项目里实现一些自己的组件。

尾巴

​ 整篇内容从开始准备到写到这里,总共花了十天的时间,本来以为Spring Listener的内容会比较简单,没想到发散开写也有这么多,不管终归还是写完了。有点点累,但是也鼓励鼓励坚持到现在的自己,下一篇要写什么还没有想好,所有大家伙有什么想看的也可以告诉我,这样我就不用自己想选题了诶嘿嘿,歇一歇也要把20年的年终总结和21年的计划写了。总之…谢谢你能看到这里。