quartz整合springboot實現動態配置任務的CRUD詳情操作

quartz整合springboot實現動態配置任務的CRUD詳情操作

quartz整合springboot完成動態的配置定時任務的CRUD

	Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,它可以與J2EE與J2SE應用程序相結合也可以單獨使用。Quartz可以用來創建簡單或爲運行十個,百個,甚至是好幾萬個Jobs這樣複雜的程序。Jobs可以做成標準的Java組件或 EJBs。 

一、quartz中相關介紹

Quartz API

Quartz API的關鍵接口是:

  • Scheduler - 與調度程序交互的主要API。
  • Job - 由希望由調度程序執行的組件實現的接口。
  • JobDetail - 用於定義作業的實例。
  • Trigger(即觸發器) - 定義執行給定作業的計劃的組件。
  • JobBuilder - 用於定義/構建JobDetail實例,用於定義作業的實例。
  • TriggerBuilder - 用於定義/構建觸發器實例。

調度器 scheduler:

Scheduler:調度器。所有的調度都是由它控制
       Scheduler就是Quartz的大腦,所有任務都是由它來設施
       Schdue1r包含一個兩個重要組件:JobStore和ThreadPool
           JobStore是會來存儲運行時信息的,包括Trigger,Schduler,JobDetai1,業務鎖等
           ThreadPoo1就是線程池,Quartz有自己的線程池實現。所有任務的都會由線程池執行
       SchdulerFactory,顧名思義就是來用創建Schduler了,
           有兩個實現:DirectSchedulerFactory和StdSchdulerFactory。
           前者可以用來在代碼裏定製你自己的Schduler參數。
           後者是直接讀取classpath下的quartz.properties(不存在就都使用默認值)配置來實例化Schduler.通常來講,我們使用StdSchdulerFactory也就足夠了。
      SchdulerFactory本身是支持創建RMI stub的,可以用來管理遠程的Scheduler,功能與本地一樣

觸發器 trigger:

Trigger用於觸發Job的執行。當你準備調度一個job時,你創建一個Trigger的實例,然後設置調度相關的屬性。Trigger也有一個相關聯的JobDataMap,用於給Job傳遞一些觸發相關的參數。Quartz自帶了各種不同類型的Trigger,最常用的主要是SimpleTrigger和CronTrigger。
SimpleTrigger主要用於一次性執行的Job(只在某個特定的時間點執行一次),或者Job在特定的時間點執行,重複執行N次,每次執行間隔T個時間單位。CronTrigger在基於日曆的調度上非常有用,如“每個星期五的正午”,或者“每月的第十天的上午10:15”等。
爲什麼既有Job,又有Trigger呢?很多任務調度器並不區分Job和Trigger。有些調度器只是簡單地通過一個執行時間和一些job標識符來定義一個Job;其它的一些調度器將Quartz的Job和Trigger對象合二爲一。在開發Quartz的時候,我們認爲將調度和要調度的任務分離是合理的。在我們看來,這可以帶來很多好處。
例如,Job被創建後,可以保存在Scheduler中,與Trigger是獨立的,同一個Job可以有多個Trigger;這種鬆耦合的另一個好處是,當與Scheduler中的Job關聯的trigger都過期時,可以配置Job稍後被重新調度,而不用重新定義Job;還有,可以修改或者替換Trigger,而不用重新定義與之關聯的Job。

任務job:

當Job的一個trigger被觸發時,execute()方法由調度程序的一個工作線程調用。傳遞給execute()方法的JobExecutionContext對象向作業實例提供有關其“運行時”環job的一個trigger被觸發後,execute()方法會被scheduler的一個工作線程調用;傳遞給execute()方法的JobExecutionContext對象中保存着該job運行時的一些信息 ,執行job的scheduler的引用,觸發job的trigger的引用,JobDetail對象引用,以及一些其它信息。
JobDetail對象是在將job加入scheduler時,由客戶端程序(你的程序)創建的。它包含job的各種屬性設置,以及用於存儲job實例狀態信息的JobDataMap。

二、quartz整合springboot實現動態配置任務的CRUD

1、導入jar包

 <!-- Quartz定時任務 -->
     <dependency>
     	<groupId>org.springframework.boot</groupId>
     	<artifactId>spring-boot-starter-quartz</artifactId>
     </dependency>

2.相關代碼( 這裏用到了 SpringDataJpa 操作數據庫 )

2.1定時任務實體類:

import lombok.Data;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.util.Date;

/**
 * @author zb
 * @date 2020/2/21 20:15
 * @Description: 定時任務實體類
 */
@Data
@EntityListeners(AuditingEntityListener.class)
@Entity
public class QuartzJob {
    
    /**id*/
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**創建人*/
    @CreatedBy
    private String createBy;

