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

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


前言

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

思路为使用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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
多任务定时器能够完成现有定时软件的大多数功能,其中包括:   任务启动方式:     手动启动      每天的某个时间段启动  (新)      每周某天的某个时间段启动(新)      每月某日的某个时间段启动(新)      每隔N天后启动     (新)      按日期段启动      (新)      系统运行N久后启动   (新)         动作执行方式:     立即执行      倒计时执行      循环执行      定时执行   动作种类:     关机      重启      注销      待机      关闭屏幕      打开文件      关闭进程      打开网址      文字提示      播放音乐      音量设置      等待      执行另一个任务 (新) 2: 而且还有:多任务运行、多动作执行、浮动窗口显示......   用户可以根据自己的需要设置各种任务,并可方便的随时执行。而任务可执行的动作更可 添加多个,如文字提示的同时播放音乐。动作的数量没有限制,使得软件能够实现用户各种想 要的效果。同时,从2.1版开始,增加了多任务执行的功能,可以根据需要启动多个任务。新增 的功能势必能让你使用起来更加方便。   此外,你是否发生过其他定时软件在执行时难以知道其执行状态的情况呢?多任务定时器贴心 的为你设置了一个浮动窗口,只需占用显示器一点点空间,便可以随时知道任务状态,同时, 你还可以通过对浮动窗口的参数进行设置,使浮动窗口更加个性化。   从2.1版开始,通过几个简单的步骤,更可让浮动窗口以不规则窗口的形式显示(步骤见【基础教程】)。 3: 界面美观,操作人性    多任务定时器在经过用户反馈后,进行了各种细微的改进。2.1版的界面更  经过了较大的调整,更加美观,易用。    其余的改进特点有:    方便快速使用的简洁模式。    功能更加强大的任务模式。     改进最常使用的【文字提示】动作,为其配备更加合理美观的提示窗口。    提供浮动窗口的更多可设参数,小到字体,大到窗口形状,都可以进行设置。 多任务执行功能实现起来其实不难,但是在一些方面上没能想到一个 较好的解决方案,例如:多任务执行会不会带来混乱、多任务执行时浮动窗口要如何 显示各个任务信息等。总不能把一个尚未处理好的功能添加进来吧?于是这个功能就 一直拖着没加。不过好在最终还是为这些问题想到了解决方案,并把这个功能添加了 进来。   下面是与多任务执行的一些相关介绍: 1、每个任务都有四种状态:未启动、检测中、执行中以及已自动启动   未启动:  任务需要用户手动启动。   检测中:  任务属于自动启动的,此时软件正在检测其启动条件。   执行中:  任务现在正在执行。   已自动启动:任务已被自动启动并执行完成。   2、当你希望所以任务都暂停自动启动时,取消选择该处即可。重新选择该   处后,多任务定时器便会重新检测并启动这些任务。 切换任务信息显示:   当有多个任务在执行时(3),浮动窗口会优先显示最早执行的任务的信息。 若你要显示其他正在执行的任务的信息,可以在【浮动窗口】里点击右键,在 【任务显示】里选择要显示的任务(4),在任务显示的子菜单中,任务以 【任务序号:任务名称】的形式显示。根据序号或任务名,便可以在任务模式窗 口中找到对应的任务了。 单击浮动窗口亦可切换任务信息显示:   为了更加方便合理的显示任务信息,在2.1版编写的后期,浮动窗口又被赋予 了新的功能:直接单击便可以切换任务显示。 -------------------------------------------------------------------------------- 浮动窗口的时间与日期显示:   原来的浮动窗口,仅能在空闲时显示当前时间。而在2.1版,浮动窗口将显示 三种类型的内容。只要通过其菜单,便可以设置了。 -------------------------------------------------------------------------------- 任务启动方式说明:   手动启动:任务不自动启动,而是由用户进行启动   每天  :每天的某个时间段自动启动。   每周  :每周周一至周日的指定一天中的某个时间段自动启动。   每月  :每月某天的某个时段自动启动。   每隔  :每隔指定天数后启动。   按日期段:指定一个日期段,若当前日期位于日期段中,则启动任务。   系统运行:当系统累计运行(累计时间从系统开机时计)指定时间后执行。 注意:   当一个任务被自动启动并执行完后,便要等到程序重新启动才会被继续检测 和启动。 -------------------------------------------------------------------------------- 任务执行方式说明:   倒计时执行:任务启动后,会倒计时指定时间后才开始执行其动作。   定时执行 :任务启动后,在指定时间执行动作。   循环执行 :任务启动后,会等待指定时间后执行其动作,并一直循环直到任务被停止。   立即执行 :任务一启动就马上执行其动作。 -------------------------------------------------------------------------------- 动作说明:   重启  :略   关机  : 略   待机  :进入休眠模式。   注销  :注销当前用户后,转换到切换用户的界面。   关闭屏幕:略   等待  :等待指定秒数,最长为99999秒。(快捷模式无法使用)   设置音量:将当前系统音量调整为指定的百分比,百分比越高,音量越高。   打开文件:打开任何文件,包括可执行文件(exe)。如果打开的文件未被关联,        则不能被打开。   打开网址:调用系统的默认浏览器,打开指定网址。   文字提醒:弹出对话框,对话框显示用户设置的文字。在程序设置中,有两种形式        的提示窗口可供选择。   播放音乐:播放wav格式的音乐。(若需要播放mp3或其他格式的文件,请使用        【打开文件】动作来完成)   关闭进程:关闭指定进程。点击【选择进程】按钮后,会有弹出进程窗口供你选择要        关闭的进程。若要关闭的程序未运行,则在进程表里面看不到。需要运行        该程序后,重新打开进程窗口就可以看到了。   执行任务:执行另一个任务。(快捷模式无法使用)        该动作有几点需要注意:        1、被执行的任务会被重新开始执行,无论其正在执行或否。        2、若要执行的任务已被删除,则执行该任务的动作会被标记为无效并不被执行。        3、可以执行动作所处的任务,效果同1。 -------------------------------------------------------------------------------- 调整动作执行速度:    默认情况下,动作的执行速度是每秒一个周期,即每秒执行一个任务。如果你 觉得慢,可以打开程序安装目录下的Setting.inf文件,找到ExecuteTime= 一栏。调整其 后的数字(毫秒为单位,1000毫秒=1秒),就可以修改动作的执行速度。该参数 的有效范围为 1~1000 毫秒。 需要注意的是:若改快执行速度,则等待动作的时间 也会由于变快而缩短哦,而且也会消耗更多系统资源,因此建议保留默认值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值