springboot 之集成quartz
前言
一直没机会做spring生态圈的框架,公司选择的是一些小众的微服务,鉴于此考虑,丰富自己的技术栈,花了两天时间从网上各网站上学习了springboot一些基础知识。
本章只介绍springboot微服务集成quartz,用于项目中用到的一些定时任务,调度任务框架。
环境准备
- IntelliJ IDEA
- 前一章中搭建的微服务框架
开始集成
-
pom.xml中增加依赖包
依赖包.png
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
-
quartz的使用分为两种类型,一种为服务启动时定时执行任务,另一种为服务启动后,通过某些操作控制的任务(可以通过操作对其进行停止,删除,启动...)
2.1.1 先说第一种:
EnableScheduling.png
在入口类DemoApplication中启用调度任务:增加注解@EnableScheduling
2.1.2 在demo包下新建schedule包,用于存放调度任务相关类,在schedule包下新建TestSchedule类:
TestSchedule.png
package com.example.demo.schedule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 类功能描述:<br>
* <ul>
* <li>类功能描述1<br>
* <li>类功能描述2<br>
* <li>类功能描述3<br>
* <li>#cron 的表达式:
*(1)0 0 2 1 * ? * 表示在每月的1日的凌晨2点调整任务
*
*(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
*
*(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
*
*(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
*
*(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
*
*(6)0 0 12 ? * WED 表示每个星期三中午12点
*
*(7)0 0 12 * * ? 每天中午12点触发
*
*(8)0 15 10 ? * * 每天上午10:15触发
*
*(9)0 15 10 * * ? 每天上午10:15触发
*
*(10)0 15 10 * * ? * 每天上午10:15触发
*
*(11)0 15 10 * * ? 2005 2005年的每天上午10:15触发
*
*(12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
*
*(13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
*
*(14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
*
*(15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
*
*(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
*
*(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
*
*(18)0 15 10 15 * ? 每月15日上午10:15触发
*
*(19)0 15 10 L * ? 每月最后一日的上午10:15触发
*
*(20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
*
*(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
*
*(22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发<br>
* </ul>
* 修改记录:<br>
* <ul>
* <li>修改记录描述1<br>
* <li>修改记录描述2<br>
* <li>修改记录描述3<br>
* </ul>
*
* @author xuefl
* @version 5.0 since 2019-12-19
*/
@Component
@Slf4j
public class TestSchedule {
@Scheduled(fixedRate = 10 * 1000, initialDelay = 5000) //采用间隔调度,每10秒执行一次
public void runJoba(){ //定义一个执行的任务
log.info("[*******MyTaskA -- 间隔调度 ******]"+
new SimpleDateFormat("yyy-MM-dd HH:mm:ss.SSS").format(new Date()));
}
@Scheduled(cron = "*/10 * * * * ?") //采用间隔调度,每10秒执行一次
public void runJobb(){ //定义一个执行的任务
log.info("[*******MyTaskB -- 间隔调度 ******]"+
new SimpleDateFormat("yyy-MM-dd HH:mm:ss.SSS").format(new Date()));
}
}
此类中包含两种定时方式的定时任务(1.每n秒执行一次定时任务,2.按照cron表达式执行任务),使用方法只需要在执行业务的方法前加@Scheduled注解即可,根据不同场景,使用适当的定时方式执行定时任务
2.2.1 在与DemoApplication同级的包下新建一个Schedule的配置类SchedulerAutoConfiguration,用于通过ScheduleFactory生成schedule实例:
SchedulerAutoConfiguration.png
package com.example.demo;
import org.quartz.Scheduler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import java.io.IOException;
/**
* 类功能描述:<br>
* <ul>
* <li>类功能描述1<br>
* <li>类功能描述2<br>
* <li>类功能描述3<br>
* </ul>
* 修改记录:<br>
* <ul>
* <li>修改记录描述1<br>
* <li>修改记录描述2<br>
* <li>修改记录描述3<br>
* </ul>
*
* @author xuefl
* @version 5.0 since 2019-12-19
*/
@Configuration
public class SchedulerAutoConfiguration {
//这个地方如果需要使用自定义的executor,可以在别的地方配置好,然后这里注入
//@Autowired
//private ThreadPoolTaskExecutor taskExecutor;
@Bean(name="SchedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setAutoStartup(true);
//这里如果不配置任务池,它就会默认加载SimpleThreadPool
//factory.setTaskExecutor();
return factory;
}
@Bean(name="funnyScheduler")
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
}
此处定义的Bean funnyScheduler会被后续schedule对象操作类中注入,注入时,名称必须一致
2.2.2 在service包下新建JobScheduleService接口,定义对调度任务的操作抽象方法
JobScheduleService.png
package com.example.demo.service;
import java.util.Date;
/**
* 类功能描述:<br>
* <ul>
* <li>类功能描述1<br>
* <li>类功能描述2<br>
* <li>类功能描述3<br>
* </ul>
* 修改记录:<br>
* <ul>
* <li>修改记录描述1<br>
* <li>修改记录描述2<br>
* <li>修改记录描述3<br>
* </ul>
*
* @author xuefl
* @version 5.0 since 2019-12-19
*/
public interface JobScheduleService {
/**
* 功能描述: 添加简单任务
* 可以自定义一个任务信息对象,然后从信息对象中获取参数创建简单任务
*
* @param
* @return:void
* @since: v1.0
* @Author:xf
* @Date: 2019/3/15 17:00
*/
void addSimpleJob(Class taskClass, String jobName, String jobGroup, Date startTime, Date endTime);
/**
* 功能描述: 添加定时任务
* 可以自定义一个任务信息对象,然后从信息对象中获取参数创建定时任务
*
* @param
* @return:void
* @since: v1.0
* @Author:xf
* @Date: 2019/3/15 17:00
*/
void addCronJob(Class taskClass, String jobName, String jobGroup, Date startTime, Date endTime, String cronExpression);
/**
* 功能描述: 修改任务Trigger,即修改任务的定时机制
*
* @param
* @return:void
* @since: v1.0
* @Author:xf
* @Date: 2019/3/15 17:00
*/
void modifyJob(String jobName, String jobGroup, String cronExpression);
/**
* 功能描述: 暂停任务,只支持定时任务的暂停,不支持单次任务,单次任务需要interrupt
*
* @param
* @return:void
* @since: v1.0
* @Author:xf
* @Date: 2019/3/15 17:00
*/
void pauseJob(String jobName, String jobGroup);
/**
* 功能描述: 从暂停状态中恢复定时任务运行
*
* @param
* @return:void
* @since: v1.0
* @Author:xf
* @Date: 2019/3/15 17:00
*/
void resumeJob(String jobName, String jobGroup);
/**
* 功能描述: 删除任务
*
* @param
* @return:void
* @since: v1.0
* @Author:xf
* @Date: 2019/3/15 17:00
*/
void deleteJob(String jobName, String jobGroup);
}
2.2.3 在service.impl包下新建JobScheduleService接口的实现类JobScheduleServiceImpl
JobScheduleServiceImpl.png
package com.example.demo.service.impl;
import com.example.demo.service.JobScheduleService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.text.ParseException;
import java.util.Date;
/**
* 类功能描述:<br>
* <ul>
* <li>类功能描述1<br>
* <li>类功能描述2<br>
* <li>类功能描述3<br>
* </ul>
* 修改记录:<br>
* <ul>
* <li>修改记录描述1<br>
* <li>修改记录描述2<br>
* <li>修改记录描述3<br>
* </ul>
*
* @author xuefl
* @version 5.0 since 2019-12-19
*/
@Service
@Slf4j
public class JobScheduleServiceImpl implements JobScheduleService {
/**
* 因为在配置中设定了这个bean的名称,这里就需要指定bean的名称,不然启动就会报错
*/
@Autowired
@Qualifier("funnyScheduler")
private Scheduler scheduler;
/**
* 功能描述: 添加简单任务
*
* @param
* @return:void
* @since: v1.0
* @Author:xf
* @Date: 2019/3/15 17:00
*/
@Override
public void addSimpleJob(Class taskClass, String jobName, String jobGroup, Date startTime, Date endTime) {
JobDetail jobDetail = JobBuilder.newJob(taskClass).withIdentity(jobName, jobGroup).build();
SimpleTrigger simpleTrigger = TriggerBuilder.newTrigger()
.withIdentity(jobName, jobGroup)
.startAt(startTime)
.endAt(endTime)
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(3)
.withRepeatCount(5))
.build();
try {
scheduler.scheduleJob(jobDetail, simpleTrigger);
} catch (SchedulerException e) {
log.error("addSimpleJob catch {}", e.getMessage());
}
}
/**
* 功能描述: 添加定时任务
*
* @param
* @return:void
* @since: v1.0
* @Author:xf
* @Date: 2019/3/15 17:00
*/
@Override
public void addCronJob(Class taskClass, String jobName, String jobGroup, Date startTime, Date endTime, String cronExpression) {
JobDetail jobDetail = JobBuilder.newJob(taskClass).withIdentity(jobName, jobGroup).build();
// 触发器
try {
CronTrigger trigger = new CronTriggerImpl(jobName, jobGroup, jobName, jobGroup, startTime, endTime, cronExpression);// 触发器名,触发器组
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException | ParseException e) {
log.error("addCronJob catch {}", e.getMessage());
}
}
/**
* 功能描述: 修改任务Trigger,即修改任务的定时机制
*
* @param
* @return:void
* @since: v1.0
* @Author:xf
* @Date: 2019/3/15 17:00
*/
@Override
public void modifyJob(String jobName, String jobGroup, String cronExpression) {
TriggerKey oldKey = new TriggerKey(jobName, jobGroup);
//表达式调度构建器(即任务执行的时间)
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().
withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build();
try {
scheduler.rescheduleJob(oldKey, trigger);
} catch (SchedulerException e) {
log.error("modifyJob catch {}", e.getMessage());
}
}
/**
* 功能描述: 暂停任务,只支持定时任务的暂停,不支持单次任务,单次任务需要interrupt
*
* @param
* @return:void
* @since: v1.0
* @Author:xf
* @Date: 2019/3/15 17:00
*/
@Override
public void pauseJob(String jobName, String jobGroup) {
JobKey jobKey = new JobKey(jobName, jobGroup);
try {
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if (StringUtils.isEmpty(jobDetail)) {
System.out.println("没有这个job");
}
scheduler.pauseJob(jobKey);
} catch (SchedulerException e) {
log.error("pauseJob catch {}", e.getMessage());
}
}
/**
* 功能描述: 从暂停状态中恢复定时任务运行
*
* @param
* @return:void
* @since: v1.0
* @Author:xf
* @Date: 2019/3/15 17:00
*/
@Override
public void resumeJob(String jobName, String jobGroup) {
JobKey jobKey = new JobKey(jobName, jobGroup);
try {
scheduler.resumeJob(jobKey);
} catch (SchedulerException e) {
log.error("resumeJob catch {}", e.getMessage());
}
}
/**
* 功能描述: 删除任务
*
* @param
* @return:void
* @since: v1.0
* @Author:xf
* @Date: 2019/3/15 17:00
*/
@Override
public void deleteJob(String jobName, String jobGroup) {
JobKey jobKey = new JobKey(jobName, jobGroup);
try {
scheduler.deleteJob(jobKey);
} catch (SchedulerException e) {
log.error("deleteJob catch {}", e.getMessage());
}
}
}
此类实现了JobScheduleService 接口定义的对调度任务各种操作的具体实现步骤,通过注入funnyScheduler来操作。
2.2.4 定义了对调度任务的操作类后,需要增加自己的调度任务业务实现类,也就是任务具体要干的事,需要实现quartz中的Job接口,并重写其execute方法,在其中增加自己的业务流程,在schedule包中新建job包,并在job包下新建SimpleJob类:
SimpleJob.png
package com.example.demo.schedule.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* 类功能描述:<br>
* <ul>
* <li>类功能描述1<br>
* <li>类功能描述2<br>
* <li>类功能描述3<br>
* </ul>
* 修改记录:<br>
* <ul>
* <li>修改记录描述1<br>
* <li>修改记录描述2<br>
* <li>修改记录描述3<br>
* </ul>
*
* @author xuefl
* @version 5.0 since 2019-12-19
*/
@Slf4j
public class SimpleJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
log.info("简单任务执行中");
}
}
2.2.5 最后这个任务是怎么被调用的呢?其实在任何地方,业务执行过程中,rest接口中,都可以对这个任务进行CUD操作:
scheduleSimpleJob.png
package com.example.demo.controller;
import com.example.demo.schedule.job.SimpleJob;
import com.example.demo.service.JobScheduleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Date;
/**
* 类功能描述:<br>
* <ul>
* <li>类功能描述1<br>
* <li>类功能描述2<br>
* <li>类功能描述3<br>
* </ul>
* 修改记录:<br>
* <ul>
* <li>修改记录描述1<br>
* <li>修改记录描述2<br>
* <li>修改记录描述3<br>
* </ul>
*
* @author xuefl
* @version 5.0 since 2019-12-18
*/
@RestController
@Api(value = "SwaggerValue", tags={"SwaggerController"},description = "swagger应用", produces = MediaType.APPLICATION_JSON_VALUE)
public class SimpleController {
@Resource
private JobScheduleService jobScheduleService;
@RequestMapping(value = "/hello", method = RequestMethod.GET)
@ApiOperation(value="hello",httpMethod = "GET",notes="hello",produces = MediaType.APPLICATION_JSON_VALUE)
public String sayHello() {
return "hello world";
}
@RequestMapping(value = "/scheduleSimpleJob", method = RequestMethod.POST)
@ApiOperation(value="scheduleSimpleJob",httpMethod = "POST",notes="scheduleSimpleJob",produces = MediaType.APPLICATION_JSON_VALUE)
public void scheduleSimpleJob() {
jobScheduleService.addSimpleJob(SimpleJob.class,
"simpleJob", "simpleJob", new Date(), null);
}
}
注入jobScheduleService对象后 增加一个接口/scheduleSimpleJob,增加其实现业务方法,通过jobScheduleService对象对调度任务进行管理,此处只以addSimpleJob为例,其他方法大家可以增加rest接口以测试
-
启动后运行日志如下
调度任务线程池实例化.png
第一种定时任务运行日志.png
第二种调度任务,接口调用后运行日志.png