    /**創建時間*/
    @CreatedDate
    private Date createTime;

    /**修改人*/
    @LastModifiedBy
    private String updateBy;

    /**修改時間*/
    @LastModifiedDate
    private Date updateTime;

    /**刪除狀態 0 表示未刪除,1 表示已刪除*/
    private Integer delFlag = 0;

    /**任務類名*/
    private String jobClassName;

    /**cron表達式*/
    private String cronExpression;

    /**傳入參數*/
    private String parameter;

    /**描述*/
    private String description;

    /**狀態 0正常 -1停止*/
    private Integer status = 0;

}

2.2 service實現類(dao 層 Repository 類這裏省略)

import cn.hutool.core.util.StrUtil;
import cn.mesmile.querydslx.module.common.PaginationResponse;
import cn.mesmile.querydslx.module.quartz.common.CommonConstant;
import cn.mesmile.querydslx.module.quartz.dao.QuartzJobRepository;
import cn.mesmile.querydslx.module.quartz.domain.QuartzJob;
import cn.mesmile.querydslx.module.quartz.dto.QuartzJobQueryReq;
import cn.mesmile.querydslx.module.quartz.exception.QuartzTaskException;
import cn.mesmile.querydslx.module.quartz.service.QuartzJobService;
import com.github.wenhao.jpa.Specifications;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

/**
 * @author zb
 * @date 2020/2/19 20:31
 * @Description: Copyright (C),2020,AOSSCI Inc.傲勢科技有限公司
 */
@Slf4j
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true,rollbackFor = Exception.class)
@Service
public class QuartzJobServiceImpl implements QuartzJobService {

    @Autowired
    private QuartzJobRepository quartzJobRepository;

    @Autowired
    private Scheduler scheduler;


    /**
     * 保存&啓動定時任務
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveAndScheduleJob(QuartzJob quartzJob) throws QuartzTaskException {
        if (CommonConstant.STATUS_NORMAL.equals(quartzJob.getStatus())) {
            // 定時器添加
            this.schedulerAdd(quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
        }
        // DB設置修改 未刪除
        quartzJob.setDelFlag(CommonConstant.DEL_FLAG_0);
        // 保存定時任務
        quartzJobRepository.save(quartzJob);
        return true;
    }


    /**
     * 恢復定時任務
     */
    @Override
    public boolean resumeJob(QuartzJob quartzJob) throws QuartzTaskException {
        // 先刪除
        schedulerDelete(quartzJob.getJobClassName().trim());
        // 添加定時任務
        schedulerAdd(quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());

        Optional<QuartzJob> optionalQuartzJob = quartzJobRepository.findById(quartzJob.getId());

        if( !optionalQuartzJob.isPresent()) {
            return false;
        }
        QuartzJob quartzJob2 = optionalQuartzJob.get();
        // 將狀態設置爲正常
        quartzJob2.setStatus(CommonConstant.STATUS_NORMAL);
        quartzJobRepository.save(quartzJob2);

        return true;
    }

    @Override
    public QuartzJob getById(Long id) {
        Optional<QuartzJob> optional = quartzJobRepository.findById(id);
        return optional.orElse(null);
    }

    /**
     * 編輯定時任務
     * @throws SchedulerException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean editAndScheduleJob(QuartzJob quartzJob) throws SchedulerException, QuartzTaskException {
        if (CommonConstant.STATUS_NORMAL.equals(quartzJob.getStatus())) {
            // 若狀態爲【非正常】則先刪除原來的
            schedulerDelete(quartzJob.getJobClassName().trim());
            // 再添加新
            schedulerAdd(quartzJob.getJobClassName().trim(), quartzJob.getCronExpression().trim(), quartzJob.getParameter());
        }else{
 // 恢復任務
            scheduler.pauseJob(JobKey.jobKey(quartzJob.getJobClassName().trim()));
        }
        Optional<QuartzJob> optionalQuartzJob = quartzJobRepository.findById(quartzJob.getId());
        if (optionalQuartzJob.isPresent()) {
            QuartzJob quartzJob2 = optionalQuartzJob.get();
            // 修改任務
            BeanUtils.copyProperties(quartzJob,quartzJob2);
            quartzJobRepository.save(quartzJob2);
        }
        return true;
    }

    /**
     * 刪除並停止刪除定時任務
     */
    @Override
    public boolean deleteAndStopJob(QuartzJob job) throws QuartzTaskException {
        // 刪除任務
        schedulerDelete(job.getJobClassName().trim());

        Optional<QuartzJob> optionalQuartzJob = quartzJobRepository.findById(job.getId());
        if (optionalQuartzJob.isPresent()) {
            QuartzJob quartzJob2 = optionalQuartzJob.get();
            // 修改爲刪除狀態
            quartzJob2.setDelFlag(1);
           
            quartzJobRepository.save(quartzJob2);
        }
        return true;
    }

