简介
Quartz是一款功能强大的任务调度器,可以实现较为复杂的调度功能,如每月一号执行、每天凌晨执行、每周五执行等等,还支持分布式调度。本文使用Springboot+Mybatis-plus+Quartz实现对定时任务的增、删、改、查、启用、停用等功能。并把定时任务持久化到数据库,并且还实现动态编译运行Java,动态执行字符串代码模板。
为什么要使用Quartz
多任务情况下,quartz更容易管理,可以实现动态配置
执行时间表达式:
表达式示例:
Quartz的3个基本要素
- Scheduler:调度器。所有的调度都是由它控制。
- Trigger: 触发器。决定什么时候来执行任务。
- JobDetail & Job: JobDetail定义的是任务数据,而真正的执行逻辑是在Job中。使用JobDetail + Job而不是Job,这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。
集成Quartz
maven依赖:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
具体实现
1.定时任务处理
package com.example.quartz.component;
import com.example.quartz.constant.QuartzConstants;
import com.example.quartz.module.sms.QuartzTask;
import com.example.quartz.module.sms.QuartzTaskLog;
import com.example.quartz.provider.sms.mapper.QuartzTaskLogMapper;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* @author: wlp
* @description: 描述:定时任务处理
* @projectName: ScheduleJob
* @date: 2019/10/12
* @version: V1.0.0
*/
@Slf4j
@Component
public class MyScheduleJob extends QuartzJobBean {
private final ThreadLocal<ExecutorService> service = ThreadLocal.withInitial(Executors::newSingleThreadExecutor);
@Resource
private QuartzTaskLogMapper quartzTaskLogMapper;
@Override
protected void executeInternal(JobExecutionContext context) {
QuartzTask quartzTask = (QuartzTask) context.getMergedJobDataMap().get(QuartzConstants.QUARTZ_PARAM_KEY);
//任务开始时间
long startTime = System.currentTimeMillis();
QuartzTaskLog quartzTaskLog = QuartzTaskLog.builder()
.quartzId(quartzTask.getId())
.targetBean(quartzTask.getTargetBean())
.targetMethod(quartzTask.getTargetMethod())
.params(quartzTask.getParams())
.quartzName("执行定时任务【" + quartzTask.getQuartzName() + "】")
.quartzStatus(0)
.build();
quartzTaskLog.setCreateUserId(1L);
quartzTaskLog.setCreateTime(new Date());
try {
ScheduleRunnable task = new ScheduleRunnable(quartzTask.getTargetBean(), quartzTask.getTargetMethod(), quartzTask.getParams(), quartzTask.getQuartzType(), quartzTask.getTargetContent());
Future<?> future = service.get().submit(task);
future.get();
long times = System.currentTimeMillis() - startTime;
quartzTaskLog.setExecutionTime(times);
} catch (Exception e) {
log.error("任务执行失败,任务ID:" + quartzTask.getId(), e);
long times = System.currentTimeMillis() - startTime;
quartzTaskLog.setExecutionTime(times);
quartzTaskLog.setErrorInfo("任务执行完毕,任务ID:" + quartzTask.getId() + ", 总共耗时:" + times + "毫秒");
quartzTaskLog.setQuartzStatus(1);
quartzTaskLog.setErrorInfo("任务执行失败,任务ID:" + quartzTask.getId() + "===>>" + e.getMessage());
} finally {
quartzTaskLogMapper.insert(quartzTaskLog);
}
}
}
2.执行定时任务
package com.example.quartz.component;
import com.example.quartz.utils.DynamicCompilerJavaUtil;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
/**
* @author: wlp
* @description: 描述:执行定时任务
* @projectName: ScheduleRunnable
* @date: 2019/10/12
* @version: V1.0.0
*/
@Data
public class ScheduleRunnable implements Runnable {
private Object targetBean;
private Method method;
private String methodParams;
private Integer quartzType;
private String targetContent;
private String methodName;
private String className;
ScheduleRunnable(String beanName, String methodName, String methodParams, Integer quartzType, String targetContent) throws NoSuchMethodException, SecurityException {
this.className = beanName;
this.methodName = methodName;
this.quartzType = quartzType;
this.targetContent = targetContent;
this.targetBean = SpringContextHandler.getBean(beanName);
this.methodParams = methodParams;
if (StringUtils.isNotBlank(methodParams)) {
this.method = targetBean.getClass().getDeclaredMethod(methodName, String.class);
} else {
this.method = targetBean.getClass().getDeclaredMethod(methodName);
}
}
@Override
public void run() {
try {
if (quartzType.equals(1)) {
ReflectionUtils.makeAccessible(method);
if (StringUtils.isNotBlank(methodParams)) {
method.invoke(targetBean, methodParams);
} else {
method.invoke(targetBean);
}
} else {
DynamicCompilerJavaUtil.run(targetContent, methodName, className, methodParams);
}
} catch (Exception e) {
throw new RuntimeException("执行定时任务失败", e);
}
}
}
3.动态Java编译
package com.example.quartz.utils;
import lombok.extern.slf4j.Slf4j;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import static com.example.quartz.constant.JavaFileConstant.*;
/**
* @description: 动态Java编译
* @author: wlp
* @createDate: 2020-01-06
* @version: 1.0
*/
@Slf4j
public class DynamicCompilerJavaUtil {
private synchronized static void compile(String javaSourceCode, String className) throws Exception {
File file = new File(BASE_DIR + className + JAVA_SUFFIX);
boolean newFile = file.createNewFile();
if (newFile) {
file.deleteOnExit();
// 将代码输出到文件
PrintWriter out = new PrintWriter(new FileOutputStream(file));
out.println(javaSourceCode);
out.close();
//动态编译
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
int status = javac.run(null, null, null, "-d", BASE_DIR, BASE_DIR + file.getName());
if (status != 0) {
throw new Exception("语法错误!");
}
} else {
throw new Exception("创建文件失败!");
}
}
public static synchronized void run(String javaSourceCode, String methodName, String className, Object... params) throws Exception {
compile(javaSourceCode, className);
new File(BASE_DIR + className + SUFFIX).deleteOnExit();
try {
MyClassLoader loader = new MyClassLoader();
Class cls = loader.findClass(className);
if (null != params) {
Method main = cls.getMethod(methodName, params.getClass().getComponentType());
main.invoke(cls, params);
} else {
Method main = cls.getMethod(methodName, null);
main.invoke(cls, null);
}
} catch (Exception se) {
se.printStackTrace();
log.error("编译运行失败==》", se);
}
}
public static void main(String[] args) {
String str = "import com.baomidou.mybatisplus.core.toolkit.IdWorker;" +
"public class TestJava{\n" +
" public static void method(Object name){\n" +
" for (int i = 0; i < 10; i++) {\n" +
" String uuid = IdWorker.get32UUID();\n" +
" System.out.println(uuid);\n" +
" System.out.println(name);\n" +
" }\n" +
"\n" +
" }\n" +
"}";
try {
run(str, "method", "TestJava", "大吉大利");
} catch (Exception e) {
e.printStackTrace();
log.error("代码编译运行出错");
}
}
}
4.定时任务工具类
package com.example.quartz.utils;
import com.example.quartz.component.MyScheduleJob;
import com.example.quartz.constant.QuartzConstants;
import com.example.quartz.module.sms.QuartzTask;
import org.quartz.*;
/**
* @author: wlp
* @description: 描述:定时任务工具类
* @projectName: ScheduleUtils
* @date: 2019/10/12
* @version: V1.0.0
*/
public class SchedulerUtils {
/**
* 获取触发器key
*/
public static TriggerKey getTriggerKey(Long quartzId) {
return TriggerKey.triggerKey(QuartzConstants.TRIGGER_NAME + quartzId);
}
/**
* 获取jobKey
*/
public static JobKey getJobKey(Long quartzId) {
return JobKey.jobKey(QuartzConstants.QUARTZ_NAME + quartzId);
}
/**
* 获取表达式触发器
*/
public static CronTrigger getCronTrigger(Scheduler scheduler, Long quartzId) {
try {
return (CronTrigger) scheduler.getTrigger(getTriggerKey(quartzId));
} catch (SchedulerException e) {
throw new RuntimeException("获取定时任务CronTrigger出现异常", e);
}
}
/**
* 创建定时任务
*/
public static void createScheduleJob(Scheduler scheduler, QuartzTask quartzTask) {
try {
//构建job信息
JobDetail jobDetail = JobBuilder.newJob(MyScheduleJob.class).withIdentity(getJobKey(quartzTask.getId())).build();
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzTask.getQuartzCron())
.withMisfireHandlingInstructionDoNothing();
//按新的cronExpression表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(quartzTask.getId())).withSchedule(scheduleBuilder).build();
//放入参数,运行时的方法可以获取
jobDetail.getJobDataMap().put(QuartzConstants.QUARTZ_PARAM_KEY, quartzTask);
scheduler.scheduleJob(jobDetail, trigger);
//暂停任务
if (quartzTask.getQuartzStatus() == QuartzConstants.QUARTZ_STATUS_PUSH) {
pauseJob(scheduler, quartzTask.getId());
}
} catch (SchedulerException e) {
throw new RuntimeException("创建定时任务失败", e);
}
}
/**
* 更新定时任务
*/
public static void updateScheduleJob(Scheduler scheduler, QuartzTask quartzTask) {
try {
TriggerKey triggerKey = getTriggerKey(quartzTask.getId());
//表达式调度构建器
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzTask.getQuartzCron())
.withMisfireHandlingInstructionDoNothing();
CronTrigger trigger = getCronTrigger(scheduler, quartzTask.getId());
//按新的cronExpression表达式重新构建trigger
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//参数
trigger.getJobDataMap().put(QuartzConstants.QUARTZ_PARAM_KEY, quartzTask);
scheduler.rescheduleJob(triggerKey, trigger);
//暂停任务
if (quartzTask.getQuartzStatus() == QuartzConstants.QUARTZ_STATUS_PUSH) {
pauseJob(scheduler, quartzTask.getId());
}
} catch (SchedulerException e) {
throw new RuntimeException("更新定时任务失败", e);
}
}
/**
* 立即执行任务
*/
public static void run(Scheduler scheduler, QuartzTask quartzTask) {
try {
//参数
JobDataMap dataMap = new JobDataMap();
dataMap.put(QuartzConstants.QUARTZ_PARAM_KEY, quartzTask);
scheduler.triggerJob(getJobKey(quartzTask.getId()), dataMap);
} catch (SchedulerException e) {
throw new RuntimeException("立即执行定时任务失败", e);
}
}
/**
* 暂停任务
*/
public static void pauseJob(Scheduler scheduler, Long quartzId) {
try {
scheduler.pauseJob(getJobKey(quartzId));
} catch (SchedulerException e) {
throw new RuntimeException("暂停定时任务失败", e);
}
}
/**
* 恢复任务
*/
public static void resumeJob(Scheduler scheduler, Long quartzId) {
try {
scheduler.resumeJob(getJobKey(quartzId));
} catch (SchedulerException e) {
throw new RuntimeException("暂停定时任务失败", e);
}
}
/**
* 删除定时任务
*/
public static void deleteScheduleJob(Scheduler scheduler, Long quartzId) {
try {
scheduler.deleteJob(getJobKey(quartzId));
} catch (SchedulerException e) {
throw new RuntimeException("删除定时任务失败", e);
}
}
}
项目源码
可以自行下载源码运行测试即可