SpringBoot使用quartz实现定时任务动态配置,实现在后台增删改查数据库中配置的定时任务,服务中实时动态更新
<!-- quartz 任务调用系统-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
代码如下(示例):
import cn.hutool.core.thread.ThreadUtil;
import com.program.maintenance.bean.LogSysQuartz;
import com.program.maintenance.bean.SysQuartzJob;
import com.program.maintenance.dao.LogSysQuartzDao;
import com.program.maintenance.service.InitDataService;
import com.program.maintenance.service.ProceduresConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.concurrent.Future;
@Async
@Slf4j
public class ExecutionJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) {
SysQuartzJob quartzJob = (SysQuartzJob) context.getMergedJobDataMap().get(QuartzManage.JOB_KEY);
// 获取spring bean
LogSysQuartzDao logSysQuartzDao = SpringContextHolder.getBean(LogSysQuartzDao.class);
ProceduresConfigService sysQuartzJobService = SpringContextHolder.getBean(ProceduresConfigService.class);
InitDataService initDataService = SpringContextHolder.getBean(InitDataService.class);
String uuid = String.valueOf(quartzJob.getId());
//这个是定时任务执行日志
LogSysQuartz logSysQuartz = new LogSysQuartz();
logSysQuartz.setJobId(quartzJob.getId());
logSysQuartz.setJobName(quartzJob.getJobName());
logSysQuartz.setBeanName(quartzJob.getBeanName());
logSysQuartz.setMethodName(quartzJob.getMethodName());
logSysQuartz.setParams(quartzJob.getParams());
logSysQuartz.setCronExpression(quartzJob.getCronExpression());
long startTime = System.currentTimeMillis();
try {
// 执行任务
QuartzRunnable task = new QuartzRunnable(quartzJob.getBeanName(), quartzJob.getMethodName(),
quartzJob.getParams());
Future<Object> future = ThreadUtil.execAsync(task);
future.get();
long times = System.currentTimeMillis() - startTime;
logSysQuartz.setTime(times);
if (StringUtils.isNotBlank(uuid)) {
initDataService.putTask(uuid, true);
}
// 任务状态
logSysQuartz.setIsSuccess(1);
} catch (Exception e) {
if (StringUtils.isNotBlank(uuid)) {
initDataService.putTask(uuid, false);
}
long times = System.currentTimeMillis() - startTime;
logSysQuartz.setTime(times);
// 任务状态 1:成功 0:失败
logSysQuartz.setIsSuccess(0);
logSysQuartz.setExceptionDetail(e.getMessage());
log.error("【定时任务】定时任务执行失败 =={} == {}", quartzJob.getJobName(), e.getMessage());
e.printStackTrace();
// 任务如果失败了则暂停
if (quartzJob.getPauseAfterFailure() != null && quartzJob.getPauseAfterFailure() == 1) {
quartzJob.setIsPause(1);
//更新状态
sysQuartzJobService.updateIsPause(quartzJob);
}
} finally {
//新增定时任务执行日志,这个dao层代码我就不贴了
logSysQuartzDao.insertSelective(logSysQuartz);
}
}
代码如下(示例):
import com.program.maintenance.bean.SysQuartzJob;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import static org.quartz.TriggerBuilder.newTrigger;
@Slf4j
@Component
public class QuartzManage {
private static final String JOB_NAME = "TASK_";
public static final String JOB_KEY = "JOB_KEY";
@Resource(name = "scheduler")
private Scheduler scheduler;
public void addJob(SysQuartzJob quartzJob) {
try {
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(ExecutionJob.class).
withIdentity(JOB_NAME + quartzJob.getId()).build();
//通过触发器名和cron 表达式创建 Trigger
Trigger cronTrigger = newTrigger()
.withIdentity(JOB_NAME + quartzJob.getId())
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression()))
.build();
cronTrigger.getJobDataMap().put(JOB_KEY, quartzJob);
//重置启动时间
((CronTriggerImpl)cronTrigger).setStartTime(new Date());
//执行定时任务
scheduler.scheduleJob(jobDetail,cronTrigger);
// 暂停任务
if (quartzJob.getIsPause()==0) {
pauseJob(quartzJob);
}
} catch (Exception e){
log.error("创建定时任务失败:" + quartzJob.getJobName(), e);
}
}
/**
* 更新job cron表达式
* @param quartzJob /
*/
public void updateJobCron(SysQuartzJob quartzJob){
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if(trigger == null){
addJob(quartzJob);
trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
}
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(quartzJob.getCronExpression());
trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
//重置启动时间
((CronTriggerImpl)trigger).setStartTime(new Date());
trigger.getJobDataMap().put(JOB_KEY,quartzJob);
scheduler.rescheduleJob(triggerKey, trigger);
// 暂停任务
if (quartzJob.getIsPause()==1) {
pauseJob(quartzJob);
}
} catch (Exception e){
log.error("更新定时任务失败", e);
}
}
/**
* 删除一个job
* @param quartzJob /
*/
public void deleteJob(SysQuartzJob quartzJob){
try {
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
scheduler.pauseJob(jobKey);
scheduler.deleteJob(jobKey);
} catch (Exception e){
log.error("删除定时任务失败", e);
}
}
/**
* 恢复一个job
* @param quartzJob /
*/
public void resumeJob(SysQuartzJob quartzJob){
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if(trigger == null) {
addJob(quartzJob);
}
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
scheduler.resumeJob(jobKey);
} catch (Exception e){
log.error("恢复定时任务失败", e);
}
}
/**
* 立即执行job
* @param quartzJob /
*/
public void runJobNow(SysQuartzJob quartzJob){
try {
TriggerKey triggerKey = TriggerKey.triggerKey(JOB_NAME + quartzJob.getId());
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
// 如果不存在则创建一个定时任务
if(trigger == null) {
addJob(quartzJob);
}
JobDataMap dataMap = new JobDataMap();
dataMap.put(JOB_KEY, quartzJob);
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
scheduler.triggerJob(jobKey,dataMap);
} catch (Exception e){
log.error("定时任务执行失败", e);
}
}
/**
* 暂停一个job
* @param quartzJob /
*/
public void pauseJob(SysQuartzJob quartzJob){
try {
JobKey jobKey = JobKey.jobKey(JOB_NAME + quartzJob.getId());
scheduler.pauseJob(jobKey);
} catch (Exception e){
log.error("定时任务暂停失败", e);
}
}
}
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
@Slf4j
public class QuartzRunnable implements Callable<Object> {
private final Object target;
private final Method method;
private final String params;
QuartzRunnable(String beanName, String methodName, String params)
throws NoSuchMethodException, SecurityException {
this.target = SpringContextHolder.getBean(beanName);
this.params = params;
if (StringUtils.isNotBlank(params)) {
this.method = target.getClass().getDeclaredMethod(methodName, String.class);
} else {
this.method = target.getClass().getDeclaredMethod(methodName);
}
}
@Override
public Object call() throws Exception {
ReflectionUtils.makeAccessible(method);
if (StringUtils.isNotBlank(params)) {
method.invoke(target, params);
} else {
method.invoke(target);
}
return null;
}
}
import com.program.common.util.CallBack;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Component
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext = null;
private static final List<CallBack> CALL_BACKS = new ArrayList<>();
private static boolean addCallback = true;
/**
* 针对 某些初始化方法,在SpringContextHolder 未初始化时 提交回调方法。
* 在SpringContextHolder 初始化后,进行回调使用
*
* @param callBack 回调函数
*/
public synchronized static void addCallBacks(CallBack callBack) {
if (addCallback) {
SpringContextHolder.CALL_BACKS.add(callBack);
} else {
log.warn("CallBack:{} 已无法添加!立即执行", callBack.getCallBackName());
callBack.executor();
}
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
assertContextInjected();
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
assertContextInjected();
return applicationContext.getBean(requiredType);
}
/**
* 获取SpringBoot 配置信息
*
* @param property 属性key
* @param defaultValue 默认值
* @param requiredType 返回类型
* @return /
*/
public static <T> T getProperties(String property, T defaultValue, Class<T> requiredType) {
T result = defaultValue;
try {
result = getBean(Environment.class).getProperty(property, requiredType);
} catch (Exception ignored) {}
return result;
}
/**
* 获取SpringBoot 配置信息
*
* @param property 属性key
* @return /
*/
public static String getProperties(String property) {
return getProperties(property, null, String.class);
}
/**
* 获取SpringBoot 配置信息
*
* @param property 属性key
* @param requiredType 返回类型
* @return /
*/
public static <T> T getProperties(String property, Class<T> requiredType) {
return getProperties(property, null, requiredType);
}
/**
* 检查ApplicationContext不为空.
*/
private static void assertContextInjected() {
if (applicationContext == null) {
throw new IllegalStateException("applicaitonContext属性未注入, 请在applicationContext" +
".xml中定义SpringContextHolder或在SpringBoot启动类中注册SpringContextHolder.");
}
}
/**
* 清除SpringContextHolder中的ApplicationContext为Null.
*/
private static void clearHolder() {
log.debug("清除SpringContextHolder中的ApplicationContext:"
+ applicationContext);
applicationContext = null;
}
@Override
public void destroy() {
SpringContextHolder.clearHolder();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringContextHolder.applicationContext != null) {
log.warn("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);
}
SpringContextHolder.applicationContext = applicationContext;
if (addCallback) {
for (CallBack callBack : SpringContextHolder.CALL_BACKS) {
callBack.executor();
}
CALL_BACKS.clear();
}
SpringContextHolder.addCallback = false;
}
}
import com.program.maintenance.bean.SysQuartzJob;
import com.program.maintenance.dao.SysQuartzJobDao;
import com.program.maintenance.util.QuartzManage;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Component
@RequiredArgsConstructor
public class JobRunner implements ApplicationRunner {
private static final Logger log = LoggerFactory.getLogger(JobRunner.class);
@Resource
private SysQuartzJobDao sysQuartzJobDao;
@Resource
private QuartzManage quartzManage;
/**
* 项目启动时重新激活启用的定时任务
*
* @param applicationArguments /
*/
@Override
public void run(ApplicationArguments applicationArguments) {
List<SysQuartzJob> quartzJobs = sysQuartzJobDao.getByIsPause(1);
log.info("--------------------注入定时任务中---------------------");
quartzJobs.forEach(quartzManage::addJob);
for (SysQuartzJob quartzJob : quartzJobs) {
log.info("定时任务:" + quartzJob.getJobName());
}
log.info("--------------------定时任务注入完成---------------------");
}
import lombok.extern.slf4j.Slf4j;
import org.quartz.Scheduler;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Component;
@Slf4j
@Configuration
public class QuartzConfig {
/**
* 解决Job中注入Spring Bean为null的问题
*/
@Component("quartzJobFactory")
public static class QuartzJobFactory extends AdaptableJobFactory {
private final AutowireCapableBeanFactory capableBeanFactory;
public QuartzJobFactory(AutowireCapableBeanFactory capableBeanFactory) {
this.capableBeanFactory = capableBeanFactory;
}
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
/**
* 注入scheduler到spring
* @param quartzJobFactory /
* @return Scheduler
* @throws Exception /
*/
@Bean(name = "scheduler")
public Scheduler scheduler(QuartzJobFactory quartzJobFactory) throws Exception {
SchedulerFactoryBean factoryBean=new SchedulerFactoryBean();
factoryBean.setJobFactory(quartzJobFactory);
factoryBean.afterPropertiesSet();
Scheduler scheduler=factoryBean.getScheduler();
scheduler.start();
return scheduler;
}
}
这部分的表结构映射实体也可以根据自己的需要去进行修改,不一定要和我一样;
/**
* 定时任务执行日志表
*/
@Data
public class LogSysQuartz {
/**
* id(系统定时任务日志)
*/
private Integer id;
/**
* 系统定时任务id
*/
private Integer jobId;
/**
* Spring Bean名称
*/
private String beanName;
/**
* 创建时间
*/
private String createTime;
/**
* cron 表达式
*/
private String cronExpression;
/**
* 异常详情
*/
private String exceptionDetail;
/**
* 是否成功(0否1是)
*/
private Integer isSuccess;
/**
* 任务名称
*/
private String jobName;
/**
* 方法名称
*/
private String methodName;
/**
* 参数
*/
private String params;
/**
* 执行时间
*/
private Long time;
private Integer sysType;
}
/**
* 定时任务配置表
*/
@Data
public class SysQuartzJob{
/**
* id(系统定时任务表)
*/
private Integer id;
/**
* Spring Bean名称
*/
private String beanName;
/**
* cron 表达式
*/
private String cronExpression;
/**
* 是否暂停状态:1是、0否
*/
private Integer isPause;
/**
* 任务名称
*/
private String jobName;
/**
* 方法名称
*/
private String methodName;
/**
* 参数
*/
private String params;
/**
* 任务失败后是否暂停(0否1是)
*/
private Integer pauseAfterFailure;
/**
* 备注
*/
private String remark;
/**
* 删除标志(0正常1删除)
*/
private Integer del;
}
@Service
@Slf4j
public class InitDataService {
/**
* 任务执行状态 Map<任务id,是否正常执行>
*/
private static final Map<String, Boolean> TASK_MAP = new ConcurrentHashMap<>();
/**
* 设置任务状态
*
* @param uuid uuid
* @param status 任务运行状态
*/
public void putTask(String uuid, boolean status) {
TASK_MAP.put(uuid, status);
}
/**
* 获取任务状态
*
* @param uuid uuid
* @return true正常,false异常
*/
public Boolean getTask(String uuid) {
return TASK_MAP.get(uuid);
}
/**
* 删除任务状态
*
* @param uuid uuid
*/
public void delTask(String uuid) {
TASK_MAP.remove(uuid);
}
}
启动类上记得加上这几个注解;
@SpringBootApplication
@EnableAsync
@EnableTransactionManagement
@EnableScheduling
public class MaintenanceApplication {
public static void main(String[] args) {
SpringApplication.run(MaintenanceApplication.class, args);
}
}
@Slf4j
@Component
public class WeChatAccessTokenTask {
public void updateWxAccessToken(){
log.info("定时更新微信Token执行");
}
}
这里需要注意下:因为本文使用的是映射机制,所以注意下类名称和方法名称,下方是我的数据库配置的示例,可以参考下,包名称和类名称不要大写
该用到的代码都贴上了,有啥不清楚的可以下方评论,有看到的都会及时回复