简单实现动态修改定时任务

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

本文简单介绍在不引入其他定时任务框架情况下,自定义实现动态调整定时任务的执行周期、启用、禁用
注意:不适用多实例,示例可以在不重启服务的情况下实现已有定时任务的调整;如果为新增定时任务,则需重启服务

思路为使用spring的ScheduledTaskRegistrar来添加自定义的定时任务**1.**ScheduledTaskRegistrar.addTriggerTask中可以对定时任务task(task为Runnable的实现对象)设置触发器,触发器中我们可以实时去查询任务的cron,从而进行设置来修改定时任务的时间。
**2.**真正的任务的执行其实是执行task对象中的run方法,可以把真正的业务内容抽离出一个新的方法,加入名为execute,在run方法中可以去实时查询任务的状态从而去决定是否调用execute,这样就可以控制定时任务的启用与否

一、示例

1.任务对象,记录定时任务的实现类路径、cron、启用状态

@Data
@Entity
//一个IScheduleTask实现,只能对应一个定时任务
@Table(indexes = @Index(name = "task_class_path",columnList = "taskClassPath",unique = true))
public class ScheduleTask {
    @Id
    @GenericGenerator(name = "my-uuid",strategy = "uuid")
    @GeneratedValue(generator = "my-uuid")
    private String id;
    private String taskClassPath;
    private String cron;
    @Enumerated(EnumType.STRING)
    private TaskStatus status;
}
public enum TaskStatus {
    ENABLED("enabled"),DISABLED("disabled");
    private String value;
    TaskStatus(String value){
        this.value = value;
    }
}

2.任务对象CRUD相关方法

package com.example.scheduletask.repository;
import com.example.scheduletask.entity.ScheduleTask;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.io.Serializable;

@Repository
public interface ScheduleTaskRepository extends JpaRepository<ScheduleTask, Serializable> {
}
package com.example.scheduletask.service;

import com.example.scheduletask.entity.ScheduleTask;
import com.example.scheduletask.enums.TaskStatus;
import com.example.scheduletask.repository.ScheduleTaskRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.List;
import java.util.Optional;

@Service
public class ScheduleTaskService {
    @Autowired
    private ScheduleTaskRepository scheduleTaskRepository;

    public ScheduleTask save(ScheduleTask scheduleTask){
        try {
            Class.forName(scheduleTask.getTaskClassPath());
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("无定时任务实现类:" + scheduleTask.getTaskClassPath());
        }
        ScheduleTask target = this.findByTaskClassPath(scheduleTask.getTaskClassPath());
        Assert.isNull(target, "已存在" + scheduleTask.getTaskClassPath() + "的定时任务");
        return scheduleTaskRepository.save(scheduleTask);
    }

    public List<ScheduleTask> findAllTasks(){
        return scheduleTaskRepository.findAll();
    }
    public ScheduleTask findByTaskClassPath(String taskClassPath){
        ScheduleTask scheduleTaskExample = new ScheduleTask();
        scheduleTaskExample.setTaskClassPath(taskClassPath);
        Example<ScheduleTask> example = Example.of(scheduleTaskExample);
        Optional<ScheduleTask> optionalScheduleTask = scheduleTaskRepository.findOne(example);
        if(optionalScheduleTask.isPresent()){
            return optionalScheduleTask.get();
        }
        return null;
    }
    public ScheduleTask update(String id,String cron){
        ScheduleTask target = this.findById(id);
        Assert.notNull(target, "不存在id为" + id + "的定时任务");
        target.setCron(cron);
       return scheduleTaskRepository.save(target);
    }

    public ScheduleTask findById(String id){
        Optional<ScheduleTask> optionalScheduleTask = scheduleTaskRepository.findById(id);
        if(optionalScheduleTask.isPresent()){
            return optionalScheduleTask.get();
        }
        return null;
    }
    public String disableTaskById(String id){
        modifyTaskStatusById(id,TaskStatus.DISABLED);
        return "已禁用任务" + id;
    }
    public String enableTaskById(String id){
        modifyTaskStatusById(id,TaskStatus.ENABLED);
        return "已启用任务" + id;
    }
    public void modifyTaskStatusById(String id,TaskStatus status){
        ScheduleTask target = this.findById(id);
        Assert.notNull(target, "不存在id为" + id+ "的定时任务");
        target.setStatus(status);
        scheduleTaskRepository.save(target);
    }
}

3.定时任务接口

jdk8之后,接口中可以有已实现的方法,方法必须为default或者static声明

package com.example.scheduletask.task;
import com.example.scheduletask.entity.ScheduleTask;
import com.example.scheduletask.enums.TaskStatus;
import com.example.scheduletask.service.ScheduleTaskService;
import com.example.scheduletask.utils.SpringUtils;

public interface IScheduleTask extends Runnable{
    /**
     * 任务内容
     */
    void execute();
    
