SpringFramework-异步发布订阅

96 阅读2分钟

发布订阅原理

spring在启动时会进行初始化监听器,并添加到广播器中进行管理。当在代码中通过ApplicationEventPublisher的publishEvent方法进行发布事件时。找到对应订阅的listener进行同步处理。这样就完整在spring内部实现了简单的发布订阅机制。

源码

自定义的listener bean

image.png

通过beanNameForType方法获取对应的监听器的bean,注册到广播器中。

发布event

也是通过AbstractApplicationContext中的publishEvent方法进行调用,传递event对象到listener中。通过广播器找到对应的listener集合。一个event是可以被多个listener监听,并使用。需要注意的一个点是,如果有两个及两个以上listener时,不要操作同一个资源在不同的listener中。会出现数据错乱的问题。

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

类型匹配

从容器中拿出对应的监听的列表,并判断eventType和listener方法上的类型是否匹配。得到对应的列表再进行调用。

image.png

通过上述源码可以得知当listener中的类型为空或者类型是event事件类型的父类都是可以的。

缺点

以上简单的发布订阅,缺点是在一个线程中完成的,没有达到真正的解耦,存在阻塞主线程的可能性。可以利用bean拦截切面的方法,使用@Async注解来达到解耦,不影响主流程

示例

event

image.png

listener

image.png

Async 使用注意点

使用Async注解,每次都会创建一个线程来执行,造成了极大的浪费。任务过多时会造成系统资源的浪费。需要使用自定义的线程池来进行处理。扩展AsyncConfigurer,实现自定义的线程池类。 代码如下:

@Configuration
open class AsyncConfig: AsyncConfigurer {

    /**
     * 实现异步线程池
     */
    override fun getAsyncExecutor(): Executor? {

        return Executors.newFixedThreadPool(10, CustomizableThreadFactory("event-async-"))

    }
}

执行结果如下:

image.png 可以看到总共就10个线程,没配置之前每次都是开一个线程进行任务执行。