    /**
     * 添加定時任務
     *
     * @param jobClassName    例:cn.mesmile.quartz.job.SampleJob    SampleJob類需要實現 quartz 中的 Job 接口,然後覆寫 execute 方法,在此方法中實現我們需要執行的任務 具體看第 2.3 步。
     * @param cronExpression  例:cron 表達式 * * * * * ? *
     * @param parameter       例:執行此方法需要傳入的參數
     */
    private void schedulerAdd(String jobClassName, String cronExpression, String parameter) throws QuartzTaskException {
        try {
            // ① 啓動調度器
            scheduler.start();

            // ② 構建job信息
            JobDetail jobDetail = JobBuilder.newJob(
                    getClass(jobClassName).getClass()
            )
                    .withIdentity(jobClassName)
                    // .storeDurably(true) 持久儲存
                    .usingJobData("parameter", parameter)
                    .build();

            // 表達式調度構建器(即任務執行的時間)
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
                    .cronSchedule(cronExpression);

            // ③ 按新的cronExpression表達式構建一個新的trigger
            CronTrigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity(jobClassName)
                    .withSchedule(scheduleBuilder)
                    .build();

            // ④ 調度器中加入 任務 和 觸發器
            scheduler.scheduleJob(jobDetail, trigger);


            /*
                三種監聽器:
                    ①   JobListener            任務監聽器
                    ②   TriggerListener        觸發監聽器
                    ③   SchedulerListener      調度監聽器
             */

            /*
                 JobListener

                1)getName方法:用於獲取該JobListener的名稱。
                2)jobToBeExecuted方法:Scheduler在JobDetail將要被執行時調用這個方法。
                3)jobExecutionVetoed方悉:Scheduler在JobDetail即將被執行,但又被TriggerListener否決時會調用該方法
                4)jobWasExecuted方法:Scheduler在JobDetail被執行之後調用這個方法
             */
            // 註冊一個全局的監聽器  MyJobListen 實現 JobListen
            // scheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());

            // 註冊一個局部
            // scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(JobKey.jobKey("","")));

        } catch (SchedulerException e) {
            throw new QuartzTaskException("創建定時任務失敗", e);
        } catch (RuntimeException e) {
            throw new QuartzTaskException(e.getMessage(), e);
        }catch (Exception e) {
            throw new QuartzTaskException("後臺找不到該類名:" + jobClassName, e);
        }
    }

    /**
     * 刪除定時任務
     *
     * @param jobClassName  例如:cn.mesmile.quartz.job.
     */
    private void schedulerDelete(String jobClassName) throws QuartzTaskException {
        try {
            // 暫停觸發器
            scheduler.pauseTrigger(TriggerKey.triggerKey(jobClassName));
            // 解除任務調度器
            scheduler.unscheduleJob(TriggerKey.triggerKey(jobClassName));
            // 刪除任務
            scheduler.deleteJob(JobKey.jobKey(jobClassName));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new QuartzTaskException("刪除定時任務失敗");
        }
    }

    // 通過包名 反射獲得 對象
    private static Job getClass(String classname) throws Exception {
        Class<?> class1 = Class.forName(classname);
        return (Job) class1.newInstance();
    }
}    

2.3設置需要執行任務的類

​ (1) 無參數傳入任務

import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
 * @author zb
 * @date 2020/2/21 20:33
 * @Description: 無參數傳入任務, 需要實現quartz中的 Job 接口
 */
@Slf4j
public class SampleJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

        log.info("------------- 這裏執行了普通無參定時任務 !  時間: {}" , DateUtil.now() );
        
    }
}

(2)有參數傳入任務

import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
 * @author zb
 * @date 2020/2/21 20:38
 * @Description: 有參數定時任務, 需要實現quartz中的 Job 接口
 */
@Slf4j
public class SampleParamJob implements Job {

    /**
     * 傳入參數名 和 service實現類中 添加任務時設定的參數 parameter 名稱要一致
     */
    private String parameter;

    public void setParameter(String parameter) {
        this.parameter = parameter;
    }

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {

        // 創建工作詳情
        JobDetail jobDetail = jobExecutionContext.getJobDetail();
        // 任務名
        String name = jobDetail.getKey().getName();
        // 任務分組
        String group = jobDetail.getKey().getGroup();
        // 任務中的數據
        String parameter = jobDetail.getJobDataMap().getString("parameter");

        log.info("job執行 SampleParamJob 有參數傳入,任務名:{} , 分組group: {},  數據data: {} , 時間: {} ", name, group , parameter, DateUtil.now());

    }
}





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章