Spring@Async異步和事件驅動模型

本文主要是介紹一下Spring中如何進行異步編程以及事件驅動編程;

Spring事件驅動

Spirng事件驅動有3個部分組成:

  • ApplicationEvent:表示事件本身,自定義事件需要繼承該類,用來定義事件
  • ApplicationEventPublisher:事件發送器,主要用來發布事件
  • ApplicationListener:事件監聽器接口,監聽類實現ApplicationListener 裏onApplicationEvent方法即可,也可以在方法上增加@EventListener以實現事件監聽

實現Spring事件驅動一般只需要三步:

  • 自定義需要發佈的事件類,需要繼承ApplicationEvent類
  • 使用ApplicationEventPublisher來發布自定義事件
  • 使用@EventListener來監聽事件

這裏需要特別注意的一點是,在實際項目中,默認情況時間都是同步執行的,即事件被publish後會等待Listener的處理。如果發佈事件處的業務存在事務,監聽器處理也會在相同的事務中。如果需要異步處理事件,可以onApplicationEvent方法上加@Aync支持異步或在有@EventListener的註解方法上加上@Aync。

創建事件

public class GenerateOrUpdateStudentScheduleEvent extends ApplicationEvent {

    public final static int ACTION_SAVE = 1;                                             //1-新增課表記錄
    public final static int ACTION_UPDATE_BATCH_RELATED_SECTION = 2;                     //2-更新課表:批次關聯課節(可批量)
    public final static int ACTION_UPDATE_SECTION_RELATED_BATCH = 3;                     //3-更新課表:課節關聯批次(可批量)
    public final static int ACTION_UPDATE_BATCH_SECTION_RELATION_CANCEL = 4;             //4-更新課表:批次課節關聯移除
    public final static int ACTION_UPDATE_CLASS_TYPE_RELATED_SECTION = 5;                //5-更新課表:普通班型關聯課節(可批量)
    public final static int ACTION_UPDATE_SECTION_RELATED_CLASS_TYPE = 6;                //6-更新課表:課節關聯普通班型(可批量)
    public final static int ACTION_UPDATE_CLASS_TYPE_SECTION_RELATION_CANCEL = 7;        //7-更新課表:普通班型課節關聯移除(可批量)
    public final static int ACTION_UPDATE_CHANGE_BATCH = 8;                              //8-更新課表:學員購買的班型轉批次

    //默認是新增
    private Integer action = ACTION_SAVE;

    @ApiModelProperty(value = "訂單課程id")
    private String orderCourseId;

    @ApiModelProperty(value = "批次id")
    private String batchId;

    @ApiModelProperty(value = "班型id")
    private String classTypeId;

    @ApiModelProperty(value = "課節id")
    private String classSectionId;

    @ApiModelProperty(value = "加課規則id")
    private String extraClassRuleId;

    @ApiModelProperty(value = "批次id列表")
    private List<String> batchIdList;

    @ApiModelProperty(value = "課節id列表")
    private List<String> classSectionIdList;

    @ApiModelProperty(value = "加課規則id列表")
    private List<String> extraClassRuleIdList;

    @ApiModelProperty(value = "班型id列表")
    private List<String> classTypeIdList;

    @ApiModelProperty(value = "新轉班的訂單課程id")
    private String newChangeOrderCourseId;

    @ApiModelProperty(value = "關聯移除操作數組對象(批次課節關聯、規則課節關聯)")
    private JSONArray jsonArray;


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

    public static int getActionSave() {
        return ACTION_SAVE;
    }

    public static int getActionUpdateBatchRelatedSection() {
        return ACTION_UPDATE_BATCH_RELATED_SECTION;
    }

    public static int getActionUpdateSectionRelatedBatch() {
        return ACTION_UPDATE_SECTION_RELATED_BATCH;
    }

    public static int getActionUpdateBatchSectionRelationCancel() {
        return ACTION_UPDATE_BATCH_SECTION_RELATION_CANCEL;
    }

    public static int getActionUpdateClassTypeRelatedSection() {
        return ACTION_UPDATE_CLASS_TYPE_RELATED_SECTION;
    }

