使用Spring的quartz实现一个简单定时任务调度管理系统
1.效果图(前端做的太low,所以结合了swagger2框架调试接口)
1.1:页面效果图
1.2:接口文档图:
2.主要功能
- 定时任务状态的启动、停止、恢复、立即执行一次。
- 定时任务执行周期的动态修改。
- 定时任务的增删改。
3.功能实现
3.3:数据库表结构:(暂时没加日志)
CREATE TABLE `job` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`job_name` varchar(255) NOT NULL COMMENT '任务名称',
`job_group` varchar(255) NOT NULL COMMENT '任务组名',
`status` int(255) NOT NULL COMMENT '任务状态:0=正常,1=暂停',
`trigger_name` varchar(255) NOT NULL COMMENT 'trigger名称',
`trigger_group` varchar(255) NOT NULL COMMENT 'trigger组名',
`invoke_target` varchar(255) NOT NULL COMMENT '调用目标字符串',
`concurrent` varchar(255) NOT NULL COMMENT '并发执行 0=允许,1=禁止',
`cron_expression` varchar(255) NOT NULL COMMENT 'cron执行表达式',
`misfire_policy` varchar(255) NOT NULL COMMENT '计划策略 0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4;
3.2:添加依赖
(项目是使用springboot构建,以下只展示主要用到的依赖)
<!-- 定时任务 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.3.12</version>
</dependency>
<!-- 流处理 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
<!-- swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!-- swagger2第三方ui -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
3.2:代码实现
省略Controller、mapper、实体类和接口代码
3.2.1: 任务调度通用常量代码
public class ScheduleConstants {
/** jobKey前缀标识 */
public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME";
/** 执行目标key */
public static final String TASK_PROPERTIES = "TASK_PROPERTIES";
/** 默认 */
public static final String MISFIRE_DEFAULT = "0";
/** 立即触发执行 */
public static final String MISFIRE_IGNORE_MISFIRES = "1";
/** 触发一次执行 */
public static final String MISFIRE_FIRE_AND_PROCEED = "2";
/** 不触发立即执行 */
public static final String MISFIRE_DO_NOTHING = "3";
}
3.2.2: 调用quartz抽象类
public abstract class AbstractQuartzJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
MyJob myJob = new MyJob();
copyBeanProp(myJob, jobExecutionContext.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
try {
//执行定时任务
doExecute(jobExecutionContext,myJob);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 执行方法,由子类重载
*
* @param context 工作执行上下文对象
* @param myJob 系统计划任务
* @throws Exception 执行过程中的异常
*/
protected abstract void doExecute(JobExecutionContext context, MyJob myJob) throws Exception;
/**
* Bean属性复制工具方法。
*
* @param dest 目标对象
* @param src 源对象
*/
public static void copyBeanProp(Object dest, Object src) {
try {
copyProperties(src, dest);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.2.3: job任务类
Job任务累分为俩种:允许并发执行和禁止并非执行
/**
* 定时任务处理(禁止并发执行)
*/
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
{
@Override
protected void doExecute(JobExecutionContext context, MyJob job) throws Exception
{
JobInvokeUtil.invokeMethod(job);
}
}
/**
* 定时任务处理(允许并发执行)
*/
public class QuartzJobExecution extends AbstractQuartzJob
{
@Override
protected void doExecute(JobExecutionContext context, MyJob myJob) throws Exception
{
JobInvokeUtil.invokeMethod(myJob);
}
}
3.2.4: Spring工具类
@Component
public final class SpringUtils implements BeanFactoryPostProcessor{
/**
* Spring应用上下文环境
*/
private static ConfigurableListableBeanFactory beanFactory;
/**
* 获取对象
*
* @param name
* @return Object 一个以所给名字注册的bean的实例
* @throws BeansException
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException {
return (T) beanFactory.getBean(name);
}
3.2.5: 任务执行工具工具类
public class JobInvokeUtil {
/**
* 调用任务方法
*
* @param bean 目标对象
* @param methodName 方法名称
* @param methodParams 方法参数
*/
private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
if (isNotNull(methodParams) && methodParams.size() > 0) {
Method method = bean.getClass().getDeclaredMethod(methodName, getMethodParamsType(methodParams));
method.invoke(bean, getMethodParamsValue(methodParams));
} else {
Method method = bean.getClass().getDeclaredMethod(methodName);
method.invoke(bean);
}
}
/**
* 执行方法
*
* @param job 系统任务
*/
public static void invokeMethod(MyJob job) throws Exception {
String invokeTarget = job.getInvokeTarget();
//获取bean的名称
String beanName = getBeanName(invokeTarget);
//获取方法名
String methodName = getMethodName(invokeTarget);
List<Object[]> methodParams = getMethodParams(invokeTarget);
if (!isValidClassName(beanName)) {
Object bean = SpringUtils.getBean(beanName);
invokeMethod(bean, methodName, methodParams);
} else {
Object bean = Class.forName(beanName).newInstance();
invokeMethod(bean, methodName, methodParams);
}
}
/**
* 获取bean名称
*
* @param invokeTarget 目标字符串
* @return bean名称
*/
public static String getBeanName(String invokeTarget) {
String beanName = StringUtils.substringBefore(invokeTarget, "(");
return StringUtils.substringBeforeLast(beanName, ".");
}
/**
* 获取bean中的方法
*
* @param invokeTarget 目标字符串
* @return method方法
*/
public static String getMethodName(String invokeTarget) {
String methodName = StringUtils.substringBefore(invokeTarget, "(");
return StringUtils.substringAfterLast(methodName, ".");
}
/**
* 校验是否为为class包名
*
* @param invokeTarget 名称
* @return true是 false否
*/
public static boolean isValidClassName(String invokeTarget) {
return StringUtils.countMatches(invokeTarget, ".") > 1;
}
/**
* 获取method方法参数相关列表
*
* @param invokeTarget 目标字符串
* @return method方法相关参数列表
*/
public static List<Object[]> getMethodParams(String invokeTarget) {
String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
if (StringUtils.isEmpty(methodStr)) {
return null;
}
String[] methodParams = methodStr.split(",");
List<Object[]> classs = new LinkedList<>();
for (int i = 0; i < methodParams.length; i++) {
String str = StringUtils.trimToEmpty(methodParams[i]);
// String字符串类型,包含'
if (StringUtils.contains(str, "'")) {
classs.add(new Object[]{StringUtils.replace(str, "'", ""), String.class});
}
// boolean布尔类型,等于true或者false
else if (StringUtils.equals(str, "true") || StringUtils.equalsIgnoreCase(str, "false")) {
classs.add(new Object[]{Boolean.valueOf(str), Boolean.class});
}
// long长整形,包含L
else if (StringUtils.containsIgnoreCase(str, "L")) {
classs.add(new Object[]{Long.valueOf(StringUtils.replaceIgnoreCase(str, "L", "")), Long.class});
}
// double浮点类型,包含D
else if (StringUtils.containsIgnoreCase(str, "D")) {
classs.add(new Object[]{Double.valueOf(StringUtils.replaceIgnoreCase(str, "D", "")), Double.class});
}
// 其他类型归类为整形
else {
classs.add(new Object[]{Integer.valueOf(str), Integer.class});
}
}
return classs;
}
/**
* 获取参数值
*
* @param methodParams 参数相关列表
* @return 参数值列表
*/
public static Object[] getMethodParamsValue(List<Object[]> methodParams) {
Object[] classs = new Object[methodParams.size()];
int index = 0;
for (Object[] os : methodParams) {
classs[index] = (Object) os[0];
index++;
}
return classs;
}
/**
* 获取参数类型
*
* @param methodParams 参数相关列表
* @return 参数类型列表
*/
public static Class<?>[] getMethodParamsType(List<Object[]> methodParams) {
Class<?>[] classs = new Class<?>[methodParams.size()];
int index = 0;
for (Object[] os : methodParams) {
classs[index] = (Class<?>) os[1];
index++;
}
return classs;
}
/**
* * 判断一个对象是否非空
*
* @param object Object
* @return true:非空 false:空
*/
public static boolean isNotNull(Object object) {
return object == null;
}
}
3.2.6: Schedule工具类
public class ScheduleUtil {
/**
* 得到job任务类
*
* @param myJob 执行计划
* @return 具体执行任务类
*/
private static Class<? extends Job> getQuartzJobClass(MyJob myJob) {
boolean isConcurrent = "0".equals(myJob.getConcurrent());
return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
}
public static void intSchedule(Scheduler scheduler, MyJob myJob) throws Exception {
//1.创建JobDetail
JobDetail jobDetail = JobBuilder.newJob(getQuartzJobClass(myJob))
.withIdentity(getJobKey(myJob.getId(), myJob.getJobGroup()))
.build();
//1.1绑定传递参数
jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, myJob);
//2.cron表达式调度构建器
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(myJob.getCronExpression());
cronScheduleBuilder = handleCronScheduleMisfirePolicy(myJob, cronScheduleBuilder);
//3.按照cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(getTriggerKey(myJob.getId(),
myJob.getJobGroup()))
.withSchedule(cronScheduleBuilder)
.build();
// 4.判断是否存在
if (scheduler.checkExists(getJobKey(myJob.getId(), myJob.getJobGroup()))) {
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(getJobKey(myJob.getId(), myJob.getJobGroup()));
}
// 5.注册任务和定时器
scheduler.scheduleJob(jobDetail, trigger);
// 6.暂停任务
//判断定时任务状态 状态为1 暂停
if (myJob.getStatus().equals("1")) {
scheduler.pauseJob(getJobKey(myJob.getId(), myJob.getJobGroup()));
}
}
/**
* 构建任务键对象
*/
public static JobKey getJobKey(Long jobId, String jobGroup) {
return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
}
/** 构建任务触发对象 */
public static TriggerKey getTriggerKey(Long jobId, String jobGroup) {
return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup);
}
/**
* 设置定时任务策略
*/
public static CronScheduleBuilder handleCronScheduleMisfirePolicy(MyJob myJob, CronScheduleBuilder cb)
throws Exception {
switch (myJob.getMisfirePolicy()) {
case ScheduleConstants.MISFIRE_DEFAULT:
return cb;
case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
return cb.withMisfireHandlingInstructionIgnoreMisfires();
case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
return cb.withMisfireHandlingInstructionFireAndProceed();
case ScheduleConstants.MISFIRE_DO_NOTHING:
return cb.withMisfireHandlingInstructionDoNothing();
default:
throw new Exception("参数有问题" + myJob.getMisfirePolicy());
}
}
}
3.2.6: JobService类
@Service
public class JobServiceImpl implements JobService {
@Autowired
private Scheduler scheduler;
@Autowired
private JobMapper jobMapper;
/**
* 初始化
* @throws SchedulerException
*/
@PostConstruct
public void intJob() throws SchedulerException {
List<MyJob> myJobs = jobMapper.queryQuartz();
//先清理缓存
scheduler.clear();
//创建任务
myJobs.forEach(myJob -> {
try {
ScheduleUtil.intSchedule(scheduler,myJob);
} catch (SchedulerException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
});
}
/**
* 启动一次任务
* @throws SchedulerException
*/
@Override
public void runJob(Long id) throws SchedulerException {
MyJob myJob = jobMapper.queryJobById(id);
JobDataMap dataMap = new JobDataMap();
//设置参数
dataMap.put(ScheduleConstants.TASK_PROPERTIES, myJob);
scheduler.triggerJob(ScheduleUtil.getJobKey(myJob.getId(), myJob.getJobGroup()), dataMap);
}
/**
* 暂停任务
* @throws SchedulerException
*/
@Override
public void stopJob(Long id) throws SchedulerException {
MyJob myJob = jobMapper.queryJobById(id);
scheduler.pauseJob(ScheduleUtil.getJobKey(myJob.getId(), myJob.getJobGroup()));
}
/**
* 恢复任务
* @throws SchedulerException
*/
@Override
public void resumeJob(Long id) throws SchedulerException {
MyJob myJob = jobMapper.queryJobById(id);
scheduler.resumeJob(ScheduleUtil.getJobKey(myJob.getId(), myJob.getJobGroup()));
}
/**
* 删除任务
* @throws SchedulerException
*/
@Override
public void deleteJob(Long id) throws SchedulerException {
//删除任务
MyJob myJob = jobMapper.queryJobById(id);
scheduler.deleteJob(ScheduleUtil.getJobKey(myJob.getId(), myJob.getJobGroup()));
//删除数据
MyJob myJob = scheduleMapper.deleteQuartzById(id);
//更新任务
List<MyJob> jobs = jobMapper.queryJob();
//先清理缓存
scheduler.clear();
jobs.forEach(j -> {
try {
ScheduleUtil.intSchedule(scheduler,j);
} catch (SchedulerException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
});
}
/**
* 更新任务
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSchedulerJob(MyJob myJob) throws Exception {
//更新数据
scheduleMapper.updateQuartzById(myJob.getId());
// 判断是否存在
JobKey jobKey = ScheduleUtil.getJobKey(myJob.getId(), myJob.getJobGroup());
if (scheduler.checkExists(jobKey)){
// 防止创建时存在数据问题 先移除,然后在执行创建操作
scheduler.deleteJob(jobKey);
}
ScheduleUtil.intSchedule(scheduler,myJob);
}
}
4.使用方法
在数据库中增加两条如下数据
编写TaskService
//注意这里的bean名称
@Service("test")
public class TaskService {
//注意这里的方法名称 这俩个对应数据库中invoke_target字段
public void test(){
System.err.println("task测试成功");
}
public void testParam(String params){
System.out.println(params);
System.err.println("task测试成功");
}
}
任务会通过反射调用这里的方法,具体看原理如下分析
5.原理
数据库字段的使用地方:
id、job_name、job_group、trigger_name、trigger_group、concurrent、cron_expression、misfire_policy这些字段在定时任务初始化时被当作生成jobkey和其他条件使用。
iid、job_name、nvoke_target这些字段在定时任务调度时被当作生成jobkey和调用自建方法的条件使用
第一步:动态初始化定时任务
初始化的每个任务都会根据数据库的数据生成自己的JobKey。(JobKey根据id和常量生成的)
getJobKey()是生成jobkey的方法
第二步:动态定时任务的调度
当用户在管理平台操作执行一次定时任务时,会拿着id传到后端,后端将任务数据查出来后,并使用数据生成对于的JobKey,执行对应jobkey的任务。
接下来定时任务会调用我们实现的Job接口中的execute方法
最后一步就是根据数据通过反射调用当时写好的方法
注意:上面方法处理的数据是数据库中的invoke_target字段,小数点前缀是Bean名称,后缀是方法名
5.总结
通过以上的学习,进一步加强了对spring的认知,太强大了。