一、前言
实现定时任务的方式有很多,本文重点介绍Quartz在eladmin中的应用。
二、Quartz简介
Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
2.1 核心概念
Job
(任务): Job 是 Quartz 中用于执行具体任务逻辑的接口或抽象类,开发者需要实现该接口或继承该类,并编写实际的任务逻辑。每个 Job 都必须定义一个执行的任务。
java
public interface Job {
void execute(JobExecutionContext var1) throws JobExecutionException;
}
-
JobDetail
(任务详情):JobDetail
是对Job
的具体实例化和描述,它定义了Job
的属性和配置信息,包括Job
的名称、所属的组、Job
类型、Job
是否持久化等。JobDetail
用来描述和配置Job
,作为调度器执行的任务对象。 -
Trigger
(触发器):-Trigger
用于定义任务的触发条件,即何时执行任务。Quartz 提供了多种类型的触发器,包括简单触发器(SimpleTrigger
)和 Cron 触发器(CronTrigger
)。触发器可以设置任务的执行时间、执行频率、重复次数等。 -
Scheduler
(调度器):Scheduler
是 Quartz 的核心组件,负责触发和执行Job
。Scheduler
可以启动、暂停、恢复和停止任务调度。它管理着Job
和Trigger
,根据Trigger
的设定来触发 Job 的执行。 -
JobStore
(任务存储):JobStore
是 Quartz 中用于存储Job
和Trigger
的接口,它定义了如何存储和管理调度的任务。Quartz 提供了多种实现JobStore
的方式,包括内存存储、数据库存储等。 -
Listener
(监听器):Quartz 提供了一套监听器机制,可以监听调度器、触发器和任务的各种事件。开发者可以通过监听器来处理任务执行前后的逻辑,例如记录日志、发送通知等。
三、 Quartz在eladmin中的应用
3.1 定义Job
eladmin 源码
java
@Async
public class ExecutionJob extends QuartzJobBean {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
// 此处仅供参考,可根据任务执行情况自定义线程池参数
private final ThreadPoolTaskExecutor executor = SpringContextHolder.getBean("elAsync");
@Override
public void executeInternal(JobExecutionContext context) {
// 获取任务
QuartzJob quartzJob = (QuartzJob) context.getMergedJobDataMap().get(QuartzJob.JOB_KEY);
// 获取spring bean
QuartzLogRepository quartzLogRepository = SpringContextHolder.getBean(QuartzLogRepository.class);
QuartzJobService quartzJobService = SpringContextHolder.getBean(QuartzJobService.class);
RedisUtils redisUtils = SpringContextHolder.getBean(RedisUtils.class);
String uuid = quartzJob.getUuid();
QuartzLog log = new QuartzLog();
log.setJobName(quartzJob.getJobName());
log.setBeanName(quartzJob.getBeanName());
log.setMethodName(quartzJob.getMethodName());
log.setParams(quartzJob.getParams());
long startTime = System.currentTimeMillis();
log.setCronExpression(quartzJob.getCronExpression());
try {
// 执行任务
QuartzRunnable task = new QuartzRunnable(quartzJob.getBeanName(), quartzJob.getMethodName(), quartzJob.getParams());
Future<?> future = executor.submit(task);
future.get();
long times = System.currentTimeMillis() - startTime;
log.setTime(times);
if(StringUtils.isNotBlank(uuid)) {
redisUtils.set(uuid, true);
}
// 任务状态
log.setIsSuccess(true);
logger.info("任务执行成功,任务名称:" + quartzJob.getJobName() + ", 执行时间:" + times + "毫秒");
// 判断是否存在子任务
if(StringUtils.isNotBlank(quartzJob.getSubTask())){
String[] tasks = quartzJob.getSubTask().split("[,,]");
// 执行子任务
quartzJobService.executionSubJob(tasks);
}
} catch (Exception e) {
if(StringUtils.isNotBlank(uuid)) {
redisUtils.set(uuid, false);
}
logger.error("任务执行失败,任务名称:" + quartzJob.getJobName());
long times = System.currentTimeMillis() - startTime;
log.setTime(times);
// 任务状态 0:成功 1:失败
log.setIsSuccess(false);
log.setExceptionDetail(ThrowableUtil.getStackTrace(e));
// 任务如果失败了则暂停
if(quartzJob.getPauseAfterFailure() != null && quartzJob.getPauseAfterFailure()){
quartzJob.setIsPause(false);
//更新状态
quartzJobService.updateIsPause(quartzJob);
}
if(quartzJob.getEmail() != null){
EmailService emailService = SpringContextHolder.getBean(EmailService.class);
// 邮箱报警
if(StringUtils.isNoneBlank(quartzJob.getEmail())){
EmailVo emailVo = taskAlarm(quartzJob, ThrowableUtil.getStackTrace(e));
emailService.send(emailVo, emailService.find());
}
}
} finally {
quartzLogRepository.save(log);
}
}
private EmailVo taskAlarm(QuartzJob quartzJob, String msg) {
EmailVo emailVo = new EmailVo();
emailVo.setSubject("定时任务【"+ quartzJob.getJobName() +"】执行失败,请尽快处理!");
Map<String, Object> data = new HashMap<>(16);
data.put("task", quartzJob);
data.put("msg", msg);
TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
Template template = engine.getTemplate("taskAlarm.ftl");
emailVo.setContent(template.render(data));
List<String> emails = Arrays.asList(quartzJob.getEmail().split("[,,]"));
emailVo.setTos(emails);
return emailVo;
}
}
解读:
QuartzJobBean
介绍:QuartzJobBean
帮助简化Quartz Job的实现,并提供与Spring框架的无缝集成。QuartzJobBean
提供了一个executeInternal方法,该方法会在每次触发Job时被调用,以执行具体的任务逻辑。开发人员只需专注于编写业务逻辑代码,而不必关心与Quartz框架的集成细节。- 业务逻辑
3.2 自定义线程池elAsync及定义QuartzRunnable
细心的同学会发现在QuartzJobBean
中使用到了自定义线程池elAsync
和来执行定时任务,为何要这样做呢?我们先看源码,首先是自定义线程池:
scss
@Configuration
public class CustomExecutorConfig {
/**
* 自定义线程池,用法 @Async
* @return Executor
*/
@Bean
@Primary
public Executor elAsync() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(AsyncTaskProperties.corePoolSize);
executor.setMaxPoolSize(AsyncTaskProperties.maxPoolSize);
executor.setQueueCapacity(AsyncTaskProperties.queueCapacity);
executor.setThreadNamePrefix("el-async-");
executor.setKeepAliveSeconds(AsyncTaskProperties.keepAliveSeconds);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
/**
* 自定义线程池,用法 @Async("otherAsync")
* @return Executor
*/
@Bean
public Executor otherAsync() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(15);
executor.setQueueCapacity(50);
executor.setKeepAliveSeconds(AsyncTaskProperties.keepAliveSeconds);
executor.setThreadNamePrefix("el-task-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
然后是QuartzRunnable
源码:
kotlin
@Slf4j
public class QuartzRunnable implements Callable<Object> {
private final Object target;
private final Method method;
private final String params;
QuartzRunnable(String beanName, String methodName, String params)
throws NoSuchMethodException, SecurityException {
this.target = SpringContextHolder.getBean(beanName);
this.params = params;
if (StringUtils.isNotBlank(params)) {
this.method = target.getClass().getDeclaredMethod(methodName, String.class);
} else {
this.method = target.getClass().getDeclaredMethod(methodName);
}
}
@Override
@SuppressWarnings("all")
public Object call() throws Exception {
ReflectionUtils.makeAccessible(method);
if (StringUtils.isNotBlank(params)) {
method.invoke(target, params);
} else {
method.invoke(target);
}
return null;
}
}
解读:
- 为何要使用线程池:
- 看到线程池,就要想起线程池的作用------------提高线程的利用率、减少资源开销(避免线程反复创建销毁)进而提高性能、控制并发度等;而自定义线程池
elAsync
则在此基础上增加了灵活性、扩展性和定制化的优势。Quartz结合线程池可以并发处理多个定时任务。
- 看到线程池,就要想起线程池的作用------------提高线程的利用率、减少资源开销(避免线程反复创建销毁)进而提高性能、控制并发度等;而自定义线程池
QuartzRunnable
的作用:封装一个要在 Quartz 调度器中执行的任务,并提供了灵活的方式来调用目标方法- 首先在实例化
QuartzRunnable
对象时,要传入beanName、methodName、params三个属性,用于定位要执行的目标方法,再利用Java的反射机制调用目标方法达到执行定时任务的目的。
- 首先在实例化
3.3 定义QuartzManage
QuartzManage
中通过JobDetail
,Trigger
以及Scheduler
三者组合,实现添加定时任务,删除定时任务,暂停定时任务等操作。
scss
@Slf4j
@Component
public class QuartzManage {
private static final String JOB_NAME = "TASK_";
@Resource
private Scheduler scheduler;
/**
*
* @param quartzJob
*/
public void addJob(QuartzJob quartzJob){
try {
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(ExecutionJob.class).
withIdentity(JOB_NAME + quartzJob.getId()).build();
//通过触发器名和cron 表达式创建 Trigger
Trigger cronTrigger = newTrigger()
.withIdentity(JOB_NAME + quartzJob.getId())
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression()))
.build();
cronTrigger.getJobDataMap().put(QuartzJob.JOB_KEY, quartzJob);
//重置启动时间
((CronTriggerImpl)cronTrigger).setStartTime(new Date());
//执行定时任务
scheduler.scheduleJob(jobDetail,cronTrigger);
// 暂停任务
if (quartzJob.getIsPause()) {
pauseJob(quartzJob);
}
} catch (Exception e){
log.error("创建定时任务失败", e);
throw new BadRequestException("创建定时任务失败");
}
}
/**
* 更新job cron表达式
* @param quartzJob /
*/
public void updateJobCron(QuartzJob quartzJob){
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if(trigger == null){
addJob(quartzJob);
trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
}
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression());
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//重置启动时间
((CronTriggerImpl)trigger).setStartTime(new Date());
trigger.getJobDataMap().put(QuartzJob.JOB_KEY,quartzJob);
scheduler.rescheduleJob(triggerKey, trigger);
// 暂停任务
if (quartzJob.getIsPause()) {
pauseJob(quartzJob);
}
} catch (Exception e){
log.error("更新定时任务失败", e);
throw new BadRequestException("更新定时任务失败");
}
}
/**
* 删除一个job
* @param quartzJob /
*/
public void deleteJob(QuartzJob quartzJob){
try {
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
scheduler.pauseJob(jobKey);
scheduler.deleteJob(jobKey);
} catch (Exception e){
log.error("删除定时任务失败", e);
throw new BadRequestException("删除定时任务失败");
}
}
/**
* 恢复一个job
* @param quartzJob /
*/
public void resumeJob(QuartzJob quartzJob){
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if(trigger == null) {
addJob(quartzJob);
}
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
scheduler.resumeJob(jobKey);
} catch (Exception e){
log.error("恢复定时任务失败", e);
throw new BadRequestException("恢复定时任务失败");
}
}
/**
* 立即执行job
* @param quartzJob /
*/
public void runJobNow(QuartzJob quartzJob){
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if(trigger == null) {
addJob(quartzJob);
}
JobDataMap dataMap = new JobDataMap();
dataMap.put(QuartzJob.JOB_KEY, quartzJob);
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
scheduler.triggerJob(jobKey,dataMap);
} catch (Exception e){
log.error("定时任务执行失败", e);
throw new BadRequestException("定时任务执行失败");
}
}
/**
* 暂停一个job
* @param quartzJob /
*/
public void pauseJob(QuartzJob quartzJob){
try {
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
scheduler.pauseJob(jobKey);
} catch (Exception e){
log.error("定时任务暂停失败", e);
throw new BadRequestException("定时任务暂停失败");
}
}
}
解读:
这里面的代码逻辑比较简单,看代码注释也能理解,就不一一画流程图了,后面有机会补上。