动态定时任务

文章介绍了一种使用Spring的ThreadPoolTaskScheduler来动态执行定时任务的方法,任务调度支持从数据库中获取并实时更新cron表达式。提供了Web界面操作任务的功能,包括添加、修改、删除、立即执行、暂停和启动等。代码示例展示了如何配置和初始化线程池,以及如何根据数据库中的任务信息动态创建和调整定时任务。
摘要由CSDN通过智能技术生成

动态定时任务

利用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());
    }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值