Gitee仓库
https://gitee.com/Lin_DH/system
介绍
实现方式
java定时任务调度的实现方式:Timer,ScheduledExecutor,Spring Scheduler,JCron Tab,Quartz 等。
Quartz
Quartz是一个由Java开发的开源项目,它可以与J2EE、J2SE应用程序相结合也可以单独使用。
Quartz是一个定时调度框架,用来执行定时任务(某个时间出发执行某个动作)。
官网
https://www.quartz-scheduler.org/
核心概念
Scheduler:调度器,负责进行任务的调度与管理。Scheduler可以启动、停止、暂停、恢复任务的执行,还可以配置任务的触发条件和执行计划。
Trigger:触发器,负责定义任务的触发条件,即何时触发任务执行。一个Job可以关联一个或多个Trigger,根据时间表达式或特定的时间间隔来配置触发器。
TriggerBuilder:触发器构建器,用来创建触发器的实例。
Job:业务组件,需要被调度任务执行的具体事件。需要将Job注册到Scheduler中,调度器会调用Job接口的execute方法完成具体的业务实现。
JobDetail:任务详情,JobDetail是与Job相关联的详细信息,包括Job名称、所属的Job类、Job的身份标识等。
JobBuilder:任务构建器,用来创建JobDetail实例。
JobStore:任务存储,Quartz的持久化机制,负责将任务和调度相关的信息存储到数据库或其他存储介质中。即使应用程序重启或服务器关闭,已经配置的调度任务仍然可以保留。
Listener:监听器,Quartz提供了丰富的监听器接口,可以监控任务的状态变化、执行情况、异常事件。通过实现监听器接口,可以在任务执行前后、暂停、恢复、出错等情况下执行额外的逻辑。
ThreadPool:线程池,Scheduler使用线程池来并发执行任务,提高任务的处理效率。允许配置线程池的大小、类型、特性,以适应不同的负载情况。

三大核心组件
Scheduler(调度器)、Trigger(触发器)、Job(任务)
持久化方式
Quartz提供了两种持久化方式
1)RAMJobStore:在默认情况下,Quartz会将任务调度的运行信息保存在内存中,这种方法提供了最佳的性能。不足之处是缺乏数据的持久性,当程序停止或崩溃时,所有运行的信息都会丢失。
2)JobStoreTX:分布式方式一般采用此中方式,持久化到数据库中。所有的任务信息都会保存到数据库中,可以控制事务,如果应用程序或服务器关闭或重启时,已经保存到数据库中的任务信息不会丢失,并且可以恢复继续执行。
依赖
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- 读取yml配置文件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
方式1(简单)
需求
实现固定的一个或几个Job的定时任务的效果。
效果图

代码实现
ScheduleConfig.java
package com.lm.system.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author DUHAOLIN
* @date 2024/8/12
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "schedule")
public class ScheduleConfig {
private String cron;
}
application.yml
schedule:
cron: 0/5 * * * * ? #5s执行一次
Task.java
package com.lm.system.job;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
/**
* 具体的任务类
* @author DUHAOLIN
* @date 2024/8/12
*/
@Slf4j
public class Task {
public void execute(int arg) {
int i = new Random().nextInt(arg);
log.info("执行具体任务,{}", i);
}
}
TaskSchedule.java
package com.lm.system.job;
import com.lm.system.config.ScheduleConfig;
import lombok.extern.slf4j.Slf4j;
import org.quartz.JobDetail;
import org.quartz.Trigger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.annotation.Resource;
/**
* @author DUHAOLIN
* @date 2024/8/12
*/
@Slf4j
@Configuration
public class TaskSchedule {
@Resource
private ScheduleConfig scheduleConfig;
@Bean
public SchedulerFactoryBean schedulerFactoryBean(Trigger taskTrigger) {
log.info("注册调度器");
SchedulerFactoryBean factoryBean = new SchedulerFactoryBean();
factoryBean.setTriggers(taskTrigger); //注册触发器
return factoryBean;
}
@Bean
public CronTriggerFactoryBean taskTrigger(JobDetail taskJob) {
log.info("注册触发器");
CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
bean.setJobDetail(taskJob);
bean.setCronExpression(scheduleConfig.getCron());
return bean;
}
@Bean
public MethodInvokingJobDetailFactoryBean taskJob() {
log.info("注册任务");
MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
bean.setConcurrent(false); //同一Job不允许并发执行
bean.setTargetObject(new Task()); //目标方法所在的实例对象
bean.setTargetMethod("execute"); //需要执行的方法
bean.setArguments(100); //需要执行的方法的入参
return bean;
}
}
项目目录结构图

方式2(按配置文件配置加载)
参考链接
Spring+Quartz 实现手动开关定时器(https://www.jianshu.com/p/2b21cbb1dcac)
从0到1搭建SpringBoot整合Quartz定时任务框架(https://blog.csdn.net/HJW_233/article/details/131427247?spm=1001.2014.3001.5501)
需求
实现按配置文件的配置启动一个或多个定时任务(WxJob,AlipayJob,OctJob),并且可以通过浏览器或访问Swagger对Job进行增删改查和暂停。
效果图

代码实现
application.yml
schedule:
cron: 0/5 * * * * ? #5s执行一次
wxFlag: true
alipayFlag: true
octFlag: false
SpringUtil.java
package com.lm.system.util;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
/**
* @author DUHAOLIN
* @date 2024/8/16
*/
@Component
public final class SpringUtil implements BeanFactoryPostProcessor {
/**
* Spring应用上下文环境
*/
private static ConfigurableListableBeanFactory beanFactory;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SpringUtil.beanFactory = beanFactory;
}
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
return (T) beanFactory.getBean(name);
}
}
JobType.java
package com.lm.system.common.enums;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author DUHAOLIN
* @date 2024/8/14
*/
public enum JobType {
WxJob("wxJob"),
AlipayJob("alipayJob"),
OctJob("octJob")
;
JobType(String name) {
this.name = name;
}
private final static List<String> jobNames = Arrays.stream(JobType.values()).map(JobType::getName).collect(Collectors.toList());
private final String name;
public String getName() {
return name;
}
public static List<String> getJobNames() {
return jobNames;
}
}
注:想要让application.yml中的 schedule 下的属性跳转到ScheduleConfig类,需要重新编译程序。(spring-boot-configuration-processor依赖的作用)
ScheduleConfig.java
package com.lm.system.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author DUHAOLIN
* @date 2024/8/12
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "schedule")
public class ScheduleConfig {
private String cron;
private boolean wxFlag; //是否开启该类型定时任务的标志
private boolean alipayFlag;
private boolean octFlag;
}
WxJob.java
package com.lm.system.schedule.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
/**
* @author DUHAOLIN
* @date 2024/8/14
*/
@Slf4j
@Component
public class WxJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
log.info("wxJob.execute(context)");
}
}
AlipayJob.java
package com.lm.system.schedule.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
/**
* @author DUHAOLIN
* @date 2024/8/14
*/
@Slf4j
@Component
public class AlipayJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
log.info("alipayJob.execute(context)");
}
}
OctJob.java
package com.lm.system.schedule.job;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
/**
* @author DUHAOLIN
* @date 2024/8/14
*/
@Slf4j
@Component
public class OctJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
log.info("octJob.execute(context)");
}
}
Constants.java
package com.lm.system.common;
/**
* @author DUHAOLIN
* @date 2024/8/16
*/
public class Constants {
public static final String TASK_PARAMS = "PARAMS";
/**
* 默认
*/
public static final String MISFIRE_DEFAULT = "0";
/**
* 立即执行
*/
public static final String MISFIRE_IGNORE_MISFIRES = "1";
/**
* 执行一次
*/
public static final String MISFIRE_FIRE_AND_PROCEED = "2";
/**
* 放弃执行
*/
public static final String MISFIRE_DO_NOTHING = "3";
}
CustomJob.java
package com.lm.system.common;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author DUHAOLIN
* @date 2024/8/14
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CustomJob {
private String jobId;
private String jobName;
private String jobGroup;
/**
* 目标bean名
*/
private String beanTarget;
/**
* 目标bean的方法名
*/
private String beanMethodTarget;
/**
* 执行表达式
*/
private String cronExpression;
/**
* 是否并发
* 0:不允许并发执行
* 1:允许并发执行
*/
private String concurrent;
/**
* 计划策略
*/
private String misfirePolicy = Constants.MISFIRE_DEFAULT;
}
AbstractJob.java
package com.lm.system.common;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.lang.reflect.InvocationTargetException;
/**
* 采用了设计模式中的模板方法模式
* @author DUHAOLIN
* @date 2024/8/16
*/
public abstract class AbstractJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
try {
doExecute(context);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected abstract void doExecute(JobExecutionContext jobExecutionContext) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException;
}
JobExecuteUtil.java
package com.lm.system.util;
import com.lm.system.common.CustomJob;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import com.lm.system.common.Constants;
import org.springframework.beans.BeanUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author DUHAOLIN
* @date 2024/8/16
*/
public class JobExecuteUtil {
public static void execute(JobExecutionContext context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Object params = context.getMergedJobDataMap().get(Constants.TASK_PARAMS);
CustomJob job = new CustomJob();
BeanUtils.copyProperties(params, job); //将params的数据拷贝到job
Job bean = SpringUtil.getBean(job.getBeanTarget());
Method method = bean.getClass().getMethod(job.getBeanMethodTarget(), JobExecutionContext.class);
method.invoke(bean, context);
}
}
QuartzJobExecution.java
package com.lm.system.schedule.execution;
import com.lm.system.common.AbstractJob;
import com.lm.system.util.JobExecuteUtil;
import org.quartz.JobExecutionContext;
import java.lang.reflect.InvocationTargetException;
/**
* @author DUHAOLIN
* @date 2024/8/16
*/
public class QuartzJobExecution extends AbstractJob {
@Override
protected void doExecute(JobExecutionContext context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
JobExecuteUtil.execute(context);
}
}
QuartzDisallowConcurrentExecution.java
package com.lm.system.schedule.execution;
import com.lm.system.common.AbstractJob;
import com.lm.system.util.JobExecuteUtil;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import java.lang.reflect.InvocationTargetException;
/**
* @author DUHAOLIN
* @date 2024/8/16
*/
@DisallowConcurrentExecution //禁止并发执行
public class QuartzDisallowConcurrentExecution extends AbstractJob {
@Override
protected void doExecute(JobExecutionContext context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
JobExecuteUtil.execute(context);
}
}
ScheduleUtil.java
package com.lm.system.util;
import com.lm.system.common.Constants;
import com.lm.system.common.CustomJob;
import com.lm.system.schedule.execution.QuartzDisallowConcurrentExecution;
import com.lm.system.schedule.execution.QuartzJobExecution;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
/**
* @author DUHAOLIN
* @date 2024/8/16
*/
@Slf4j
public class ScheduleUtil {
public static void createJob(Scheduler scheduler, CustomJob job) throws SchedulerException {
log.info("注册{}", job.getJobName());
//获取任务类型
Class<? extends Job> jobClass = getJobClass(job);
//构建Job
JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(job.getJobName(), job.getJobGroup()).build();
//创建表达式调度器构造器
CronScheduleBuilder builder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
//配置执行策略
builder = handlerCronScheduleMisfirePolicy(job, builder);
//创建触发器
String triggerName = job.getJobName().replace("Job", "Trigger");
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerName, job.getJobGroup())
.withSchedule(builder).build();
//配置参数,运行时可获取
jobDetail.getJobDataMap().put(Constants.TASK_PARAMS, job);
//执行调度任务
scheduler.scheduleJob(jobDetail, trigger);
}
private static Class<? extends Job> getJobClass(CustomJob job) {
boolean isConcurrent = "1".equals(job.getConcurrent());
return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
}
/**
* 配置定时任务策略
*/
private static CronScheduleBuilder handlerCronScheduleMisfirePolicy(CustomJob job, CronScheduleBuilder builder) {
switch (job.getMisfirePolicy())
{
case Constants.MISFIRE_DEFAULT:
return builder;
case Constants.MISFIRE_IGNORE_MISFIRES:
return builder.withMisfireHandlingInstructionIgnoreMisfires();
case Constants.MISFIRE_FIRE_AND_PROCEED:
return builder.withMisfireHandlingInstructionFireAndProceed();
case Constants.MISFIRE_DO_NOTHING:
return builder.withMisfireHandlingInstructionDoNothing();
default:
throw new RuntimeException("策略异常");
}
}
}
JobSchedule.java
package com.lm.system.schedule;
import com.lm.system.common.Constants;
import com.lm.system.common.CustomJob;
import com.lm.system.common.enums.JobType;
import com.lm.system.config.ScheduleConfig;
import com.lm.system.util.ScheduleUtil;
import com.lm.system.util.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import javax.annotation.Resource;
import java.util.*;
/**
* @author DUHAOLIN
* @date 2024/8/16
*/
@Slf4j
@Configuration
public class JobSchedule {
private Scheduler scheduler;
@Resource
private ScheduleConfig scheduleConfig;
/**
* 注册默认调度器
*/
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
log.info("注册调度器");
return new SchedulerFactoryBean();
}
public void setScheduler() {
scheduler = SpringUtil.getBean("schedulerFactoryBean");
}
@Bean
public void initByYml() throws SchedulerException {
setScheduler();
scheduler.clear(); //清空定时任务
List<CustomJob> jobs = getJobs(JobType.getJobNames());
for (CustomJob job : jobs) {
if (scheduleConfig.isWxFlag() && job.getJobName().startsWith("wx")) {
ScheduleUtil.createJob(scheduler, job);
} else if (scheduleConfig.isAlipayFlag() && job.getJobName().startsWith("alipay")) {
ScheduleUtil.createJob(scheduler, job);
} else if (scheduleConfig.isOctFlag() && job.getJobName().startsWith("oct")) {
ScheduleUtil.createJob(scheduler, job);
}
}
}
/**
* 获取需要注册的Job
*/
private List<CustomJob> getJobs(List<String> jobNames) {
List<CustomJob> jobs = new ArrayList<>();
for (String jobName : jobNames) {
CustomJob job = CustomJob.builder()
.jobId(UUID.randomUUID().toString())
.jobName(jobName)
.jobGroup("DEFAULT")
.concurrent("1")
.cronExpression(scheduleConfig.getCron())
.beanTarget(jobName)
.beanMethodTarget("execute")
.misfirePolicy(Constants.MISFIRE_DEFAULT)
.build();
jobs.add(job);
}
return jobs;
}
}
JobException.java
package com.lm.system.exception;
/**
* @author DUHAOLIN
* @date 2024/8/16
*/
public class JobException extends RuntimeException {
public JobException() {
super();
}
public JobException(String message) {
super(message);
}
public JobException(String message, Throwable cause) {
super(message, cause);
}
}
ScheduleController.java
package com.lm.system.controller;
import com.lm.system.common.CustomJob;
import com.lm.system.common.ResultBody;
import com.lm.system.common.enums.JobType;
import com.lm.system.config.ScheduleConfig;
import com.lm.system.exception.JobException;
import com.lm.system.util.ScheduleUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @author DUHAOLIN
* @date 2024/8/13
*/
@Slf4j
@RestController
@Api(tags = "定时任务调度接口")
@RequestMapping("schedule")
public class ScheduleController {
@Resource
private Scheduler scheduler;
@Resource
private ScheduleConfig scheduleConfig;
@GetMapping("jobType")
@ApiOperation("查询拥有的任务类型")
public String jobType() {
List<String> jobNames = JobType.getJobNames();
return ResultBody
.build(HttpStatus.OK)
.setData(jobNames)
.setCount(jobNames.size())
.getReturn();
}
/**
* 查询当前启动的Job
*/
@GetMapping("job")
@ApiOperation("查询当前启动的Job")
public String job() throws SchedulerException {
log.info("查询当前启动的Job");
return getJobByState(Trigger.TriggerState.NORMAL);
}
/**
* 任务创建或更新(未存在的就创建,已存在的则更新)
* @param cron 非必填参数,默认为配置文件中的cron
*/
@GetMapping("insertOrUpdate")
@ApiOperation("任务创建或更新")
public String insertOrUpdate(String jobName, @RequestParam(required = false, value = "cron") String cron) throws SchedulerException {
checkJobName(jobName);
cron = cron == null ? scheduleConfig.getCron() : cron;
CustomJob job = new CustomJob();
job.setJobName(jobName);
job.setCronExpression(cron);
log.info("任务创建与更新, jobName:{}, cron: {}", jobName, cron);
//获取触发器标识
TriggerKey triggerKey = getTriggerKey(jobName);
//获取触发器
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (null == trigger) {
try {
ScheduleUtil.createJob(scheduler, job);
} catch (ObjectAlreadyExistsException e) {
throw new ObjectAlreadyExistsException("任务已存在");
}
} else { //存在该任务,进行修改
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
trigger = trigger.getTriggerBuilder()
.withIdentity(triggerKey)
.withSchedule(cronScheduleBuilder)
.build();
//按新的trigger重新配置job执行
scheduler.rescheduleJob(triggerKey, trigger);
}
return ResultBody
.build(HttpStatus.OK)
.getReturn();
}
@GetMapping("delete")
@ApiOperation("删除任务")
public String delete(String jobName) throws SchedulerException {
log.info("删除任务, jobName:{}", jobName);
JobKey jobKey = JobKey.jobKey(jobName);
scheduler.deleteJob(jobKey);
return ResultBody
.build(HttpStatus.OK)
.getReturn();
}
/**
* jobName:任务名称(需要包含JobType中的类型)
*/
private void checkJobName(String jobName) {
boolean b = JobType.getJobNames().contains(jobName);
if (b) return;
throw new JobException("jobName参数有误,该Job类型不存在");
}
private String getJobByState(Trigger.TriggerState triggerState) throws SchedulerException {
Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.anyJobGroup());
List<CustomJob> results = new ArrayList<>();
for (JobKey jobKey : jobKeys) {
String triggerName = jobKey.getName().replace("Job", "Trigger");
TriggerKey triggerKey = getTriggerKey(triggerName);
Trigger.TriggerState state = scheduler.getTriggerState(triggerKey);
if (state == triggerState) {
CronTrigger trigger = (CronTriggerImpl) scheduler.getTrigger(triggerKey);
CustomJob job = CustomJob.builder()
.jobName(jobKey.getName())
.jobGroup(jobKey.getGroup())
.cronExpression(trigger.getCronExpression())
.build();
results.add(job);
}
}
return ResultBody
.build(results.size() == 0 ? HttpStatus.NO_CONTENT : HttpStatus.OK)
.setData(results)
.setCount(results.size())
.getReturn();
}
private TriggerKey getTriggerKey(String jobName) {
return TriggerKey.triggerKey(jobName.replace("Job", "Trigger"));
}
}
项目目录结构图

3万+

被折叠的 条评论
为什么被折叠?



