提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
本文简单介绍在不引入其他定时任务框架情况下,自定义实现动态调整定时任务的执行周期、启用、禁用
注意:不适用多实例,示例可以在不重启服务的情况下实现已有定时任务的调整;如果为新增定时任务,则需重启服务
思路为使用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