在日常工作中,经常会遇到一些定时任务,比如定时发邮件、异构数据库同步数据等。目前比较常用的是SpringBoot+Quartz实现动态定时任务的Web项目,这种模式通常需要建立定时任务相关的表。本例为SpringBoot+Quartz实现动态定时任务的非Web项目,可以根据项目启动的参数来决定执行配置文件中的某些任务,并且所有的任务都配置在配置文件中,从而避免建数据库的麻烦。
一、流程说明:
程序启动时注册QuartzConfig配置类(该配置类将读取quartz.properties中的配置、任务信息、初始化调度器、job实例工厂等),然后通过CommandLineRunner的实现类,重写其run()方法,该方法根据启动传入的参数判断哪些配置任务信息需要进行执行,并调用QuartzManager工具类启动相应任务。
二、代码实例:
1.引入相关依赖
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-quartz
org.projectlombok
lombok
provided
cn.hutool
hutool-all
5.1.0
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
true
2.【必须】quartz框架配置文件quartz.properties,主要配置quartz线程池、任务列表等
说明:由于本例采用非Web项目,故application.yml配置文件可以不配。实际业务中可以配置些动态数据源,实现定时任务的业务逻辑
#[非必须]设置调度器实例名称,默认QuartzScheduler
org.quartz.scheduler.instanceName=quartz-scheduler
#[非必须]设置调度器实例ID,默认值NON_CLUSTERED
org.quartz.scheduler.instanceId=AUTO
#[必须]设置线程池实现类(一般使用SimpleThreadPool定长线程池)
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#[必须]设置线程数(无默认值,一般设置为1-100的整数)
org.quartz.threadPool.threadCount=5
#[非必须]设置线程的优先级(最大为9,最小为1,默认为5)
org.quartz.threadPool.threadPriority=5
#[非必须]设置是否为守护线程(默认为true)
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
#[非必须]线程前缀(默认为调度器实例名称_Worker)
org.quartz.threadPool.threadNamePrefix=quartz-scheduler-thread
#[非必须]设置schedule相关信息存储方法(默认存储在内存中)
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
#[非必须]最大能忍受的触发超时时间(默认为60秒)
org.quartz.jobStore.misfireThreshold=60000
##################################配置定时任务##################################
#jobName:任务名称,建议英文或任务描述拼音码首拼[必须]
#jobDesc:任务中文描述[必须]
#classMethod:任务执行的方法[必须]
#jobCron:任务的Cron表达式[必须]
#第1个定时任务
springboot.quartz.scheduledJobs[0].jobName=job1
springboot.quartz.scheduledJobs[0].jobDesc=测试任务1
springboot.quartz.scheduledJobs[0].classMethod=com.wwu.jobs.FirstJob
springboot.quartz.scheduledJobs[0].jobCron=0/10 * * * * ?
#第2个定时任务
springboot.quartz.scheduledJobs[1].jobName=job2
springboot.quartz.scheduledJobs[1].jobDesc=测试任务2
springboot.quartz.scheduledJobs[1].classMethod=com.wwu.jobs.SecondJob
springboot.quartz.scheduledJobs[1].jobCron=0/15 * * * * ?
#第3个定时任务
springboot.quartz.scheduledJobs[2].jobName=job3
springboot.quartz.scheduledJobs[2].jobDesc=测试任务3
springboot.quartz.scheduledJobs[2].classMethod=com.wwu.jobs.ThirdJob
springboot.quartz.scheduledJobs[2].jobCron=0 05 19 ? * 2
3.【非必须】slf4j日志分级切割配置文件logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
${sysLog.pattern}
INFO
ACCEPT
DENY
${sysLog.logDir}/${sysLog.logName}_info.txt
${sysLog.logDir}/${sysLog.logName}_info_%d{yyyyMMdd}_%i.txt
${sysLog.maxHistory}
${sysLog.maxFileSize}
${sysLog.fileCharset}
${sysLog.pattern}
WARN
${sysLog.logDir}/${sysLog.logName}_error.txt
${sysLog.logDir}/${sysLog.logName}_error_%d{yyyyMMdd}_%i.txt
${maxHistory}
${sysLog.maxFileSize}
${sysLog.fileCharset}
${sysLog.pattern}
0
${sysLog.queueSize}
0
${sysLog.queueSize}
4.创建任务实体类ScheduledJob.java,在初始化任务列表时创建任务明细(JobDetail)及触发器(Trigger)
package com.wwu.entity;
import lombok.Data;
/**
* @description: 任务实体类
* @author 一蓑烟雨
* @version 1.0.0
* @date 2023-04-16 16:53
*/
@Data
public class ScheduledJob {
/** 任务ID */
private String jobId;
/** 任务名称 */
private String jobName;
/** 任务描述 */
private String jobDesc;
/** 任务表达式 */
private String jobCron;
/** 任务表达式中文 */
private String jobCronZh;
/** 任务分组编码*/
private String jobGroup;
/** 任务分组名称 */
private String jobGroupName;
/** 执行方法 */
private String classMethod;
/** 任务执行状态(Y,执行成功;N,执行失败;空,未执行) */
private String jobStatus;
/** 任务可用状态(Y:可用;N:不可用) */
private String enabledFlag;
/** 下次执行时间 */
private String nextExecTime;
}
5.创建quartz配置类quartzConfig.java,主要用来配置线程池、调度器、job实例工厂等
package com.wwu.config;
import com.wwu.entity.ScheduledJob;
import lombok.Data;
import org.quartz.Scheduler;
import org.quartz.spi.JobFactory;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
/**
* @description: quartz配置类,配置线程池、调度器、job实例工厂等
* @author 一蓑烟雨
* @version 1.0.0
* @date 2023-04-16 18:08
*/
@Data
@Configuration
@PropertySource("classpath:quartz.properties")
@ConfigurationProperties(prefix = "springboot.quartz")
public class QuartzConfig {
/** 注入quartz.properties配置文件中的任务 */
private List scheduledJobs;
@Resource
private JobFactory jobFactory;
/**
* @description: SchedulerFactoryBean工厂
* @return org.springframework.scheduling.quartz.SchedulerFactoryBean
* @author 一蓑烟雨
* @date 2023/4/16 18:10
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
//创建 SchedulerFactoryBean 实例
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
try {
//设置是否覆盖已存在的job,为True表示有相同名称和分组的触发器和任务存在时则替换
schedulerFactoryBean.setOverwriteExistingJobs(true);
//设置是否自动启动Scheduler
schedulerFactoryBean.setAutoStartup(true);
//设置quartz属性
schedulerFactoryBean.setQuartzProperties(quartzProperties());
//设置任务调度器
schedulerFactoryBean.setJobFactory(jobFactory);
} catch (Exception e) {
e.printStackTrace();
}
return schedulerFactoryBean;
}
/**
* @description: 读取quartz配置文件中配置相关属性
* @return java.util.Properties
* @author 一蓑烟雨
* @date 2023/4/17 17:18
*/
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
//在quartz.properties中的属性被读取并注入后再初始化对象
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
/**
* @description: 初始化schedule任务调度器
* @return org.quartz.Scheduler
* @author 一蓑烟雨
* @date 2023/4/16 18:12
*/
@Bean
public Scheduler scheduler() {
return schedulerFactoryBean().getScheduler();
}
}
6.创建job实例工厂JobFactory.java
package com.wwu.config;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @description: 创建job实例工厂
* @author 一蓑烟雨
* @version 1.0.0
* @date 2023-04-16 18:31
*/
@Component
public class JobFactory extends AdaptableJobFactory {
@Resource
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
//进行注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
7.创建CommandLineRunner接口的实现类QuartzCommandRunner.java,作用为启动项目时预先加载配置文件中的所有定时任务,并根据启动参数启动相应任务
package com.wwu.config;
import com.wwu.entity.ScheduledJob;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @description: 启动项目时预先加载配置文件中的所有定时任务,并根据启动参数启动相应任务
* CommandLineRunner中的Order表示在项目启动时预先加载的类
* @author 一蓑烟雨
* @version 1.0.0
* @date 2023-04-16 18:13
*/
@Slf4j
@Component
@Order(1)
public class QuartzCommandRunner implements CommandLineRunner {
@Resource
private QuartzManager quartzManager;
@Resource
private QuartzConfig quartzConfig;
/**
* @description: 重写run方法
* @param [args]
* @return void
* @author 一蓑烟雨
* @date 2023/4/17 15:37
*/
@Override
public void run(String... args) throws Exception {
try {
//初始化需要启动的任务列表
List jobList = initSchedule(args);
for (ScheduledJob scheduledJob : jobList) {
//执行任务
quartzManager.startJob(scheduledJob);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @description: 初始化需要执行的任务列表
* @param [args]
* @return java.util.List
* @author 一蓑烟雨
* @date 2023/4/17 15:37
*/
public List initSchedule(String... args) throws SchedulerException {
List jobs = new ArrayList();
try {
log.info("...根据参数加载配置文件quartz.properties中任务列表开始...");
//1.遍历启动程序时传入的接口类型参数
String[] typeParam = null;
for (String arg : args) {
if (arg.startsWith("type")){
typeParam = arg.substring(5).split(",");
break;
}
}
//2.遍历quartz.properties配置文件中任务列表
for (ScheduledJob scheduledJob : quartzConfig.getScheduledJobs()) {
//如果启动传入了参数,则按照参数加载启动任务
if (typeParam != null && typeParam.length > 0) {
//遍历参数值和任务列表,若相同则加入到启动任务列表中
Arrays.stream(typeParam).forEach(param -> {
if (param.trim().equals(scheduledJob.getJobName())) {
jobs.add(scheduledJob);
log.info("......" + scheduledJob);
}
});
} else {
//没有启动参数,默认启动配置文件中的所有任务
jobs.add(scheduledJob);
log.info("......" + scheduledJob);
}
}
log.info("......共加载{}条任务需要执行......", jobs.size());
} catch (Exception e) {
e.printStackTrace();
}
log.info("...根据参数加载配置文件quartz.properties中任务列表结束...");
return jobs;
}
}
8.创建quartz定时任务工具类QuartzManager.java,对任务进行管理(创建、启动、修改、删除、暂停)
package com.wwu.config;
import com.wwu.entity.ScheduledJob;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @description: quartz定时任务工具类,对任务进行管理(创建、启动、修改、删除、暂停)
* @author 一蓑烟雨
* @version 1.0.0
* @date 2023-04-16 17:11
*/
@Component
public class QuartzManager {
/** 注入QuartzConfig中定义的任务调度器scheduler */
@Resource
private Scheduler scheduler;
/**
* @description: 获取所有任务信息
* @return java.util.List
* @author 一蓑烟雨
* @date 2023/4/16 18:59
*/
public List getAllJobInfo() throws SchedulerException {
List jobList = new ArrayList();
GroupMatcher matcher = GroupMatcher.anyJobGroup();
Set jobKeys = scheduler.getJobKeys(matcher);
for(JobKey jobKey: jobKeys){
List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
for(Trigger trigger: triggers){
ScheduledJob job = new ScheduledJob();
job.setJobName(jobKey.getName());
job.setJobGroup(jobKey.getGroup());
job.setJobDesc(trigger.getDescription());
Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
job.setJobStatus(triggerState.name());
if(trigger instanceof CronTrigger){
CronTrigger cronTrigger= (CronTrigger) trigger;
job.setJobCron(cronTrigger.getCronExpression());
}
jobList.add(job);
}
}
return jobList;
}
/**
* @description: 获取某个任务的信息
* @param scheduledJob
* @return java.lang.String
* @author 一蓑烟雨
* @date 2023/4/16 18:07
*/
public String getJobInfo(ScheduledJob scheduledJob) throws SchedulerException {
TriggerKey triggerKey = new TriggerKey(scheduledJob.getJobName(), scheduledJob.getJobGroup());
System.out.println("triggerKey:"+triggerKey);
CronTrigger cronTrigger= (CronTrigger) scheduler.getTrigger(triggerKey);
return String.format("time:%s,state:%s",cronTrigger.getCronExpression(),
scheduler.getTriggerState(triggerKey).name());
}
/**
* @description: 获取任务数量
* @return java.lang.String
* @author 一蓑烟雨
* @date 2023/4/16 18:07
*/
public int getJobSize() throws SchedulerException {
GroupMatcher matcher = GroupMatcher.anyJobGroup();
Set jobKeys = scheduler.getJobKeys(matcher);
return jobKeys.size();
}
/**
* @description: 获取触发器状态
* @param scheduledJob
* @return NONE:不存在;NORMAL:正常;PAUSED:暂停;COMPLETE:完成;ERROR:错误;BLOCKED:阻塞
* @author 一蓑烟雨
* @date 2023/4/17 18:56
*/
public String getTriggerState(ScheduledJob scheduledJob) throws SchedulerException {
TriggerKey triggerKey = new TriggerKey(scheduledJob.getJobName(), scheduledJob.getJobGroup());
Trigger.TriggerState triggerState = scheduler.getTriggerState(triggerKey);
return triggerState.name();
}
/**
* @description: 开启某个任务
* @param scheduledJob
* @return void
* @author 一蓑烟雨
* @date 2023/4/16 18:05
*/
public void startJob(ScheduledJob scheduledJob) throws Exception {
JobKey jobKey = JobKey.jobKey(scheduledJob.getJobName(), scheduledJob.getJobGroup());
//不存在则添加任务
if(!scheduler.checkExists(jobKey)){
addJobTask(scheduledJob);
}
//启动
scheduler.start();
}
/**
* @description: 添加某个任务
* @param scheduledJob
* @return void
* @author 一蓑烟雨
* @date 2023/4/16 18:01
*/
public boolean addJobTask(ScheduledJob scheduledJob) throws Exception {
//利用反射机制获取任务执行类
Class<? extends Job> jobClass = (Class<? extends Job>)(Class.forName(scheduledJob.getClassMethod()).newInstance().getClass());
//设置任务明细,调用定义的任务逻辑
JobDetail jobDetail = JobBuilder.newJob(jobClass)
//添加认证信息(也可通过usingJobData传参数)
.withIdentity(scheduledJob.getJobName(), scheduledJob.getJobGroup())
//执行
.build();
//设置任务触发器,cornTrigger规则定义执行规则
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
//通过键值对方式向job实现业务逻辑传参数
.usingJobData("jobDesc",scheduledJob.getJobDesc())
.usingJobData("jobCron",scheduledJob.getJobCron())
//添加认证信息
.withIdentity(scheduledJob.getJobName(), scheduledJob.getJobGroup())
//程序启动后间隔多久开始执行任务
.startAt(DateBuilder.futureDate(5, DateBuilder.IntervalUnit.SECOND))
//执行Cron表达时
.withSchedule(CronScheduleBuilder.cronSchedule(scheduledJob.getJobCron()))
//执行
.build();
//把任务明细和触发器注册到任务调度中
scheduler.scheduleJob(jobDetail, cronTrigger);
return true;
}
/**
* @description: 修改任务的Cron表达式
* @param scheduledJob
* @return boolean
* @author 一蓑烟雨
* @date 2023/4/16 17:46
*/
public boolean modifyJob(ScheduledJob scheduledJob) throws SchedulerException{
TriggerKey triggerKey = new TriggerKey(scheduledJob.getJobName(), scheduledJob.getJobGroup());
CronTrigger cronTrigger= (CronTrigger) scheduler.getTrigger(triggerKey);
String oldTime = cronTrigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(scheduledJob.getJobCron())){
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(scheduledJob.getJobCron());
CronTrigger trigger=TriggerBuilder.newTrigger()
.withIdentity(scheduledJob.getJobName(), scheduledJob.getJobGroup())
.withSchedule(cronScheduleBuilder)
.build();
scheduler.rescheduleJob(triggerKey,trigger);
return true;
}else{
return false;
}
}
/**
* @description: 暂停所有任务
* @return void
* @author 一蓑烟雨
* @date 2023/4/16 17:41
*/
public void pauseAllJob()throws SchedulerException{
scheduler.pauseAll();
}
/**
* @description: 暂停某个任务
* @param scheduledJob
* @return void
* @author 一蓑烟雨
* @date 2023/4/16 17:41
*/
public void pauseJob(ScheduledJob scheduledJob)throws SchedulerException{
JobKey jobKey = JobKey.jobKey(scheduledJob.getJobName(), scheduledJob.getJobGroup());
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if (jobDetail == null) {
return;
}
scheduler.pauseJob(jobKey);
}
/**
* @description: 恢复所有任务
* @return void
* @author 一蓑烟雨
* @date 2023/4/16 17:38
*/
public void resumeAllJob()throws SchedulerException{
scheduler.resumeAll();
}
/**
* @description: 恢复某个任务
* @param scheduledJob
* @return void
* @author 一蓑烟雨
* @date 2023/4/16 17:39
*/
public void resumeJob(ScheduledJob scheduledJob)throws SchedulerException {
JobKey jobKey = JobKey.jobKey(scheduledJob.getJobName(), scheduledJob.getJobGroup());
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if (jobDetail == null) {
return;
}
scheduler.resumeJob(jobKey);
}
/**
* @description: 删除任务
* @param scheduledJob
* @return void
* @author 一蓑烟雨
* @date 2023/4/16 17:32
*/
public void deleteJob(ScheduledJob scheduledJob)throws SchedulerException{
JobKey jobKey = JobKey.jobKey(scheduledJob.getJobName(), scheduledJob.getJobGroup());
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if (jobDetail == null) {
return;
}
scheduler.deleteJob(jobKey);
}
}
8.依次创建quartz中对应任务的job业务类FirstJob.java、SecondJob.java和ThirdJob.java,这些job业务类需要实现Job接口
package com.wwu.jobs;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
/**
* @description: 定义任务逻辑,需要实现Job接口并重写execute()方法
* DisallowConcurrentExecution注解用来避免同一个任务由于执行时间过长导致并发执行
* @author 一蓑烟雨
* @version 1.0.0
* @date 2023-04-16 19:51
*/
@Slf4j
@DisallowConcurrentExecution
public class FirstJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//获取触发器cronTrigger传递的参数
JobDataMap dataMap = context.getTrigger().getJobDataMap();
log.info("【{}】任务执行开始,执行频率为:{}",dataMap.get("jobDesc"),dataMap.get("jobCron"));
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("【{}】任务执行结束",dataMap.get("jobDesc"));
}
}
9.在IDEA开发工具配置参数或将项目打包成jar并通过命令行传参执行(如type=job1,job2),即可看到传入参数对应的任务已开始执行
页面更新:2024-03-12
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号