    public static int getActionUpdateSectionRelatedClassType() {
        return ACTION_UPDATE_SECTION_RELATED_CLASS_TYPE;
    }

    public static int getActionUpdateClassTypeSectionRelationCancel() {
        return ACTION_UPDATE_CLASS_TYPE_SECTION_RELATION_CANCEL;
    }

    public static int getActionUpdateChangeBatch() {
        return ACTION_UPDATE_CHANGE_BATCH;
    }

    public Integer getAction() {
        return action;
    }

    public void setAction(Integer action) {
        this.action = action;
    }

    public String getOrderCourseId() {
        return orderCourseId;
    }

    public void setOrderCourseId(String orderCourseId) {
        this.orderCourseId = orderCourseId;
    }

    public String getBatchId() {
        return batchId;
    }

    public void setBatchId(String batchId) {
        this.batchId = batchId;
    }

    public String getClassTypeId() {
        return classTypeId;
    }

    public void setClassTypeId(String classTypeId) {
        this.classTypeId = classTypeId;
    }

    public String getClassSectionId() {
        return classSectionId;
    }

    public void setClassSectionId(String classSectionId) {
        this.classSectionId = classSectionId;
    }

    public String getExtraClassRuleId() {
        return extraClassRuleId;
    }

    public void setExtraClassRuleId(String extraClassRuleId) {
        this.extraClassRuleId = extraClassRuleId;
    }

    public List<String> getBatchIdList() {
        return batchIdList;
    }

    public void setBatchIdList(List<String> batchIdList) {
        this.batchIdList = batchIdList;
    }

    public List<String> getClassSectionIdList() {
        return classSectionIdList;
    }

    public void setClassSectionIdList(List<String> classSectionIdList) {
        this.classSectionIdList = classSectionIdList;
    }

    public List<String> getExtraClassRuleIdList() {
        return extraClassRuleIdList;
    }

    public void setExtraClassRuleIdList(List<String> extraClassRuleIdList) {
        this.extraClassRuleIdList = extraClassRuleIdList;
    }

    public List<String> getClassTypeIdList() {
        return classTypeIdList;
    }

    public void setClassTypeIdList(List<String> classTypeIdList) {
        this.classTypeIdList = classTypeIdList;
    }

    public String getNewChangeOrderCourseId() {
        return newChangeOrderCourseId;
    }

    public void setNewChangeOrderCourseId(String newChangeOrderCourseId) {
        this.newChangeOrderCourseId = newChangeOrderCourseId;
    }

    public JSONArray getJsonArray() {
        return jsonArray;
    }

    public void setJsonArray(JSONArray jsonArray) {
        this.jsonArray = jsonArray;
    }

}

發佈事件

 @Override
 @Transactional(rollbackFor = Exception.class)
 public Result<?> relatedClassSections(SdSchoolRelatedSectionsDTO sdSchoolRelatedSectionsDTO, HttpServletRequest request) {
        Result<Object> result = new Result<>();
        try {
            //....
            //更新學員排課記錄事件觸發
            SdSchoolScheduleUpdateParamDTO scheduleUpdateParamDTO = new SdSchoolScheduleUpdateParamDTO();
            scheduleUpdateParamDTO.setBatchId(batchId);
            scheduleUpdateParamDTO.setClassSectionIdList(classSectionIdList);
            sdSchoolCourseStudentScheduleService.onUpdateStudentScheduleEvent(scheduleUpdateParamDTO, GenerateOrUpdateStudentScheduleEvent.ACTION_UPDATE_BATCH_RELATED_SECTION);

        } catch (Exception e) {
            log.error("批次關聯課節失敗", e);
            result.error500("批次關聯課節失敗");
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }
}

 @Override
 public void onUpdateStudentScheduleEvent(SdSchoolScheduleUpdateParamDTO scheduleUpdateParamDTO, int action) {
        GenerateOrUpdateStudentScheduleEvent event = new GenerateOrUpdateStudentScheduleEvent(System.currentTimeMillis());
        event.setAction(action);
        //發佈事件
        applicationContext.publishEvent(event);

    }

