Spring项目中监听完成的"责任链"套路

1,074 阅读3分钟

背景介绍

搬砖人搬砖人 又是一天搬砖日。

在做数据搬迁的过程中,写着写着发现原表里面的部分数据有外键依赖关系。但是当前的迁移项目是基于多线程多任务的并发操作。

同时各个任务也是基于多态的设计考虑

基于当前的项目架构,项目运行启动的时候,所有的task则会同时进行运行(利用schedule)。并且是一个单体应用(不存在多个节点运行)。于是这边就利用了Spring中的listener进行改良,也没有完全按照责任链的模式进行改造整个项目(改造起来成本还是有点大的,个人觉得),而是基于观察者模式去处理。

责任链介绍

为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

上图可以简单的描述责任链的整个过程,一个个进行下去(可以理解为电路中的串联连接)

改造

因为数据库中有对应的数据依赖,所以我们需要将当前的项目由原来的并行改造为"串行"。

改造过程

  1. 将有部分task数据依赖的接口实现ApplicationListener接口以及定义同一个ApplicationEvent事件实体。 eg:
@Component
public class BRunner implements IRunner, ApplicationListener<NoticeEvent> {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;


    @Override
    public void run() {
        System.out.println("this is BRunner"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        try {
            Thread.sleep(10_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        NoticeEvent noticeEvent = new NoticeEvent(this,"C");
        applicationEventPublisher.publishEvent(noticeEvent);
    }

    @Override
    @Async
    public void onApplicationEvent(NoticeEvent noticeEvent) {
        if ("B".equalsIgnoreCase(noticeEvent.getMessage())){
            this.run();
        }
    }
}
public class NoticeEvent extends ApplicationEvent {

    private String message;

    public NoticeEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public NoticeEvent(Object source) {
        super(source);
    }

    public String getMessage() {
        return message;
    }
}
  1. 作为数据来源主体进行发布消息改造。 eg:
@Component
public class ARunner implements IRunner {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;


    @Override
    public void run() {
        System.out.println("this is ARunner"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        try {
            Thread.sleep(10_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        NoticeEvent noticeEvent = new NoticeEvent(this,"B");
        applicationEventPublisher.publishEvent(noticeEvent);
    }
}
  1. 因为项目启动的时候是利用schedule进行执行多个task任务的,我们需要将"消费者"部分从启动的时候剔除(这里剔除的方法有好几种了,我这里简单说下最简单的)。
    • 维护一个静态集合存储需要剔除的task名称,容器初始化的时候加载进去,在执行时候判断是否在此集合中。
    • 利用instanceof去判断当前task是否属于ApplicationListener,属于就剔除执行计划。

改良

通过上述的过程,我们是可以实现我们所需要的(但是运行起来不对劲呀,怎么真的是串行了,有存在可以并行的task)。发现所有的task真的变成责任链了,没有达到我想要的效果,于是进到源码里面发现问题并改正。

改良处

  1. 启动类增加@EnableAsync
  2. onApplicationEvent方法上增加@Async

原因

发现监听器收到消息执行的时候有判断是否异步执行

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

**因为这里之前没有采取异步的执行方式,造成第一版是串行方式进行执行。详情代码可以通过org.springframework.context.event.SimpleApplicationEventMulticaster查看。

结尾

以上是我觉得改动最少却能完成需求的最小代价改动。当然基于责任链是可以完成整个项目,同时也需要改动很多。起码暂时觉得利用监听完成代价最小。大佬们有其他好的意见可以分享分享😝。