SpringBoot系列——事件發佈與監聽

  前言

  日常開發中,我們經常會碰到這樣的業務場景:用戶註冊,註冊成功後需要發送郵箱、短訊提示用戶,通常我們都是這樣寫:

    /**
     * 用戶註冊
     */
    @GetMapping("/userRegister")
    public String userRegister(UserVo userVo) {
        //校驗參數

        //存庫

        //發送郵件

        //發送短訊
        
        //API返回結果
        return "操作成功!";
    }

  可以發現,用戶註冊與信息推送強耦合,用戶註冊其實到存庫成功,就已經算是完成了,後面的信息推送都是額外的操作,甚至信息推送失敗報錯,還會影響API接口的結果,如果在同一事務,報錯信息不捕獲,還會導致事務回滾,存庫失敗。

  官方文檔相關介紹://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-application-events-and-listeners

 

  本文記錄springboot使用@EventListener監聽事件、ApplicationEventPublisher.publishEvent發佈事件實現業務解耦。

 

  代碼

  項目結構

 

  默認情況下,事件的發佈和監聽操作是同步執行的,我們先配置一下async,優雅多線程異步任務,詳情請戳:SpringBoot系列——@Async優雅的異步調用

  啟動類添加@EnableAsync註解

/**
 * 異步任務線程池的配置
 */
@Configuration
public class AsyncConfig {

    private static final int MAX_POOL_SIZE = 50;

    private static final int CORE_POOL_SIZE = 20;

    @Bean("asyncTaskExecutor")
    public AsyncTaskExecutor asyncTaskExecutor() {
        ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor();
        asyncTaskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        asyncTaskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        asyncTaskExecutor.setThreadNamePrefix("async-task-");
        asyncTaskExecutor.initialize();
        return asyncTaskExecutor;
    }
}

  多數情況下的業務操作都會涉及數據庫事務,可以使用@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)註解開啟事務監聽,確保數據入庫後再進行異步任務操作。

 

   定義事件源

  先定義兩個事件源,繼承ApplicationEvent

/**
 * 用戶Vo
 */
@Data
public class UserVo {

    private Integer id;

    private String username;
}

/**
 * 用戶事件源
 */
@Getter
@Setter
public class UserEventSource extends ApplicationEvent {
    private UserVo userVo;

    UserEventSource(UserVo userVo) {
        super(userVo);
        this.userVo = userVo;
    }
}
/**
 * 業務工單Vo
 */
@Data
public class WorkOrderVo {

    private Integer id;

    private String WorkOrderName;
}


/**
 * 業務工單事件源
 */
@Getter
@Setter
public class WorkOrderEventSource extends ApplicationEvent {
    private cn.huanzi.qch.springbooteventsandlisteners.pojo.WorkOrderVo WorkOrderVo;

    WorkOrderEventSource(WorkOrderVo WorkOrderVo) {
        super(WorkOrderVo);
        this.WorkOrderVo = WorkOrderVo;
    }
}

 

  監聽事件

  監聽用戶註冊事件、監聽業務工單發起事件

/**
 * 事件監聽
 */
@Slf4j
@Component
public class EventListenerList {

    /**
     * 用戶註冊事件監聽
     */
    @Async("asyncTaskExecutor")
    @EventListener
    @Order(1)//一個事件多個事監聽,使用@order值越小,執行順序優先
    public void userRegisterListener(UserEventSource eventSourceEvent){
        log.info("用戶註冊事件監聽1:"+eventSourceEvent.getUserVo());

        //開展其他業務,例如發送郵件、短訊等
    }
    /**
     * 用戶註冊事件監聽
     */
    @Async("asyncTaskExecutor")
    @EventListener
    @Order(2)//一個事件多個事監聽,使用@order值越小,執行順序優先
    public void userRegisterListener2(UserEventSource eventSourceEvent){
        log.info("用戶註冊事件監聽2:"+eventSourceEvent.getUserVo());

        //開展其他業務,例如發送郵件、短訊等
    }

    /**
     * 業務工單發起事件監聽
     */
    @Async("asyncTaskExecutor")
    @EventListener
    public void workOrderStartListener(WorkOrderEventSource eventSourceEvent){
        log.info("業務工單發起事件:"+eventSourceEvent.getWorkOrderVo());

        //開展其他業務,例如發送郵件、短訊等
    }
}

 

  發佈事件

  創建一個controller,新增兩個測試接口

/**
 * 事件發佈
 */
@Slf4j
@RestController
@RequestMapping("/eventPublish/")
public class EventPublish {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    /**
     * 用戶註冊
     */
    @GetMapping("userRegister")
    public String userRegister(UserVo userVo) {
        log.info("用戶註冊!");

        //發佈 用戶註冊事件
        applicationEventPublisher.publishEvent(new UserEventSource(userVo));

        return "操作成功!";
    }

    /**
     * 業務工單發起
     */
    @GetMapping("workOrderStart")
    public String workOrderStart(WorkOrderVo workOrderVo) {
        log.info("業務工單發起!");

        //發佈 業務工單發起事件
        applicationEventPublisher.publishEvent(new WorkOrderEventSource(workOrderVo));

        return "操作成功!";
    }
}

 

  效果

  用戶註冊

  //localhost:10010/eventPublish/userRegister?id=1&username=張三

  API返回

 

  後台異步任務執行

 

 

   工單發起

  //localhost:10010/eventPublish/workOrderStart?id=1&workOrderName=設備出入申請單

  API返回

 

   後台異步任務執行

 

 

  後記

  springboot使用事件發佈與監聽就暫時記錄到這,後續再進行補充。

 

  代碼開源

 

  代碼已經開源、託管到我的GitHub、碼云:

  GitHub://github.com/huanzi-qch/springBoot

  碼云://gitee.com/huanzi-qch/springBoot