SpringBoot整合Quartz定时任务
文章目录
1.加载properties方式
1.pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
或
<!--quartz-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<!--quartz-jobs-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
2.quartz.properties配置文件
#Main Scheduler Settings
#配置集群时,quartz调度器的id,由于配置集群时,只有一个调度器,必须保证每个服务器该值都相同,可以不用修改,只要每个ams都一样就行
org.quartz.scheduler.instanceName=quartzScheduler_ym
org.quartz.scheduler.rmi.export=false
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false
#集群中每台服务器自己的id,AUTO表示自动生成,无需修改
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.threadsInheritContextClassLoaderOfInitializer=true
org.quartz.scheduler.skipUpdateCheck=true
#一次性取出的任务数,默认值是1,适合负载均衡,但不适合大量的短时任务
org.quartz.scheduler.batchTriggerAcquisitionMaxCount=25
#Configure ThreadPool
#quartz线程池的实现类,无需修改
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#quartz线程池中线程数,可根据任务数量和负载度来调整
org.quartz.threadPool.threadCount=25
#quartz线程优先级
org.quartz.threadPool.threadPriority=5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
#Configure JobStore
org.quartz.jobStore.acquireTriggersWithinLock=true
#表示如果某个任务到达执行时间,而此时线程池中没有可用线程时,任务等待的最大时间,如果等待时间超过下面配置的值(毫秒),本次就不在执行,而等待下一次执行时间的到来,可根据任务量和负责程度来调整
org.quartz.jobStore.misfireThreshold=60000
#实现集群时,任务的存储实现方式,org.quartz.impl.jdbcjobstore.JobStoreTX表示数据库存储,无需修改
org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#quartz存储任务相关数据的表的前缀,无需修改
org.quartz.jobStore.tablePrefix=QRTZ_
#是否启用集群,启用,改为true,注意:启用集群后,必须配置下面的数据源,否则quartz调度器会初始化失败
org.quartz.jobStore.isClustered=true
#集群中服务器相互检测间隔,每台服务器都会按照下面配置的时间间隔往服务器中更新自己的状态,如果某台服务器超过以下时间没有checkin,调度器就会认为该台服务器已经down掉,不会再分配任务给该台服务器
org.quartz.jobStore.clusterCheckinInterval=10000
#Configure DataSources
#连接数据库数据源名称,与下面配置中org.quartz.dataSource.myDS的myDS一致即可,可以无需修改
org.quartz.jobStore.dataSource=myDS
org.quartz.dataSource.myDS.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.myDS.URL=jdbc:mysql://127.0.0.1:3306/ruoyi_local?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
org.quartz.dataSource.myDS.user=root
org.quartz.dataSource.myDS.password=root
#配置连接数据库连接池大小,一般为上面配置的线程池的2倍
org.quartz.dataSource.myDS.maxConnections=50
#org.quartz.dataSource.myDS.validationQuery=select 1 from dual
1.普通方法定时任务配置执行
/**
* 定时任务执行方法
* @author moshangshang
*/
public class QuartzTest {
public void test(){
System.out.println("该方法被调用" );
}
}
配置触发
/**
* Quartz配置(方法测试任务配置)
*/
@Configuration
public class QuartzConfig {
/**
* 要调用的工作类
*/
@Bean
public QuartzTest quartzDay(){
return new QuartzTest();
}
// targetMethod: 指定需要定时执行QuartzTest中的test()方法
// concurrent:对于相同的JobDetail,当指定多个Trigger时, 很可能第一个job完成之前,
// 第二个job就开始了。指定concurrent设为false,多个job不会并发运行,第二个job将不会在第一个job完成之前开始。
// triggers:通过再添加其他的ref元素可在list中放置多个触发器。
@Bean(name = "daymailtask")
public MethodInvokingJobDetailFactoryBean detailFactoryBean(@Qualifier("quartzDay") Object quartzDay) {
MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
bean.setTargetObject(quartzDay);
//调用方法名
bean.setTargetMethod("test");
bean.setConcurrent(false);
return bean;
}
@Bean(name = "doDayMailTime")
public CronTriggerFactoryBean cronTriggerBean(@Qualifier("daymailtask") MethodInvokingJobDetailFactoryBean daymailtask) {
CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
trigger.setJobDetail(daymailtask.getObject());
// 十秒一次
trigger.setCronExpression("0/10 * * * * ?");
return trigger;
}
@Bean
@Lazy(false)
public SchedulerFactoryBean schedulerFactory(Trigger[] cronTriggerBean) {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
System.err.println(cronTriggerBean[0]);
bean.setTriggers(cronTriggerBean);
return bean;
}
@Bean
public Scheduler scheduler(SchedulerFactoryBean schedulerFactoryBean) {
return schedulerFactoryBean.getScheduler();
}
}
2.对于quartz定时任务job中不能注入bean对象处理
由于Quartz的job类中无法直接注入spring容器的bean对象,会报空指针异常,所以自定义的quartz的工厂来将spring的bean进行注入,然后在调度器工厂的bean中进行设置,即可以在job类中对bean进行注入,但在操作时需要注入自己配置的工厂bean对象。
@Component
public class CustomJobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
//进行注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
@Configuration
public class QuartzConfig {
@Autowired
private CustomJobFactory customJobFactory;
@SneakyThrows
@Bean("schedulerFactory")
public StdSchedulerFactory scheduler(){
StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 自定义 JobFactory 使得在 Quartz Job 中可以使用 @Autowired
scheduler.setJobFactory(customJobFactory);
scheduler.start();
return schedulerFactory;
}
}
@Autowired
private StdSchedulerFactory schedulerFactory ;
3.加载属性配置文件
@Slf4j
@Component
public class QuartzManager implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private StdSchedulerFactory schedulerFactory ;
private static final String CONFIG_FILE = "quartz.properties";
//操作方法,见操作工具类
private void init() {
try {
Properties properties = new Properties();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(CONFIG_FILE));
schedulerFactory.initialize(properties);
System.out.println(schedulerFactory.getScheduler());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (null == event.getApplicationContext().getParent()) {
init();
}
}
}
使用参考springboot整合,使用schedulerFactory操作
2.纯yml配置方式
pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
推荐使用springboot的自动注入依赖及yml配置,该版本测试基于springboot2.7
yml配置文件
spring:
#定时任务
quartz:
# 将任务等保存化到数据库
job-store-type: jdbc
# 程序结束时会等待quartz相关的内容结束
wait-for-jobs-to-complete-on-shutdown: true
# QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录
overwrite-existing-jobs: true
# 延迟启动
startup-delay: 1
properties:
org:
quartz:
# scheduler相关
scheduler:
# scheduler的实例名
instanceName: scheduler
rmi:
export: false
proxy: false
#集群中每台服务器自己的id,AUTO表示自动生成,无需修改,由于配置集群时,只有一个调度器,必须保证每个服务器该值都相同,
instanceId: AUTO
wrapJobExecutionInUserTransaction: false
threadsInheritContextClassLoaderOfInitializer: true
skipUpdateCheck: true
#一次性取出的任务数,默认值是1,适合负载均衡,但不适合大量的短时任务
batchTriggerAcquisitionMaxCount: 25
# 持久化相关
jobStore:
acquireTriggersWithinLock: true
# 表示如果某个任务到达执行时间,而此时线程池中没有可用线程时,任务等待的最大时间,
# 如果等待时间超过下面配置的值(毫秒),本次就不在执行,而等待下一次执行时间的到来,可根据任务量和负责程度来调整
misfireThreshold: 60000
# 实现集群时,任务的存储实现方式,org.quartz.impl.jdbcjobstore.JobStoreTX表示数据库存储
# 升级spring boot > 2.5.6的版本后将不再支持JobStoreTX方式进行配置默认数据源
class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
#oracle数据库为 org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# quartz存储任务相关数据的表的前缀
tablePrefix: qrtz_
# 是否启用集群,启用,改为true,注意:启用集群后,必须配置下面的数据源,否则quartz调度器会初始化失败
isClustered: true
# 集群中服务器相互检测间隔,每台服务器都会按照下面配置的时间间隔往服务器中更新自己的状态,
# 如果某台服务器超过以下时间没有checkin,调度器就会认为该台服务器已经down掉,不会再分配任务给该台服务器
clusterCheckinInterval: 10000
# 是否将JobDataMap中的属性转为字符串存储
useProperties: false
# 线程池相关
threadPool:
class: org.quartz.simpl.SimpleThreadPool
# 线程池中线程数,可根据任务数量和负载度来调整
threadCount: 25
# 线程优先级
threadPriority: 5
threadsInheritContextClassLoaderOfInitializingThread: true
# 数据源配置
# 连接数据库数据源名称,与下面配置中dataSource.myDS的myDS一致
dataSource: myDS
dataSource.myDS.driver: com.mysql.cj.jdbc.Driver
dataSource.myDS.URL: jdbc:mysql://127.0.0.1:3306/ruoyi_local?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
dataSource.myDS.user: root
dataSource.myDS.password: root
# 配置连接数据库连接池大小,一般为上面配置的线程池的2倍
dataSource.myDS.maxConnections: 50
# dataSource.myDS.validationQuery: select 1 from dual
编写任务核心处理方法
注意事项:
- Quartz的定时任务的job类中实现的StatefulJob接口已过时,在开发时尽量使用 实现Job接口或者继承QuartzJobBean类的方式进行执行类的实现。
/**
* 定时任务执行方法
* @author moshangshang
*/
public class QuartzTestJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
String userName = (String) context.getJobDetail().getJobDataMap().get("username");
System.out.println("userName:" + userName);
}
}
1.编写任务执行配置
/**
* 定时任务执行配置
* @author moshangshang
*/
@Configuration
public class TaskConfig {
@Bean("helloJob")
public JobDetail helloJobDetail() {
return JobBuilder.newJob(QuartzTestJob.class)
.withIdentity("DateTimeJob")
.usingJobData("username", "Hello Quartz")
.storeDurably()//即使没有Trigger关联时,也不需要删除该JobDetail
.build();
}
@Bean
public Trigger printTimeJobTrigger() {
// 每秒执行一次
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/1 * * * * ?");
return TriggerBuilder.newTrigger()
.forJob(helloJobDetail())
.withIdentity("quartzTaskService")
.withSchedule(cronScheduleBuilder)
.build();
}
}
2.注入SchedulerFactoryBean动态使用
springboot使用yml配置时可直接给工具类注入SchedulerFactoryBean
对象使用
在使用yml自动配置时,配置自定义的StdSchedulerFactory
,获取的是默认的属性配置,yml不会生效,需使用SchedulerFactoryBean
@Autowired
private SchedulerFactoryBean schedulerFactory;
- 1
- 2
3.操作工具类
@Slf4j
@Component
public class QuartzManager {
@Autowired
private Scheduler scheduler;
/**
* 添加一个定时任务
*
* @param jobName 任务名
* @param jobGroupName 任务组名
* @param triggerName 触发器名
* @param triggerGroupName 触发器组名
* @param jobClass 任务
* @param cron 时间设置
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass,
String cron, String id) {
try {
// 任务名,任务组,任务执行类
JobDetail jobDetail = JobBuilder.newJob(jobClass)
.withIdentity(jobName, jobGroupName)
.build();
// 参数
JobDataMap jobDataMap = jobDetail.getJobDataMap();
jobDataMap.put("jkConfigId", id);
// 创建Trigger对象
Trigger trigger = TriggerBuilder.newTrigger()
// 触发器名,触发器组
.withIdentity(triggerName, triggerGroupName).startNow()
// 触发器时间设定
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
// 调度容器设置JobDetail和Trigger
scheduler.scheduleJob(jobDetail, trigger);
// 启动
if (!scheduler.isShutdown()) {
scheduler.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 添加一个定时任务
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public void addJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName, Class jobClass,
String cron, Map<String, Object> dataMap) {
try {
// 任务名,任务组,任务执行类
JobDetail jobDetail = JobBuilder.newJob(jobClass)
.withIdentity(jobName, jobGroupName)
.build();
// 参数
JobDataMap jobDataMap = jobDetail.getJobDataMap();
//传递的所有参数
jobDataMap.putAll(dataMap);
// 创建Trigger对象
Trigger trigger = TriggerBuilder.newTrigger()
// 触发器名,触发器组
.withIdentity(triggerName, triggerGroupName).startNow()
// 触发器时间设定
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
// 调度容器设置JobDetail和Trigger
scheduler.scheduleJob(jobDetail, trigger);
// 启动
if (!scheduler.isShutdown()) {
scheduler.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 修改一个任务的触发时间
*
* @param triggerName 触发器名
* @param triggerGroupName 触发器组名
* @param cron 时间设置
*/
public void modifyJobTime(String triggerName, String triggerGroupName, String cron) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (trigger == null) {
return;
}
String oldTime = trigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(cron)) {
/** 方式一:修改一个任务的触发时间 */
// 创建Trigger对象
trigger = TriggerBuilder.newTrigger().startNow()
// 触发器时间设定
.withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
scheduler.rescheduleJob(triggerKey, trigger);
/** 方式二:先删除,然后再创建一个新的Job */
// JobDetail jobDetail =
// scheduler.getJobDetail(JobKey.jobKey(jobName,jobGroupName));
// Class<? extends Job> jobClass = jobDetail.getJobClass();
// removeJob(jobName,jobGroupName,triggerName,triggerGroupName);
// addJob(jobName,jobGroupName,triggerName,triggerGroupName,jobClass,cron);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 触发状态为运行的任务
* 立即执行一次任务
*/
public void triggerRunningJob(String jobName, String jobGroupName) {
try {
JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
scheduler.triggerJob(jobKey);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 移除一个任务
*/
public void removeJob(String jobName, String jobGroupName, String triggerName, String triggerGroupName) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
// 停止触发器
scheduler.pauseTrigger(triggerKey);
// 移除触发器
scheduler.unscheduleJob(triggerKey);
// 删除任务
scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 启动所有定时任务
*/
public void startJobs() {
try {
scheduler.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 关闭所有定时任务
*/
public void shutdownJobs() {
try {
if (!scheduler.isShutdown()) {
scheduler.shutdown();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 暂停所有定时任务
*/
public void standby() {
try {
scheduler.standby();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
注意:shutdown方法后不能再启动,会报错,可以使用暂停standby
方法
The Scheduler cannot be restarted after shutdown() has been called.
测试
@RestController
@RequestMapping("/quartz")
public class QuartzController {
@Autowired
private QuartzManager quartzManager;
@GetMapping("/start")
public void test4() {
quartzManager.startJobs();
}
@GetMapping("/standby")
public void test5() {
quartzManager.standby();
}
@GetMapping("/add")
public void test6() {
quartzManager.addJob("DateTimeJob","aaa",
"quartzTaskService","bbb", QuartzTestJob.class,"0/1 * * * * ?","11");
}
@GetMapping("/remove")
public void test7() {
quartzManager.removeJob("DateTimeJob",null,"quartzTaskService",null);
}
}