动态定时任务
利用spring的ThreadPoolTaskScheduler任务类,动态执行定时任务
支持定时任务统一数据库管理,修改cron表达式立即生效。Web页面支持添加、修改、删除、立即执行一次、暂停任务、启动任务
ThreadPoolTaskSchedulerConfig
import com.alibaba.fastjson.JSONObject;
import com.hikvision.bjicdcg.repository.CronEntityRepository;
import com.hikvision.bjicdcg.repository.entity.CronEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
// 备注:需要把启动类的@EnableScheduling关掉,保证定时任务入口只有这一个,避免互相影响
@Configuration
@Slf4j
public class ThreadPoolTaskSchedulerConfig implements ApplicationRunner,ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**存放任务创建返回的ScheduledFuture*/
private static Map<String, ScheduledFuture> scheduleTaskFutureMap = new ConcurrentHashMap<String, ScheduledFuture>();
/**存放任务对应的Runnable*/
private static Map<String, Runnable> scheduleTaskRunableMap = new ConcurrentHashMap<String, Runnable>();
/**存放任务的运行状态,避免任务重复执行,在定时任务侧保证任务的可重复执行*/
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
@Autowired
private CronEntityRepository cronEntityRepository;
@Override
public void run(ApplicationArguments args) throws Exception {
log.debug("初始化ThreadPoolTaskScheduler");
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(20);
executor.setThreadNamePrefix("ThreadPoolTaskScheduler-task-");
executor.setWaitForTasksToCompleteOnShutdown(true);任务完成再关闭线程池
executor.setAwaitTerminationSeconds(60);//设置任务等待时间,如果超过该值还没有销毁就强制销毁,确保最后关闭
executor.initialize();
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
defaultListableBeanFactory.registerSingleton("threadPoolTaskScheduler",executor);
this.init(applicationContext);
}
//初始化启动定时任务
public void init(ApplicationContext applicationContext){
this.threadPoolTaskScheduler = applicationContext.getBean("threadPoolTaskScheduler", ThreadPoolTaskScheduler.class);
List<CronEntity> enableTaskList = cronEntityRepository.findAll();
log.info("所有待注册的启用定时任务信息:{}",JSONObject.toJSON(enableTaskList));
AtomicInteger failCount = new AtomicInteger(0);
AtomicInteger skipCount = new AtomicInteger(0);
if (ObjectUtils.isNotEmpty(enableTaskList)){
for (CronEntity scheduleTaskManager : enableTaskList) {
String declaringClass = scheduleTaskManager.getClassName();
String methodName = scheduleTaskManager.getMethodName();
String scheduleCron = scheduleTaskManager.getCorn();
String cronTaskKey = declaringClass + "#" + methodName;
if (scheduleTaskFutureMap.containsKey(cronTaskKey)){
skipCount.addAndGet(1);
log.info("定时任务已存在,跳过.定时任务信息:{}",JSONObject.toJSON(scheduleTaskManager));
continue;
}
try {
this.scheduleTask(declaringClass,methodName,scheduleCron);
} catch (Exception e) {
failCount.addAndGet(1);
log.error("定时任务动态添加异常:动态任务信息:{},堆栈信息:{}",JSONObject.toJSON(scheduleTaskManager),e);
}
}
}
log.info("所有启动定时任务注册完成,总条数:{},失败条数:{},跳过条数:{}",enableTaskList.size(),failCount.get(),skipCount.get());
}
//创建定时任务
public void scheduleTask(String declaringClass,String methodName,String scheduleCron ) throws Exception{
String cronTaskKey = declaringClass + "#" + methodName;
Class<?> scheduleTaskClass = Class.forName(declaringClass);
Method scheduleTaskMethod = null;
scheduleTaskMethod = scheduleTaskClass.getMethod(methodName);
Object scheduleTaskBean = applicationContext.getBean(scheduleTaskClass);
//参考spring的模式创建ScheduledMethodRunnable,参考:org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.processScheduled
Method method = AopUtils.selectInvocableMethod(scheduleTaskMethod, scheduleTaskBean.getClass());
Runnable scheduleRunnable = new ScheduledMethodRunnable(scheduleTaskBean, method);
synchronized (this){
if (!scheduleTaskFutureMap.containsKey(cronTaskKey)){
ScheduledFuture<?> scheduledFuture = threadPoolTaskScheduler.schedule(scheduleRunnable, new CronTrigger(scheduleCron));
scheduleTaskFutureMap.put(cronTaskKey,scheduledFuture);
scheduleTaskRunableMap.put(cronTaskKey,scheduleRunnable);
}else{
log.info("class:{},method:{},cron:{}任务已存在。。。",declaringClass,methodName,scheduleCron);
}
}
}
//获取所有的任务Key
public Set<String> obtainScheduleTaskKeyAll(){
if (ObjectUtils.isNotEmpty(scheduleTaskFutureMap)){
Set<String> taskKeySet = scheduleTaskFutureMap.keySet();
return taskKeySet;
}else {
return new HashSet<>(0);
}
}
//获取所有的任务ScheduledFuture
public List<ScheduledFuture> obtainScheduleFutureAll() {
if (ObjectUtils.isNotEmpty(scheduleTaskFutureMap)){
List<ScheduledFuture> scheduledFutureList = new ArrayList<>(scheduleTaskFutureMap.size());
Set<String> scheduleFutureKeySet = scheduleTaskFutureMap.keySet();
for (String scheduleFutureKey : scheduleFutureKeySet) {
ScheduledFuture scheduledFuture = scheduleTaskFutureMap.get(scheduleFutureKey);
scheduledFutureList.add(scheduledFuture);
}
return scheduledFutureList;
}else{
return new ArrayList<>(0);
}
}
//根据scheduleTaskKey获取指定的任务ScheduledFuture
public ScheduledFuture obtainScheduleFutureByTaskKey(String scheduleTaskKey) {
if (ObjectUtils.isNotEmpty(scheduleTaskFutureMap)){
return scheduleTaskFutureMap.get(scheduleTaskKey);
}else{
return null;
}
}
//获取所有的任务Runnable
public List<Runnable> obtainScheduleRunableAll() {
if (ObjectUtils.isNotEmpty(scheduleTaskRunableMap)){
List<Runnable> scheduledRunableList = new ArrayList<>(scheduleTaskRunableMap.size());
Set<String> scheduleRunableKeySet = scheduleTaskRunableMap.keySet();
for (String scheduleRunableKey : scheduleRunableKeySet) {
Runnable runnable = scheduleTaskRunableMap.get(scheduleRunableKey);
scheduledRunableList.add(runnable);
}
return scheduledRunableList;
}else{
return new ArrayList<>(0);
}
}
//根据scheduleTaskKey获取指定的任务Runnable
public Runnable obtainScheduleRunableByTaskKey(String scheduleTaskKey) {
if (ObjectUtils.isNotEmpty(scheduleTaskRunableMap)){
return scheduleTaskRunableMap.get(scheduleTaskKey);
}else{
return null;
}
}
//测试任务重复执行,会重新启动一个新的定时任务,所以需要限制,保证任务只有一个
@Deprecated
public void testTaskRepeat() throws Exception {
String declareClass = "com.hikvision.bjicdcg.task.TaskConfig";
String method = "testTask";
String scheduleCron = "0/10 * * * * ?";
this.scheduleTask(declareClass,method,scheduleCron);
}
//TODO 立即执行,如何判断正常是都正在同步。isDone不好使
//com.hikvision.bjicdcg.task.TaskConfig#testTask
//立即执行定时任务
public void executeTaskNow(String scheduleTaskKey) {
//立即执行,先停止,执行,再启用
Runnable runnable = this.obtainScheduleRunableByTaskKey(scheduleTaskKey);
threadPoolTaskScheduler.execute(runnable);
}
//删除定时任务
public void removeScheduleTask(String scheduleTaskKey) throws Exception {
ScheduledFuture scheduledFuture = scheduleTaskFutureMap.get(scheduleTaskKey);
synchronized (this){
scheduledFuture.cancel(false);//false保证当前任务执行完成,不会立即中断
scheduleTaskFutureMap.remove(scheduleTaskKey);
scheduleTaskRunableMap.remove(scheduleTaskKey);
}
}
//修改定时任务的执行周期
public void modifyScheduleTaskExecutePeriod(String scheduleTaskKey,String scheduleCron) throws Exception {
//先停止,再启用
this.removeScheduleTask(scheduleTaskKey);
String[] scheduleTaskKeySplit = scheduleTaskKey.split("#");
this.scheduleTask(scheduleTaskKeySplit[0],scheduleTaskKeySplit[1],scheduleCron);
}
/* public void executeOnceTask(String className, String methodName) throws Exception{
Class<?> scheduleTaskClass = Class.forName(className);
Method scheduleTaskMethod = scheduleTaskClass.getMethod(methodName, null);
Object scheduleTaskBean = applicationContext.getBean(scheduleTaskClass);
//参考spring的模式创建ScheduledMethodRunnable,参考:org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.processScheduled
Method method = AopUtils.selectInvocableMethod(scheduleTaskMethod, scheduleTaskBean.getClass());
method.invoke(scheduleTaskBean);
}*/
public void startTask(String cronTaskKey,String cron) throws Exception {
String[] scheduleTaskKeySplit = cronTaskKey.split("#");
this.scheduleTask(scheduleTaskKeySplit[0],scheduleTaskKeySplit[1],cron);
}
}
CronController
import com.hikvision.bjicdcg.domain.param.TaskParam;
import com.hikvision.bjicdcg.domain.vo.PageVo;
import com.hikvision.bjicdcg.domain.vo.ResponseVO;
import com.hikvision.bjicdcg.repository.entity.CronEntity;
import com.hikvision.bjicdcg.web.service.ITaskService;
import com.hikvision.starfish.common.errorcode.CommonErrorCodeEnum;
import com.hikvision.starfish.core.response.business.BaseResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
/**
* @version 1.0
* @desc
* @author: wangxiaogang10
* @date: 2022/11/7 9:53
*/
@RestController
@Api(value = "定时任务相关接口",tags = {"定时任务相关接口"},description = "定时任务相关接口")
@Slf4j
@RequestMapping("/cron")
public class CronController {
@Autowired
private ITaskService taskService;
@PostMapping("/task/getPage")
@ApiOperation(value = "获取任务列表", httpMethod = "POST")
public BaseResponse<PageVo<CronEntity>> getPageTask(@Valid @RequestBody TaskParam param)throws Exception{
PageVo<CronEntity> page=taskService.getPageTask(param);
return ResponseVO.success(page);
}
@PostMapping("/task/add")
@ApiOperation(value = "添加任务", httpMethod = "POST")
public BaseResponse<CronEntity> addTask(@Valid @RequestBody CronEntity param) throws Exception {
CronEntity cronEntity= taskService.addTask(param);
return ResponseVO.success(cronEntity);
}
@PostMapping("/task/upd")
@ApiOperation(value = "修改任务", httpMethod = "POST")
public BaseResponse<CronEntity> updTask(@Valid @RequestBody CronEntity param)throws Exception{
CronEntity cronEntity= taskService.updTask(param);
return ResponseVO.success(cronEntity);
}
@GetMapping("/task/del")
@ApiOperation(value = "删除任务", httpMethod = "GET")
public BaseResponse deleteTask(@Valid @NotNull @RequestParam Integer id)throws Exception{
taskService.deleteTask(id);
return ResponseVO.success(null);
}
@GetMapping("/task/execute/once")
@ApiOperation(value = "立即执行一次", httpMethod = "GET")
public BaseResponse executeOnceTask(@Valid @NotNull @RequestParam Integer id)throws Exception{
taskService.executeOnceTask(id);
return ResponseVO.success(null);
}
@GetMapping("/task/stop")
@ApiOperation(value = "暂停任务", httpMethod = "GET")
public BaseResponse stopTask(@Valid @NotNull @RequestParam Integer id)throws Exception{
taskService.stopTask(id);
return ResponseVO.success(null);
}
@GetMapping("/task/start")
@ApiOperation(value = "启动任务", httpMethod = "GET")
public BaseResponse startTask(@Valid @NotNull @RequestParam Integer id)throws Exception{
taskService.startTask(id);
return ResponseVO.success(null);
}
}
TaskServiceImpl
package com.hikvision.bjicdcg.web.service.impl;
import com.hikvision.bjicdcg.constant.CustomException;
import com.hikvision.bjicdcg.domain.param.TaskParam;
import com.hikvision.bjicdcg.domain.vo.PageVo;
import com.hikvision.bjicdcg.repository.CronEntityRepository;
import com.hikvision.bjicdcg.repository.entity.CronEntity;
import com.hikvision.bjicdcg.utils.JpaUtil;
import com.hikvision.bjicdcg.web.config.ThreadPoolTaskSchedulerConfig;
import com.hikvision.bjicdcg.web.service.ITaskService;
import com.hikvision.starfish.common.errorcode.CommonErrorCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import javax.persistence.criteria.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* @version 1.0
* @desc
* @author: wangxiaogang10
* @date: 2022/11/7 10:05
*/
@Service
@Slf4j
public class TaskServiceImpl implements ITaskService {
@Autowired
private CronEntityRepository cronEntityRepository;
@Autowired
private ThreadPoolTaskSchedulerConfig threadPoolTaskSchedulerConfig;
@Override
public PageVo<CronEntity> getPageTask(TaskParam param) {
Specification<CronEntity> queryCondition =new Specification<CronEntity>() {
@Override
public Predicate toPredicate(Root<CronEntity> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicateList = new ArrayList<>();
// predicateList.add(criteriaBuilder.equal(root.get("status"),1));
return criteriaBuilder.and(predicateList.toArray(new Predicate[predicateList.size()]));
}
};
Page<CronEntity> page= cronEntityRepository.findAll(queryCondition,
PageRequest.of(param.getPageNo()-1,param.getPageSize(),Sort.by(Sort.Direction.DESC, "id")));
PageVo pageVo=new PageVo(page);
return pageVo;
}
@Override
public CronEntity addTask(CronEntity param) throws Exception {
param.setStatus(1);
CronEntity save = cronEntityRepository.save(param);
threadPoolTaskSchedulerConfig.scheduleTask(param.getClassName(),param.getMethodName(),param.getCorn());
return save;
}
@Override
public CronEntity updTask(CronEntity param) throws Exception {
Optional<CronEntity> byId = cronEntityRepository.findById(param.getId());
if(!byId.isPresent()||byId.get()==null){
throw new CustomException(CommonErrorCodeEnum.SERVICE_UNAVAILABLE.getCode(),"更新的对象不存在");
}
CronEntity save = JpaUtil.partialUpdate(param.getId(), param, cronEntityRepository);
String cronTaskKey = param.getClassName() + "#" + param.getMethodName();
threadPoolTaskSchedulerConfig.modifyScheduleTaskExecutePeriod(cronTaskKey,param.getCorn());
return save;
}
@Override
public void deleteTask(Integer id) throws Exception {
Optional<CronEntity> byId = cronEntityRepository.findById(id);
if(!byId.isPresent()||byId.get()==null){
throw new CustomException(CommonErrorCodeEnum.SERVICE_UNAVAILABLE.getCode(),"删除的对象不存在");
}
CronEntity cronEntity= byId.get();
cronEntityRepository.delete(cronEntity);
String cronTaskKey = cronEntity.getClassName() + "#" + cronEntity.getMethodName();
threadPoolTaskSchedulerConfig.removeScheduleTask(cronTaskKey);
}
@Override
public void executeOnceTask(Integer id) throws Exception {
System.out.println(id.toString());
Optional<CronEntity> byId = cronEntityRepository.findById(id);
if(!byId.isPresent()||byId.get()==null){
throw new CustomException(CommonErrorCodeEnum.SERVICE_UNAVAILABLE.getCode(),"执行一次的对象不存在");
}
CronEntity cronEntity= byId.get();
String cronTaskKey = cronEntity.getClassName() + "#" + cronEntity.getMethodName();
threadPoolTaskSchedulerConfig.executeTaskNow(cronTaskKey);
}
@Override
public void stopTask(Integer id) throws Exception {
Optional<CronEntity> byId = cronEntityRepository.findById(id);
if(!byId.isPresent()||byId.get()==null){
throw new CustomException(CommonErrorCodeEnum.SERVICE_UNAVAILABLE.getCode(),"删除的对象不存在");
}
CronEntity cronEntity= byId.get();
cronEntity.setStatus(0);
cronEntityRepository.save(cronEntity);
String cronTaskKey = cronEntity.getClassName() + "#" + cronEntity.getMethodName();
threadPoolTaskSchedulerConfig.removeScheduleTask(cronTaskKey);
}
@Override
public void startTask(Integer id) throws Exception {
Optional<CronEntity> byId = cronEntityRepository.findById(id);
if(!byId.isPresent()||byId.get()==null){
throw new CustomException(CommonErrorCodeEnum.SERVICE_UNAVAILABLE.getCode(),"开始的对象不存在");
}
CronEntity cronEntity= byId.get();
cronEntity.setStatus(1);
cronEntityRepository.save(cronEntity);
String cronTaskKey = cronEntity.getClassName() + "#" + cronEntity.getMethodName();
threadPoolTaskSchedulerConfig.startTask(cronTaskKey,cronEntity.getCorn());
}
}