本文的出现是为了能够分享个人所学的相关知识,检验自身学习成果。内容会和其他技术存在部分关联,如有任何描述错误或者说明有误的地方,还望各位大佬指出。
1. 背景
Quartz(Job Scheduler),一个比较流行的定时任务框架。搜索引擎会有详细的介绍,此处不再赘述。
2. 环境
JDK:1.8
IDE:IDEA
maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<optional>true</optional>
</dependency>
3. 详解
Quartz的上手难度其实挺低的,它会给我们提供很多简单的定时任务方法。同时也是可以使用cron表达式设定任务执行时间,接下来的例子就是使用cron表达式进行任务的创建。cron的用法会在后续blog中讲解。
3.1 创建对外开放的类
import JobRunnable;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.text.ParseException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class QuartzUtil {
private static Scheduler SCHEDULER;
private static Map<String, Runnable> JOB_KEY_MAP = new LinkedHashMap<>(64);
private static ThreadPoolExecutor THREAD_POOL;
private QuartzUtil() {
}
@PostConstruct
public void init() {
// quartz scheduler
try {
SCHEDULER = StdSchedulerFactory.getDefaultScheduler();
// 及时启动
SCHEDULER.start();
// startDelayed(1) 为延时启动,参数为int类型,单位 秒
} catch (SchedulerException e) {
log.error("Create Default StdScheduler error.", e);
}
// threadpool
THREAD_POOL = new ThreadPoolExecutor(8, 64, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue(64), new ThreadPoolExecutor.AbortPolicy());
}
@PreDestroy
public void destroy() {
// 参数表示 当前正在执行的所有任务执行完才会结束scheduler
if (SCHEDULER != null) {
try {
SCHEDULER.shutdown(true);
} catch (SchedulerException e) {
log.error("Shutdown Scheduler error.", e);
}
}
if (THREAD_POOL != null && !THREAD_POOL.isShutdown()) {
THREAD_POOL.shutdown();
}
}
public static void addTask(String cron, Runnable runnable) {
if (runnable != null && CronExpression.isValidExpression(cron)) {
CronExpression cronExpression;
try {
cronExpression = new CronExpression(cron);
} catch (ParseException e) {
log.error("parse cron error,add Task error.", e);
return;
}
addTask(cronExpression, runnable);
} else {
log.error("param invalid, addTask error.");
return;
}
}
public static void addTask(CronExpression cron, Runnable runnable) {
if (runnable == null || cron == null || !CronExpression.isValidExpression(cron.getCronExpression())) {
log.error("param invalid, addTask error.");
return;
}
String uuid = UUID.randomUUID().toString();
JobDetail job = JobBuilder.newJob(JobRunnable.class).withIdentity(uuid).build();
Trigger trigger = TriggerBuilder.newTrigger()
.startNow()
.withIdentity(uuid)
.withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
try {
SCHEDULER.scheduleJob(job, trigger);
} catch (SchedulerException e) {
log.error("ScheduledJob error.", e);
}
JOB_KEY_MAP.put(uuid, runnable);
}
public static ThreadPoolExecutor getThreadPool() {
return THREAD_POOL;
}
public static Map<String, Runnable> getJobKeyMap() {
return JOB_KEY_MAP;
}
public static Scheduler getScheduler() {
return SCHEDULER;
}
}
3.2 创建一个实现org.quartz.Job的类JobRunnable
import QuartzUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
@Slf4j
public class JobRunnable implements Job {
@Override
public void execute(JobExecutionContext context) {
JobKey jobKey = context.getJobDetail().getKey();
if (QuartzUtil.getJobKeyMap().containsKey(jobKey.getName())) {
Runnable runnable = QuartzUtil.getJobKeyMap().get(jobKey.getName());
QuartzUtil.getThreadPool().execute(runnable);
} else {
try {
QuartzUtil.getScheduler().deleteJob(jobKey);
} catch (SchedulerException e) {
log.error("Job info is not contain ,Delete Job error.", e);
}
}
}
}
3.3 调用,验证方式为springboot启动后添加scheduled。当然你也可以在springboot入口类的main方法里简单验证下
import QuartzUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class StartWare implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// cron表达式 0/2 * * * * ? 表示每分钟的0秒开始没过2秒执行一次
QuartzUtil.addTask("0/2 * * * * ?", () -> log.info("0/2 * * * * ? scheduled exec."));
}
}
3.4 验证日志
INFO 3816 --- [pool-1-thread-1] c.w.q.common.springawares.StartWare : 0/2 * * * * ? scheduled exec.
INFO 3816 --- [pool-1-thread-2] c.w.q.common.springawares.StartWare : 0/2 * * * * ? scheduled exec.
INFO 3816 --- [pool-1-thread-3] c.w.q.common.springawares.StartWare : 0/2 * * * * ? scheduled exec.
INFO 3816 --- [pool-1-thread-4] c.w.q.common.springawares.StartWare : 0/2 * * * * ? scheduled exec.
INFO 3816 --- [pool-1-thread-5] c.w.q.common.springawares.StartWare : 0/2 * * * * ? scheduled exec.
INFO 3816 --- [pool-1-thread-6] c.w.q.common.springawares.StartWare : 0/2 * * * * ? scheduled exec.
在使用的过程中其实有很多需要注意的点,比如quartz对job和trigger的name进行的一致的校验。之所以会封装一层,改job为runnable,是为了更方便调用,包括1.8之后能使用lambda后能直接构建匿名内部类,减少不必要的类的创建。