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


  前言

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

    /**
     * 用戶注冊
     */
    @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使用事件發布與監聽就暫時記錄到這,后續再進行補充。

 

  更新

  2021-08-12更新

  有同學反饋說,都SpringBoot了,不需要繼承ApplicationEvent,直接發布Vo就行,今天一試果然如此!

 

  監聽

  直接監聽Vo

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

    /**
     * 用戶注冊事件監聽
     */
    @Async("asyncTaskExecutor")
    @EventListener
    @Order(1)//一個事件多個事監聽,同步的情況下,使用@order值越小,執行順序優先
    public void userRegisterListener(UserVo userVo){
        log.info("用戶注冊事件監聽1:"+userVo);

        //開展其他業務,例如發送郵件、短信等
    }
    /**
     * 用戶注冊事件監聽
     */
    @Async("asyncTaskExecutor")
    @EventListener
    @Order(2)//一個事件多個事監聽,同步的情況下,使用@order值越小,執行順序優先
    public void userRegisterListener2(UserVo userVo){
        log.info("用戶注冊事件監聽2:"+userVo);

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

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

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

 

  發布

  直接發布Vo

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

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

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

        //發布 用戶注冊事件
        applicationEventPublisher.publishEvent(userVo);

        return "操作成功!";
    }

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

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

        return "操作成功!";
    }
}

 

  效果

 

 

 

  代碼開源

 

  代碼已經開源、托管到我的GitHub、碼雲:

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

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM