SpringBoot系列——事件釋出與監聽

qch發表於2021-05-21

  前言

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

    /**
     * 使用者註冊
     */
    @GetMapping("/userRegister")
    public String userRegister(UserVo userVo) {
        //校驗引數

        //存庫

        //傳送郵件

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

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

  官方文件相關介紹:https://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 "操作成功!";
    }
}

 

  效果

  使用者註冊

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

  API返回

 

  後臺非同步任務執行

 

 

   工單發起

  http://localhost:10010/eventPublish/workOrderStart?id=1&workOrderName=裝置出入申請單

  API返回

 

   後臺非同步任務執行

 

 

  後記

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

 

  程式碼開源

 

  程式碼已經開源、託管到我的GitHub、碼雲:

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

  碼雲:https://gitee.com/huanzi-qch/springBoot

 

相關文章