springboot quartz构建定时任务- 开发环境
- Quartz的3个基本要素
- 如何使用
- 引入相关依赖
- resource目录下创建quartz.properties
- quartz需要用到的表
- 实体和其他相关类
- 引入相关依赖
- resource目录下创建quartz.properties
- quartz需要用到的表
- 实体和其他相关类
开发环境
- jdk:jdk1.8.0_212
- maven:apache-maven-3.6.2
- springboot版本:2.2.0
Quartz的3个基本要素
- Scheduler:调度器。所有的调度都是由它控制。
- Trigger: 触发器。决定什么时候来执行任务。
- JobDetail & Job: JobDetail定义的是任务数据,而真正的执行逻辑是在Job中。使用JobDetail Job而不是Job,这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。
如何使用
引入相关依赖
代码语言:javascript复制 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--quartz-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.13.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.8</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.6</version>
</dependency>
resource目录下创建quartz.properties
代码语言:javascript复制org.quartz.scheduler.instanceName = TestScheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.wrapJobExecutionInUserTransaction = false
org.quartz.jobStore.tablePrefix = QRTZ_
#org.quartz.jobStore.dataSource = 这项可以不填,我填了启动时候会报错org.quartz.JobPersistenceException: Failed to obtain DB connection from data source 'xxx': java.sql.SQLException: There is no DataSource named 'xxx'
org.quartz.threadPool.threadCount = 5
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread= true
org.quartz.jobStore.misfireThreshold = 5000
org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass= org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 在调度流程的第一步,也就是拉取待即将触发的triggers时,是上锁的状态,即不会同时存在多个线程拉取到相同的trigger的情况,也就避免的重复调度的危险。
quartz需要用到的表
可以去quartz官网下载对应版本的包 解压后再对应的dbTables目录下有各种数据库的建表语句
实体和其他相关类
实体:
代码语言:javascript复制/**
* @author zjq
* @date
* <p>description:实体</p>
*/
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class JobAndTrigger {
/**
* 定时任务名称
*/
private String jobName;
/**
* 定时任务组
*/
private String jobGroup;
/**
* 定时任务全类名
*/
private String jobClassName;
/**
* 触发器名称
*/
private String triggerName;
/**
* 触发器组
*/
private String triggerGroup;
/**
* 重复间隔
*/
private BigInteger repeatInterval;
/**
* 触发次数
*/
private BigInteger timesTriggered;
/**
* cron 表达式
*/
private String cronExpression;
/**
* 时区
*/
private String timeZoneId;
/**
* 定时任务状态
*/
private String triggerState;
}
job工厂:
代码语言:javascript复制@Component
public class JobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
//进行注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
调度器配置: 如果不配置这个启动时候会报错,无法注入org.quartz.Scheduler
代码语言:javascript复制@Configuration
public class SchedulerConfig implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private JobFactory jobFactory;
@Autowired
@Qualifier("dataSource")
private DataSource primaryDataSource;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("任务已经启动..." event.getSource());
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
//获取配置属性
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
//在quartz.properties中的属性被读取并注入后再初始化对象
propertiesFactoryBean.afterPropertiesSet();
//创建SchedulerFactoryBean
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setQuartzProperties(propertiesFactoryBean.getObject());
//使用数据源,自定义数据源
factory.setDataSource(this.primaryDataSource);
factory.setJobFactory(jobFactory);
factory.setWaitForJobsToCompleteOnShutdown(true);//这样当spring关闭时,会等待所有已经启动的quartz job结束后spring才能完全shutdown。
factory.setOverwriteExistingJobs(false);
factory.setStartupDelay(1);
return factory;
}
/*
* 通过SchedulerFactoryBean获取Scheduler的实例
*/
@Bean(name="scheduler")
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
@Bean
public QuartzInitializerListener executorListener() {
return new QuartzInitializerListener();
}
调度器控制类
代码语言:javascript复制@Controller
@Slf4j
public class JobController {
private final JobService jobService;
@Autowired
public JobController(JobService jobService) {
this.jobService = jobService;
}
@GetMapping("/job")
public String applicationMain() {
return "/velocity/templates/job/list";
}
/**
* 保存定时任务
*/
@PostMapping(value = "/job/add")
@ResponseBody
public ResultModel<String> addJob(@RequestBody JobDTO form) {
try {
jobService.addJob(form);
} catch (Exception e) {
return new ResultModel<>(false, LoggerUtil.convertThrowableToString(e));
}
return new ResultModel<String>(true, "新增成功");
}
/**
* 删除定时任务
*/
@DeleteMapping(value = "/job/delete")
@ResponseBody
public ResultModel<String> deleteJob(JobForm form) throws SchedulerException {
if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) {
return new ResultModel<>(false,"参数不能为空");
}
try{
jobService.deleteJob(form);
} catch (Exception e) {
return new ResultModel<>(false, LoggerUtil.convertThrowableToString(e));
}
return new ResultModel<>(true, "删除成功");
}
/**
* 暂停定时任务
*/
@PutMapping(value = "/job/pause",params = "pause")
@ResponseBody
public ResultModel<String> pauseJob(JobForm form) throws SchedulerException {
if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) {
return new ResultModel<>(false,"参数不能为空");
}
try{
jobService.pauseJob(form);
} catch (Exception e) {
return new ResultModel<>(false, LoggerUtil.convertThrowableToString(e));
}
return new ResultModel<>(true, "暂停成功");
}
/**
* 恢复定时任务
*/
@PutMapping(value = "/job/resume",params = "resume")
@ResponseBody
public ResultModel<String> resumeJob(JobForm form) throws SchedulerException {
if (StrUtil.hasBlank(form.getJobGroupName(), form.getJobClassName())) {
return new ResultModel<>(false,"参数不能为空");
}
try{
jobService.resumeJob(form);
} catch (Exception e) {
return new ResultModel<>(false, LoggerUtil.convertThrowableToString(e));
}
return new ResultModel<>(true, "恢复成功");
}
/**
* 修改定时任务,定时时间
*/
@PutMapping(value = "/job/cron",params = "cron")
@ResponseBody
public ResultModel<String> cronJob(@Valid JobForm form) {
try {
jobService.cronJob(form);
} catch (Exception e) {
return new ResultModel<>(false, LoggerUtil.convertThrowableToString(e));
}
return new ResultModel<>(true, "修改成功");
}
@PostMapping(value = "/job/query")
@ResponseBody
public ResultModel<Page> jobList(@RequestBody JobDTO jobDTO) {
Integer currentPage=1;
Integer pageSize=10;
if (ObjectUtil.isNull(currentPage)) {
currentPage = 1;
}
if (ObjectUtil.isNull(pageSize)) {
pageSize = 10;
}
//IPage<JobAndTrigger> all = null;
Page<JobAndTrigger> all = null;
try{
all = jobService.list(currentPage, pageSize);
} catch (Exception e) {
return new ResultModel<>(false, null);
}
return new ResultModel<>(true, all);
}
@RequestMapping(value = "/job/editPrepare", method = RequestMethod.POST)
public String editPrepare(Model model, @RequestBody JobDTO jobDTO) {
model.addAttribute("requirementDTO", jobDTO);
return "/velocity/templates/job/saveOrUpdate";
}
```
调度器接口和实现类:
```java
/**
* @author zjq
* @date 2019
* <p>description:定時任务接口</p>
*/
public interface JobService {
/**
* 添加并启动定时任务
*
* @param form 表单参数 {@link JobForm}
* @throws Exception 异常
*/
void addJob(JobDTO form) throws Exception;
/**
* 删除定时任务
*
* @param form 表单参数 {@link JobForm}
* @throws SchedulerException 异常
*/
void deleteJob(JobForm form) throws SchedulerException;
/**
* 暂停定时任务
*
* @param form 表单参数 {@link JobForm}
* @throws SchedulerException 异常
*/
void pauseJob(JobForm form) throws SchedulerException;
/**
* 恢复定时任务
*
* @param form 表单参数 {@link JobForm}
* @throws SchedulerException 异常
*/
void resumeJob(JobForm form) throws SchedulerException;
/**
* 重新配置定时任务
*
* @param form 表单参数 {@link JobForm}
* @throws Exception 异常
*/
void cronJob(JobForm form) throws Exception;
/**
* 查询定时任务列表
*
* @param currentPage 当前页
* @param pageSize 每页条数
* @return 定时任务列表
*/
Page<JobAndTrigger> list(Integer currentPage, Integer pageSize);
}
@Service
@Slf4j
public class JobServiceImpl implements JobService {
private final Scheduler scheduler;
private final JobMapper jobMapper;
@Autowired
public JobServiceImpl(Scheduler scheduler, JobMapper jobMapper) {
this.scheduler = scheduler;
this.jobMapper = jobMapper;
}
/**
* 添加并启动定时任务
*
* @param form 表单参数 {@link JobForm}
* @return {@link JobDetail}
* @throws Exception 异常
*/
@Override
public void addJob(JobDTO form) throws Exception {
// 启动调度器
scheduler.start();
// 构建Job信息
JobDetail jobDetail = JobBuilder.newJob(JobUtil.getClass(form.getJobClassName()).getClass()).withIdentity(form.getJobClassName(), form.getJobGroup()).build();
// Cron表达式调度构建器(即任务执行的时间)
CronScheduleBuilder cron = CronScheduleBuilder.cronSchedule(form.getCronExpression());
//根据Cron表达式构建一个Trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(form.getJobClassName(), form.getJobGroup()).withSchedule(cron).build();
try {
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
log.error("【定时任务】创建失败!", e);
throw new Exception("【定时任务】创建失败!");
}
}
/**
* 删除定时任务
*
* @param form 表单参数 {@link JobForm}
* @throws SchedulerException 异常
*/
@Override
public void deleteJob(JobForm form) throws SchedulerException {
scheduler.pauseTrigger(TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName()));
scheduler.unscheduleJob(TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName()));
scheduler.deleteJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName()));
}
/**
* 暂停定时任务
*
* @param form 表单参数 {@link JobForm}
* @throws SchedulerException 异常
*/
@Override
public void pauseJob(JobForm form) throws SchedulerException {
scheduler.pauseJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName()));
}
/**
* 恢复定时任务
*
* @param form 表单参数 {@link JobForm}
* @throws SchedulerException 异常
*/
@Override
public void resumeJob(JobForm form) throws SchedulerException {
scheduler.resumeJob(JobKey.jobKey(form.getJobClassName(), form.getJobGroupName()));
}
/**
* 重新配置定时任务
*
* @param form 表单参数 {@link JobForm}
* @throws Exception 异常
*/
@Override
public void cronJob(JobForm form) throws Exception {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(form.getJobClassName(), form.getJobGroupName());
// 表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(form.getCronExpression());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 根据Cron表达式构建一个Trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
// 按新的trigger重新设置job执行
scheduler.rescheduleJob(triggerKey, trigger);
} catch (SchedulerException e) {
log.error("【定时任务】更新失败!", e);
throw new Exception("【定时任务】创建失败!");
}
}
/**
* 查询定时任务列表
*
* @param currentPage 当前页
* @param pageSize 每页条数
* @return 定时任务列表
*/
// @Override
// public IPage<JobAndTrigger> list(Integer currentPage, Integer pageSize) {
//
// QueryWrapper queryWrapper = new QueryWrapper();
// Page<JobAndTrigger> pojo = new Page<>(currentPage, pageSize);
// IPage<JobAndTrigger> page = this.page(pojo, queryWrapper);
// List<JobAndTrigger> list = JSON.parseArray(JSON.toJSONString(page.getRecords()), JobAndTrigger.class);
// page.setRecords(list);
// return page;
// }
@Override
public Page<JobAndTrigger> list(Integer currentPage, Integer pageSize) {
Page<JobAndTrigger> pojo = new Page<>(currentPage,pageSize);
List<JobAndTrigger> list = jobMapper.list(pojo);
pojo.setRecords(list);
return pojo;
}
}
```
前端代码:
<div style="height:100%; overflow:scroll">
<form class="layui-form" action="">
<!--$csrfToken.hiddenField-->
<!--场景查询条件-->
<div class="layui-form-item">
<label class="layui-form-label">任务名称</label>
<div class="layui-input-inline">
<input type="text" name="jobName" class="layui-input" id="jobName"/>
</div>
<label class="layui-form-label" style="width: 150px">任务状态:</label>
<div class="layui-input-inline" style="width: 170px">
<select name="jobType" id="jobType" style="width: max-content">
<option value="" selected>请选择任务状态</option>
<option value="RUNNING">运行中</option>
<option value="PAUSED">暂停</option>
</select>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="job_query">查询</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>
</form>
<hr/>
<table class="layui-table" id="job_list" lay-filter="job_list">
</table>
</div>
<!-- 表头批量操作工具 -->
<script type="text/html" id="headToolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="add">新增</button>
</div>
</script>
<!--表格中便捷toolbar-->
<script type="text/html" id="barTool">
<a class="layui-btn layui-btn-xs" lay-size="lg" lay-event="job_edit" style="width: 50px">编辑</a>
#*<a class="layui-btn layui-btn-xs" lay-size="lg" lay-event="job_remove">删除</a>*#
</script>
<script type="text/html" id="jobTypeBar">
<div>
{{# if(d.jobType === "MAIN"){ }}
主干回归用例
{{# }else if(d.jobType === "PROJECT"){ }}
项目测试用例
{{# }else{ }}
自定义用例
{{# } }}
</div>
</script>
<script src="/js/3rdParty/jquery-3.2.1.min.js"></script>
<script src="/js/job/job_list.js"></script>
<div>
<form class="layui-form">
<!--$csrfToken.hiddenField-->
<br/>
<br/>
<!--projectId-->
<input type="hidden" name="id" value="$!{jobDTO.id}">
## <input type="hidden" id="tempDto" name="dto" value="$!{jobDTO}">
<br/>
<div class="layui-form-item" style="width: 100%">
<label class="layui-form-label" style="width: 20%">任务名称:</label>
<div class="layui-input-inline" style="width: 70%">
<input type="text" name="jobName" id="jobName" value="$!{jobDTO.jobName}" required
lay-verify="required"
class="layui-input projId"></input>
</div>
</div>
<div class="layui-form-item" style="width: 100%">
<label class="layui-form-label" style="width: 20%">任务组:</label>
<div class="layui-input-inline" style="width: 70%">
<input type="text" name="jobGroup" id="jobGroup" value="$!{jobDTO.jobGroup}" required
lay-verify="required"
class="layui-input projId"></input>
</div>
</div>
<div class="layui-form-item" style="width: 100%">
<label class="layui-form-label" style="width: 20%">任务全类名:</label>
<div class="layui-input-inline" style="width: 70%">
<input type="text" name="jobClassName" id="jobClassName" value="$!{jobDTO.jobClassName}" required
lay-verify="required"
class="layui-input projId"></input>
</div>
</div>
<div class="layui-form-item" style="width: 100%">
<label class="layui-form-label" style="width: 20%">cron表达式:</label>
<div class="layui-input-inline" style="width: 70%">
<input type="text" name="cronExpression" id="cronExpression" value="$!{jobDTO.cronExpression}" required
lay-verify="required"
class="layui-input projId"></input>
</div>
</div>
<div class="layui-form-item" style="width: 100%">
<label class="layui-form-label" style="width: 20%">提醒信息:</label>
<div class="layui-input-inline" style="width: 70%">
<input type="areatext" name="notice" id="notice" value="$!{jobDTO.notice}" required
lay-verify="required"
class="layui-input projId"></input>
</div>
</div>
<div class="layui-form-item" style="width: 100%">
<label class="layui-form-label" style="width: 20%">@人员数组:</label>
<div class="layui-input-inline" style="width: 70%">
<input type="areatext" name="atPersons" id="atPersons" value="$!{jobDTO.atPersons}" required
lay-verify="required"
class="layui-input projId"></input>
</div>
</div>
<br/>
<!--*************此处提交部分******************-->
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn layui-btn-sm" lay-submit lay-filter="onSubmit">保存</button>
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
</div>
</div>
</form>
</div>