Spring ApplicationEvent 事件驱动实现业务解耦

837 阅读2分钟

场景

在某些业务场景中,往往需要异步事件来进行业务解耦、提高响应速度,如注册用户后发短信、同步信息等。这些场景可以使用MQ来解决,但有没有轻量一点的解决方案呢,有,Spring自带的事件监听配合异步方法就可以实现

Demo

  1. 开启异步事件@EnableAsync

  2. 新建事件,接收的对象可自定义,此处使用String演示

public class SysLogEvent extends ApplicationEvent {
    /**
     * @param source
     */
    public SysLogEvent(String source) {
        super(source);
    }
}
  1. 监听器
@Slf4j
@Component
public class SysLogListener {

    @Async
    @EventListener(SysLogEvent.class)
    public void saveSysLog(SysLogEvent event) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("【接收事件】{} {}", event.getSource(), event.getTimestamp());
    }
}
  1. 发送监听事件
@RestController
@Slf4j
public class DemoController {
    @Resource
    private ApplicationContext ctx;

    @GetMapping("/test")
    public void test() {
        log.info("【发送事件】");
        ctx.publishEvent(new SysLogEvent("哈哈"));
    }
}

完毕

异步线程池配置

@Async本质上是使用线程池进行处理任务,在实际开发中需要根据业务场景去配置线程池大小


/**
 * @author HeyS1
 * @description
 */
@Slf4j
@Configuration
@EnableAsync
public class SchedulingConfig implements AsyncConfigurer {
    
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程
        executor.setCorePoolSize(2);
        //最大线程
        executor.setMaxPoolSize(20);
        //队列大小
        executor.setQueueCapacity(5);
        //表示线程池大小大于coreSize的时候,多余线程最多等待的时间,如果超过时间都没有处理会自行销毁
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("taskExecutor-");

        //当pool已经达到max size的时候,如何处理新任务
        //@see <a href="https://blog.csdn.net/foreverling/article/details/78073105"></a>
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        executor.initialize();
        return executor;
    }


    /**
     * 异步任务中异常处理
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            log.error("==========================" + ex.getMessage() + "=======================", ex);
            log.error("exception method:" + method.getName());
        };
    }
}

事件事务

使用@TransactionEventListener 代替 @EventListener

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {

  //表示当前事件跟随消息发送方事务的出发时机,默认为消息发送方事务提交之后才进行处理。
   TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;

   //true时不论发送方是否存在事务均出发当前事件处理逻辑
   boolean fallbackExecution() default false;

   //监听的事件具体类型,还是建议指定一下,避免监听到子类的一些情况出现
   @AliasFor(annotation = EventListener.class, attribute = "classes")
   Class<?>[] value() default {};

   //指向@EventListener对应的值
   @AliasFor(annotation = EventListener.class, attribute = "classes")
   Class<?>[] classes() default {};

   //指向@EventListener对应的值
   String condition() default "";

}
public enum TransactionPhase {
   // 指定目标方法在事务commit之前执行
   BEFORE_COMMIT,

   // 指定目标方法在事务commit之后执行
    AFTER_COMMIT,

    // 指定目标方法在事务rollback之后执行
    AFTER_ROLLBACK,

   // 指定目标方法在事务完成时执行,这里的完成是指无论事务是成功提交还是事务回滚了
   AFTER_COMPLETION
  }

补充

其实事件不继承ApplicationEvent 也是可以的

另外发送也可以使用

@Autowired
ApplicationEventPublisher publisher;

publisher.publishEvent(...);