定时任务——quartz
基本使用
官网:http://www.quartz-scheduler.org/
先理解quartz的模型关系
一个调度器可以调度多个触发器
一个触发器只可以触发一个任务详情
一个任务详情可以被多个触发器调用
一个Job可以被多个任务详情包含(一般情况下不会用到这种场景)
一般会定义两种触发器,一种是自动触发器,一种是手动触发器
跟着官网文档走,导入数据库,然后就可以进行案例实践了
这里引入一个logback配置,方便查看日志信息
<?xml version="1.0" encoding="UTF-8" ?>
<configuration debug="false" scan="false">
<!-- Console log output -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- WARN级别 -->
<root level="WARN">
<appender-ref ref = "console"/>
</root>
</configuration>
官网上的Demo
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.util.Date;
import java.util.StringJoiner;
/**
* Author: gzy
* Date: 2022/11/22-10:52
* Description:
* quartz中的job都要实现Job接口
* quartz所有的定时任务都要实现Job接口
*/
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
StringJoiner outStr = new StringJoiner(" ")
.add("Hello.execute")
.add(new Date().toString())
.add("======" + Thread.currentThread().getName())
.add(jobExecutionContext.getTrigger().getKey().getName()); // 触发器的名称
// System.out.println("HelloJobExecute: " + new Date() + "======" + Thread.currentThread().getName());
System.out.println(outStr);
}
}
/**
* Author: gzy
* Date: 2022/11/22-10:41
* Description:
* 调度器 ->>> 触发器 ->>> 任务详情(任务)
* 为什么中间有一层触发器
*/
public class QuartzTest {
public static void main(String[] args) {
try {
// Grab the Scheduler instance from the Factory
// 调度器工厂获取一个默认的调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// and start it off
// 启动
scheduler.start();
// define the job and tie it to our HelloJob class
//
JobDetail job = newJob(HelloJob.class)
.withIdentity("job1", "group1")
.build();
// Trigger the job to run now, and then repeat every 40 seconds
// 触发 jobDetail 触发器
Trigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startNow() // 启动
.withSchedule(simpleSchedule() // 调度策略
.withIntervalInSeconds(5) // 5s一次
.repeatForever()) // 永远循环下去
.build();
// Tell quartz to schedule the job using our trigger
// job 被 调度器调度
scheduler.scheduleJob(job, trigger);
// 主线程先睡一下, 以看到程序的效果
TimeUnit.SECONDS.sleep(20);
// 关闭
scheduler.shutdown();
} catch (SchedulerException | InterruptedException se) {
se.printStackTrace();
}
}
}
接下来的案例验证调度器、触发器、作业详情之间的关系
两个触发器调用一个JobDetail
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
/**
* Author: KongXiao
* Date: 2022/11/22-10:41
* Description:
* 调度器 ->>> 触发器 ->>> 任务详情(任务)
* 问什么中间有一层触发器
* 一对多的关系 一个调度器 多个触发器; 多个触发器 一个任务详情
* 一个触发器只能调度一个任务
( 一个 Job 可以被多个 JobDetail 引用
*/
public class QuartzTest2 {
public static void main(String[] args) {
try {
// Grab the Scheduler instance from the Factory
// 调度器工厂获取一个默认的调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// and start it off
// 启动
scheduler.start();
// define the job and tie it to our HelloJob class
//
JobDetail job = newJob(HelloJob.class)
.withIdentity("job1", "group1")
.build();
// Trigger the job to run now, and then repeat every 40 seconds
// 触发 jobDetail 触发器
Trigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.build();
Trigger trigger2 = newTrigger()
.withIdentity("trigger2", "group1")
.startNow() // 启动
.forJob("job1", "group1") // 指定 Job name 和 group,指定要触发的 job
.withSchedule(simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
// job 被 调度器调度
scheduler.scheduleJob(job, trigger);
scheduler.scheduleJob(trigger2); // 如果是一个未被调度过的Job,使用 forJob的方式,然后调度器想要调度它,但是只传了一个触发器,会出异常
// 主线程先睡一下
TimeUnit.SECONDS.sleep(3);
// 关闭
scheduler.shutdown();
} catch (SchedulerException | InterruptedException se) {
se.printStackTrace();
}
}
}
两个调度器调用两个作业详情
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
/**
* Author: KongXiao
* Date: 2022/11/22-10:41
* Description:
* 调度器 ->>> 触发器 ->>> 任务详情(任务)
* 问什么中间有一层触发器
* 一对多的关系 一个调度器 多个触发器; 多个触发器 一个任务详情
* 一个触发器只能调度一个任务
* 一个 Job 可以被多个 JobDetail 引用
*
* 一个调度器对应俩触发器,一个触发器对应一个jobDetail,一个jobDetail可以被多个触发器调用,一个JobDetail包含一个Job
*/
public class QuartzTest3 {
public static void main(String[] args) {
try {
// Grab the Scheduler instance from the Factory
// 调度器工厂获取一个默认的调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// and start it off
// 启动
scheduler.start();
// define the job and tie it to our HelloJob class
//
JobDetail job = newJob(HelloJob.class)
.withIdentity("job1", "group1")
.build();
JobDetail job2 = newJob(HelloJob.class) // 两个JobDetail包含了一个Job
.withIdentity("job2", "group1")
.build();
// Trigger the job to run now, and then repeat every 40 seconds
// 触发 jobDetail 触发器
Trigger trigger = newTrigger()
.withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever())
.build();
Trigger trigger2 = newTrigger()
.withIdentity("trigger2", "group1")
.startNow() // 启动
.forJob("job2", "group1") // 指定 Job name 和 group,指定要触发的 job
.withSchedule(simpleSchedule()
.withIntervalInSeconds(3)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
// job 被 调度器调度
scheduler.scheduleJob(job, trigger);
scheduler.scheduleJob(job2, trigger2); // 如果只传入一个触发器,会出异常
// scheduler.scheduleJob(trigger2); // org.quartz.JobPersistenceException: The job (group1.job2) referenced by the trigger does not exist.
// 主线程先睡一下
TimeUnit.SECONDS.sleep(3);
// 关闭
scheduler.shutdown();
} catch (SchedulerException | InterruptedException se) {
se.printStackTrace();
}
}
}
简单理解一下JobDetail和Trigger的name属性和group属性
name属性主要是用来方便标识的
group属性主要用来方便管理和标识的
多个作业可以放在一个群组下面进行管理
可以不为group赋值,但是底层会为其赋一个 DEFAULT 值
只有一个JobDetail和一个Trigger时,可以不指定name和group,如下
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
/**
* Author: KongXiao
* Date: 2022/11/22-10:41
* Description:
* 调度器 ->>> 触发器 ->>> 任务详情(任务)
* 问什么中间有一层触发器
* 一对多的关系 一个调度器 多个触发器; 多个触发器 一个任务详情 一个触发器只能调度一个任务
* 一个 Job 可以被多个 JobDetail 引用
*
*
* JobDetail和Trigger的name和group的作用:
* name是用来标记的
* group是用来标记、方便管理的(比如,一个群组下也可以有多个job)
* 可以不为 group赋值,那么 group底层会赋 DEFAULT 值
*
* 如果只有一个触发器、一个JobDetail,那么可以不指定name和group,可以试一试, 那么此时线程名是MD5加密的串
*
*
* 那么现在的模型大概是这样的
* 一个调度器可以调用多个触发器
* 一个触发器只可以调用一个任务详情
* 一个任务详情可以被多个触发器触发
* 一个任务可以被多个任务详情包含
*
* 一般情况下,不会使用多个JobDetail去包含一个Job的场景,会编写两种触发器,一个自动触发器,一个手动触发器.
*/
public class QuartzTest4 {
public static void main(String[] args) {
try {
// Grab the Scheduler instance from the Factory
// 调度器工厂获取一个默认的调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// and start it off
// 启动
scheduler.start();
// define the job and tie it to our HelloJob class
//
JobDetail job = newJob(HelloJob.class)
// .withIdentity("job1", "group1")
.build();
// Trigger the job to run now, and then repeat every 40 seconds
// 触发 jobDetail 触发器
Trigger trigger = newTrigger()
// .withIdentity("trigger1", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(1)
// .repeatForever())
.withRepeatCount(1)) // 指定重复执行的次数
.build();
// Tell quartz to schedule the job using our trigger
// job 被 调度器调度
scheduler.scheduleJob(job, trigger);
// 主线程先睡一下
TimeUnit.SECONDS.sleep(3);
// 关闭
scheduler.shutdown();
} catch (SchedulerException | InterruptedException se) {
se.printStackTrace();
}
}
}
几个触发器
CalendarIntervalTriggerImpl (org.quartz.impl.triggers)
SimpleTriggerImpl (org.quartz.impl.triggers)
DailyTimeIntervalTriggerImpl (org.quartz.impl.triggers)
CronTriggerImpl (org.quartz.impl.triggers) 重点、常用!!!
CronExpression cron表达式
Cron表达式生成网站 https://cron.qqe2.com/
...
传参以及依赖注入
Job的需要一个无参构造方法才可以被实例化,Job的实例化不是我们创建的,是quartz去帮我们实现的
传入一个Job.class到newJob中可以创建出一个Job的实例
在Job实例化的时候传参可以从JobDetail和Trigger下手,调用usingJobData()方法,底层是个Map。在Job中取值,可以从Job上下文中调用 getJobDetail()/getTrigger().getJobDataMap().get(key);
getMergedJobDataMap()可以获取合并后的数据,如果JobDetail和Trigger的key相同,那么会以Trigger的key优先,获取到的就是Trigger相应的值
Job中定义一个变量并定义相应的set方法,也可以获取值
如果在Job中注入Spring中的Bean该怎么操作呢?
Spring的Bean是由Spring管理的,而quartz是不认这个的,所以直接在Job中注入依赖是获取不到值的
@Component
public class SpringDIUtils implements ApplicationContextAware {
public static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Job中获取值
xxx = (xxx) SpringDIUtils.applicationContext.getBean(StringUtils.uncapitalize(xxx.class.getSimpleName()));
quartz的配置文件
quartz的配置文件是quartz.properties,如果不自行配置的话会默认使用自带的配置文件, 在 quartz的jar包的 org.quartz包下
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
org.quartz.jobStore.misfireThreshold: 60000
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了