事件監聽處理

/**
 * GenerateOrUpdateClassGradeScheduleEvent 時間處理類,實現異步生成更新班級課表
 *
 * @author: jacklin
 * @since: 2021/11/16 9:20
 **/
@Slf4j
@Component
public class GenerateOrUpdateClassGradeScheduleHandler implements ApplicationListener<GenerateOrUpdateClassGradeScheduleEvent> {

    @Autowired
    private ISdSchoolClassGradeScheduleService sdSchoolClassGradeScheduleService;

    @Async("generateGradeScheduleTaskExecutor")
    @Override
    public void onApplicationEvent(GenerateOrUpdateClassGradeScheduleEvent event) {
        if (event == null) {
            return;
        }

        log.info("觸發生成、更新班級課表事件 ~");

        Integer action = event.getAction();

        switch (action) {
            case GenerateOrUpdateClassGradeScheduleEvent.ACTION_SAVE_BATCH_RELATED_CLASS_TYPE:
                /**
                 * 1.批次關聯繫統班型關聯(考慮批量)、生成班級課表
                 **/
                sdSchoolClassGradeScheduleService.batchRelatedSystemClassTypeGenerateGradeSchedule(batchId, classTypeIdList);
                break;
            case GenerateOrUpdateClassGradeScheduleEvent.ACTION_SAVE_CLASS_TYPE_RELATED_BATCH:
                /**
                 * 2.系統班型關聯批次(考慮批量)、生成班級課表
                 **/
                sdSchoolClassGradeScheduleService.systemClassTypeRelatedBatchGenerateGradeSchedule(classTypeId, batchIdList);
                break;
        }
    }
}

 @Async("generateGradeScheduleTaskExecutor") 實現異步

使用了Async後會使用默認的線程池SimpleAsyncTaskExecutor,一般我們會在項目中自定義一個線程池:

/**
 * 線程池參數配置,多個線程池實現線程池隔離,@Async註解,默認使用系統自定義線程池,可在項目中設置多個線程池,在異步調用的時候,指明需要調用的線程池名稱,比如:@Async("taskName")
 *
 * @author: jacklin
 * @since: 2021/9/18 11:44
 **/
@EnableAsync
@Configuration
public class TaskPoolConfig {


    /**
     * 異步生成更新學員課表,不同異步任務配置不同的線程池,實現線程池隔離
     *
     * @author: jacklin
     * @since: 2021/11/16 17:39
     **/
    @Bean("generateStudentScheduleTaskExecutor")
    public Executor generateStudentScheduleTaskExecutor() {
        ThreadPoolTaskExecutor generateUpdateStudentScheduleTaskExecutor = new ThreadPoolTaskExecutor();
        //核心線程池大小
        generateUpdateStudentScheduleTaskExecutor.setCorePoolSize(16);
        //最大線程數
        generateUpdateStudentScheduleTaskExecutor.setMaxPoolSize(20);
        //配置隊列容量,默認值爲Integer.MAX_VALUE
        generateUpdateStudentScheduleTaskExecutor.setQueueCapacity(99999);
        //活躍時間
        generateUpdateStudentScheduleTaskExecutor.setKeepAliveSeconds(60);
        //線程名字前綴
        generateUpdateStudentScheduleTaskExecutor.setThreadNamePrefix("asyncGenerateStudentScheduleServiceExecutor -");
        //設置此執行程序應該在關閉時阻止的最大秒數,以便在容器的其餘部分繼續關閉之前等待剩餘的任務完成他們的執行
        generateUpdateStudentScheduleTaskExecutor.setAwaitTerminationSeconds(60);
        //等待所有的任務結束後再關閉線程池
        generateUpdateStudentScheduleTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        return generateUpdateStudentScheduleTaskExecutor;
    }
}
配合@Async註解。
@EnableAsync 開啓異步支持,可以自定義Executor。(如果不配置的話、springboot會提供默認的線程池) @Async 可以指定Executor,此方法被包裹成任務放入線程池中異步執行。 通過繼承AsyncConfigurerSupport,重寫getAsyncExecutor方法,自定義@Async默認線程池;
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章