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());
}
}