    @Override
    default void run(){
        //定时任务执行时其实执行的run方法,execute方法为我们真正的任务体,在run方法被定时调用时实时查询数据库任务状态来决定是否调用
        //execute方法,从而控制定时任务的开关
        ScheduleTaskService ScheduleTaskService = SpringUtils.getBean(ScheduleTaskService.class);
        ScheduleTask scheduleTask = ScheduleTaskService.findByTaskClassPath(this.getClass().getName());
        if(scheduleTask.getStatus() != null && TaskStatus.ENABLED.equals(scheduleTask.getStatus())){
            execute();
        }
    }
}

工具类,用于在接口方法中去获取bean

package com.example.scheduletask.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }
    public static <T>T getBean(Class<T> tClass){
        try{
            return applicationContext.getBean(tClass);
        } catch (BeansException e){
            return null;
        }
    }
}

4.定时任务配置

服务启动时,注册数据库已有的定时任务,并设置触发器(触发器内部实现实时查询数据库的cron去计算定时任务的下次执行时间)

package com.example.scheduletask.config;

import com.example.scheduletask.entity.ScheduleTask;
import com.example.scheduletask.service.ScheduleTaskService;
import com.example.scheduletask.task.IScheduleTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.util.Assert;

import java.util.List;

@Configuration
@EnableScheduling //开启定时任务支持
public class ScheduleTaskConfig implements SchedulingConfigurer {
    @Autowired
    private ScheduleTaskService scheduleTaskService;
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        List<ScheduleTask> scheduleTaskList = scheduleTaskService.findAllTasks();
        scheduleTaskList.forEach(e -> {
            try {
                //spring启动时,注册数据库中全部定时任务
                Class clazz = Class.forName(e.getTaskClassPath());
                Assert.isAssignable(IScheduleTask.class, clazz,e.getTaskClassPath() + "没有实现定时任务接口IScheduleTask");
                Runnable runnable = (Runnable) clazz.newInstance();
                //不使用addCronTask,而使用addTriggerTask注册,触发器内部则实现实时重新计算触发时间的逻辑,就可以实现修改数据库,从而修改定时任务时间
                taskRegistrar.addTriggerTask(runnable, triggerContext -> {
                    String actualCron = scheduleTaskService.findByTaskClassPath(e.getTaskClassPath()).getCron();
                    return new CronTrigger(actualCron).nextExecutionTime(triggerContext);
                });
            } catch (ClassNotFoundException classNotFoundException) {
                throw new IllegalArgumentException("无定时任务实现类:" + e.getTaskClassPath());
            } catch (IllegalAccessException illegalAccessException) {
                illegalAccessException.printStackTrace();
            } catch (InstantiationException instantiationException) {
                instantiationException.printStackTrace();
            } catch (IllegalArgumentException ie){
                ie.printStackTrace();
            }
        });
    }
}

5.定时任务实现

以下做一个定时任务的简单实现,打印当前时间

package com.example.scheduletask.task;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestTask implements IScheduleTask{
    @Override
    public void execute() {
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
}

6.定时任务控制器

package com.example.scheduletask.controller;

import com.example.scheduletask.entity.ScheduleTask;
import com.example.scheduletask.enums.TaskStatus;
import com.example.scheduletask.service.ScheduleTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.support.CronExpression;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
public class ScheduleTaskController {
    @Autowired
    private ScheduleTaskService scheduleTaskService;
    @GetMapping("/tasks")
    public List<ScheduleTask> findAllTasks(){
        return scheduleTaskService.findAllTasks();
    }

    /**
     * 新添加的定时任务需要重启服务,修改定时任务等则无须重启服务
     */
    @PostMapping("/task/add")
    public ScheduleTask addTask(@RequestBody ScheduleTask scheduleTask){
        if(!CronExpression.isValidExpression(scheduleTask.getCron())){
            throw new IllegalArgumentException(scheduleTask.getCron() + "不是合法的cron表达式");
        }
        scheduleTask.setStatus(TaskStatus.ENABLED);
        return scheduleTaskService.save(scheduleTask);
    }
    @PutMapping("/task/{id}")
    public ScheduleTask modiftTask(@PathVariable("id") String id,String cron){
        if(!CronExpression.isValidExpression(cron)){
            throw new IllegalArgumentException(cron + "不是合法的cron表达式");
        }
        return scheduleTaskService.update(id,cron);
    }
    @PostMapping("/task/{id}/disable")
    public String disableTask(@PathVariable("id") String id){
        return scheduleTaskService.disableTaskById(id);
    }
    @PostMapping("/task/{id}/enable")
    public String enbaleTask(@PathVariable("id") String id){
        return scheduleTaskService.enableTaskById(id);
    }
}

测试

通过postman新增1条定时任务:每秒打印当前时间
在这里插入图片描述
新增的任务需重启服务,重启后,定时任务启动成功,每秒打印时间
在这里插入图片描述
禁用任务(无需重启服务)
在这里插入图片描述
不再打印时间
在这里插入图片描述
重新启用,并修改为每3秒打印一次时间(无需重启服务)
在这里插入图片描述
可以看到打印时间的间隔已变为3秒
在这里插入图片描述
github样例地址:https://github.com/lancepan/schedule-task/tree/master/src/main/java/com/example/scheduletask


版权声明:本文为qq_42896969原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。