简易定时任务管理器开发

定时任务执行在项目中经常需要使用,springboot  提供了简单易用的定时任务,通过@Scheduled标签和@EnableScheduling就能实现。

这里通过自定义一个简易的任务调度管理器,来实现简易的自定义任务调度。

 

定义一个定时任务调度的starter:

设计思路如下:

 

 

定时任务使用 ThreadPoolTaskScheduler来执行定时任务。通过定时任务执行返回的 ScheduledFuture可以停止任务的执行。

Scheduler Starter 的开发主要类如下:

 

1. 定时任务执行类:

接口:

import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

public interface CronTask {
  void start();

  void stop();

  void reStart();

  String getCron();

  void setCron(String corn);

  String getStatus();

  void setStatus(String status);

  void setSchedulerPool(ThreadPoolTaskScheduler schedulerPool);
}

实现类

public class CronTaskService implements CronTask {

  private static final Logger logger = LoggerFactory.getLogger(CronTaskService.class);
  private ThreadPoolTaskScheduler taskScheduler;

  private ScheduledFuture<?> future;
  private ScheduledJob scheduledJob;
  private int taskId;

  private String status;

  private String currentCron;

  public CronTaskService(
      int taskId,
      String cron,
      ScheduledJob job,
      ThreadPoolTaskScheduler schedulerPool,
      String status) {
    this.taskId = taskId;
    this.currentCron = cron;
    this.scheduledJob = job;
    this.status = status;
    if (schedulerPool == null) {
      this.taskScheduler = new ThreadPoolTaskScheduler();
      taskScheduler.initialize();
    } else {
      this.taskScheduler = schedulerPool;
    }
  }

  @Override
  public void start() {
    if (future != null) {
      future.cancel(true);
    }
    if (SchedulerConstant.TASK_STATUS_INACTIVE.equals(this.status)) {
      logger.info(
          "Task id "
              + taskId
              + " is configured as inactive currently. It won't be started automatically. Please try to manually start the job if needed.");
      return;
    }
    future =
        taskScheduler.schedule(
            scheduledJob,
            (triggerContext) -> new CronTrigger(currentCron).nextExecutionTime(triggerContext));
    logger.info("Task " + taskId + " is started.");
  }

  @Override
  public void stop() {
    if (future != null) {
      future.cancel(false);
      future = null;
      logger.info("Task " + taskId + " is stopped successfully.");
    }
  }

  @Override
  public void reStart() {
    stop();
    start();
  }

  @Override
  public String getCron() {
    return this.currentCron;
  }

  @Override
  public void setCron(String corn) {
    this.currentCron = corn;
  }

  @Override
  public void setSchedulerPool(ThreadPoolTaskScheduler schedulerPool) {
    this.taskScheduler = schedulerPool;
  }

  @Override
  public String getStatus() {
    return status;
  }

  @Override
  public void setStatus(String status) {
    this.status = status;
  }
}

 

 

2. 定时任务调度管理器

public interface ScheduledTaskManager {

  void initScheduledJobs();

  boolean startTask(int taskId);

  boolean stopTask(int taskId);

  boolean reScheduleTask(int taskId, String cron) throws Exception;

  boolean reScheduleAllTask(String cron) throws Exception;

  List<TaskDefinition> getAllTasks();

  boolean registerTask(TaskDefinition taskDefinition) throws Exception;

  boolean deleteTask(int taskId) throws Exception;

  boolean updateTask(TaskDefinition taskDefinition) throws Exception;
}

  

@Service
public class ScheduledTaskManagerService implements ScheduledTaskManager {
{
  @Autowired ThreadPoolTaskScheduler taskScheduler;

  @Autowired TaskDefHandler taskDefinitionService;

  @Autowired ApplicationContextUtils applicationContextUtils;

  private Map<Integer, DynamicCronTask> scheduledJobs = new ConcurrentHashMap<>();
  private static final Logger logger = LoggerFactory.getLogger(ScheduledTaskManagerService.class);

  @PostConstruct
  @Override
  public void initScheduledJobs() {
    List<TaskDefinition> taskDefinitions = taskDefinitionService.getAllTaskDefs();
    logger.info("There are " + taskDefinitions.size() + " tasks which will start to run.");
    taskDefinitions.forEach(
        taskDefinition -> {
          try {
            CronTask dynamicCronTask = createCronTask(taskDefinition);
            dynamicCronTask.start();
          } catch (ClassNotFoundException | NoSuchMethodException e) {
            logger.error(
                "Task "
                    + taskDefinition.getTaskId()
                    + " start failed with error: Job class is not found. ");
          } catch (Exception e) {
            logger.error(
                "Task "
                    + taskDefinition.getTaskId()
                    + " start failed with error: "
                    + e.getLocalizedMessage());
          }
        });
  }

  private CronTask createCronTask(TaskDefinition taskDefinition)
      throws ClassNotFoundException, NoSuchMethodException {
    Class<ScheduledJob> scheduledJobClass =
        (Class<ScheduledJob>) Class.forName(taskDefinition.getJobClass());
    applicationContextUtils.registerBean(scheduledJobClass.getName(), scheduledJobClass);
    ScheduledJob instance =
        (ScheduledJob) applicationContextUtils.getBean(scheduledJobClass.getName());

    CronTask dynamicCronTask =
        new CronTaskService(
            taskDefinition.getTaskId(),
            taskDefinition.getCron(),
            instance,
            taskScheduler,
            taskDefinition.getStatus());
    scheduledJobs.put(taskDefinition.getTaskId(), dynamicCronTask);
    return dynamicCronTask;
  }

  @Override
  public boolean startTask(int taskId) {
    TaskDefinition task = taskDefinitionService.getTaskDefById(taskId);
    if (task == null) {
      logger.error("Task id " + taskId + " is not existing in the system. It can't be started.");
      return false;
    }

    if (SchedulerConstant.TASK_STATUS_INACTIVE.equals(task.getStatus())) {
      task.setStatus(SchedulerConstant.TASK_STATUS_ACTIVE);
      taskDefinitionService.updateTaskDefinition(task);
    }

    if (scheduledJobs.containsKey(taskId)) {
      CronTask dynamicCronTask = scheduledJobs.get(taskId);
      dynamicCronTask.setStatus(SchedulerConstant.TASK_STATUS_ACTIVE);
dynamicCronTask.setCron(task.getCron()); dynamicCronTask.start();
return true; } try { CronTask dynamicCronTask = createDynamicCronTask(task); dynamicCronTask.setStatus(SchedulerConstant.TASK_STATUS_ACTIVE); dynamicCronTask.start(); return true; } catch (ClassNotFoundException e) { logger.error("Task " + taskId + " start failed with error: Job class is not found. "); } catch (Exception e) { logger.error("Task " + taskId + " start failed with error: " + e.getLocalizedMessage()); } return false; } @Override public boolean stopTask(int taskId) { TaskDefinition task = taskDefinitionService.getTaskDefById(taskId); if (task == null) { logger.error("Task id " + taskId + " is not existing in the system. It can't be stopped."); return false; } if (SchedulerConstant.TASK_STATUS_ACTIVE.equals(task.getStatus())) { task.setStatus(SchedulerConstant.TASK_STATUS_INACTIVE); taskDefinitionService.updateTaskDefinition(task); } if (scheduledJobs.containsKey(taskId)) { CronTask dynamicCronTask = scheduledJobs.get(taskId); dynamicCronTask.setStatus(SchedulerConstant.TASK_STATUS_INACTIVE); dynamicCronTask.stop(); } return true; } @Override public boolean reScheduleTask(int taskId, String cron) throws Exception { if (!CommonValidateUtils.isCronValid(cron)) { logger.error( "Setting the task id " + taskId + " with cron expression is wrong, please correct it"); throw new Exception("Cron expression is wrong, please correct it"); } TaskDefinition taskDefinition = taskDefinitionService.getTaskDefById(taskId); if (taskDefinition == null) { logger.error("Task id " + taskId + " item is not found, it can't be rescheduled."); throw new Exception("Task id " + taskId + " item is not found, it can't be rescheduled."); } taskDefinition.setCron(cron); boolean result = taskDefinitionService.updateTaskDefinition(taskDefinition); if (!result) { logger.error("Update Task id " + taskId + " failed."); throw new Exception("Update Task id " + taskId + " failed."); } if (scheduledJobs.containsKey(taskId)) { DynamicCronTask dynamicCronTask = scheduledJobs.get(taskId); if (!dynamicCronTask.getCron().equals(cron)) { dynamicCronTask.setCron(cron); dynamicCronTask.reStart(); } } return true; } @Override public boolean reScheduleAllTask(String cron) throws Exception { if (!CommonValidateUtils.isCronValid(cron)) { logger.error("Reschedule all the task with incorrect cron expression."); throw new Exception("Cron expression is wrong, please correct it"); } List<TaskDefinition> allTasks = getAllTasks(); for (TaskDefinition taskDef : allTasks) { taskDef.setCron(cron); reScheduleTask(taskDef.getTaskId(), cron); } return true; } @Override public List<TaskDefinition> getAllTasks() { return taskDefinitionService.getAllTaskDefs(); } @Override public boolean registerTask(TaskDefinition taskDefinition) throws Exception { TaskDefinition task = taskDefinitionService.getTaskDefById(taskDefinition.getTaskId()); if (task != null) { logger.error( "Register the task with id " + taskDefinition.getTaskId() + " is already existing. Can't register the task."); throw new Exception( "Same task id " + taskDefinition.getTaskId() + " is already existing. Please use another one."); } return taskDefinitionService.registerTaskDef(taskDefinition); } @Override public boolean deleteTask(int taskId) throws Exception { TaskDefinition task = taskDefinitionService.getTaskDefById(taskId); if (task == null) { throw new Exception("Task id " + taskId + " item is not found, it can't be deleted."); } if (scheduledJobs.containsKey(taskId)) { CronTask dynamicCronTask = scheduledJobs.get(taskId); dynamicCronTask.setStatus(SchedulerConstant.TASK_STATUS_INACTIVE); dynamicCronTask.stop(); scheduledJobs.remove(taskId); } return taskDefinitionService.deleteTaskDefById(taskId); } @Override public boolean updateTask(TaskDefinition taskDefinition) throws Exception { TaskDefinition task = taskDefinitionService.getTaskDefById(taskDefinition.getTaskId()); if (task == null) { throw new Exception( "Task id " + taskDefinition.getTaskId() + " item is not found, it can't be updated."); } boolean result = taskDefinitionService.updateTaskDefinition(taskDefinition); if (!result) { logger.error("Update Task id " + taskDefinition.getTaskId() + " failed."); throw new Exception("Update Task id " + taskDefinition.getTaskId() + " failed."); } return true; } }

 

3. 数据库操作类

public interface TaskDefHandler {

  List<TaskDefinition> getAllTaskDefs();

  boolean updateTaskDefinition(TaskDefinition task);

  TaskDefinition getTaskDefById(int taskId);

  boolean deleteTaskDefById(int taskId);

  boolean registerTaskDef(TaskDefinition task);
}

 

6. 定时任务定义实体类

public class TaskDefinition {

  private Integer taskId;
  private String cron;
  private String taskName;
  private String description;
  private String jobClass;
  private String status;
  private String lockInfo;

  public Integer getTaskId() {
    return taskId;
  }

  public void setTaskId(Integer taskId) {
    this.taskId = taskId;
  }

  public String getCron() {
    return cron;
  }

  public void setCron(String cron) {
    this.cron = cron;
  }

  public String getTaskName() {
    return taskName;
  }

  public void setTaskName(String taskName) {
    this.taskName = taskName;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public String getJobClass() {
    return jobClass;
  }

  public void setJobClass(String jobClass) {
    this.jobClass = jobClass;
  }

  public String getStatus() {
    return status;
  }

  public void setStatus(String status) {
    this.status = status;
  }

  public String getLockInfo() {
    return lockInfo;
  }

  public void setLockInfo(String lockInfo) {
    this.lockInfo = lockInfo;
  }
}

 

 

5. 定时任务抽象类

public abstract class ScheduledJob implements Runnable {
  @Override
  public void run() {
    boolean flag = preProcess();
    if (flag) {
      execute();
      postProcess();
    }
  }

  public abstract void execute();

  private boolean preProcess() {
    return true;
  }

  private void postProcess() {}
}

 

6. 定时任务配置类

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "schedule")
public class ScheduleProperties {
    private int poolSize = 1;

    public int getPoolSize() {
        return poolSize;
    }

    public void setPoolSize(int poolSize) {
        if(poolSize <= 0) {
            poolSize = 1;
        }
        this.poolSize = poolSize;
    }
}

 

7. RestAPI 控制器

@RestController
@RequestMapping("/scheduler")
public class SchedulerController {
  @Autowired ScheduledTaskManager scheduledTaskManager;

  @PutMapping("/reSchedule")
  public RestResponse reSchedule(
      @RequestParam(name = "taskId") int taskId, @RequestParam(name = "cron") String cron) {
    RestResponse resp = new RestResponse();
    if (!CommonValidateUtils.isCronValid(cron)) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage("Bad Cron expression, please correct it and try again.");
      return resp;
    }
    try {
      scheduledTaskManager.reScheduleTask(taskId, cron);
    } catch (Exception e) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage(e.getMessage());
      return resp;
    }
    resp.setMessage("Reschedule the task id " + taskId + " successfully.");
    return resp;
  }

  @PutMapping("/stop")
  public RestResponse stop(@RequestParam(name = "taskId") int taskId) {
    RestResponse resp = new RestResponse();
    boolean result = scheduledTaskManager.stopTask(taskId);
    if (result) {
      resp.setMessage("Stop the task id " + taskId + " successfully.");
    } else {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage(
          "Stop the task id "
              + taskId
              + " failed. Please check if the task id is correct or please try it again.");
    }
    return resp;
  }

  @PutMapping("/start")
  public RestResponse start(@RequestParam(name = "taskId") int taskId) {
    RestResponse resp = new RestResponse();
    boolean result = scheduledTaskManager.startTask(taskId);
    if (result) {
      resp.setMessage("Start the task id " + taskId + " successfully.");
    } else {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage(
          "Start the task id "
              + taskId
              + " failed. Please check if the task id is existing or please try it again.");
    }
    return resp;
  }

  @PutMapping("/reScheduleAll")
  public RestResponse reScheduleAll(@RequestParam(name = "cron") String cron) {
    RestResponse resp = new RestResponse();
    if (!CommonValidateUtils.isCronValid(cron)) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage("Bad Cron expression, please correct it and try again.");
      return resp;
    }
    try {
      scheduledTaskManager.reScheduleAllTask(cron);
      resp.setMessage("Reschedule all the tasks successfully.");
    } catch (Exception e) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage("Reschedule some tasks are failed. Please check the error in log.");
    }
    return resp;
  }

  @GetMapping("/getAllTasks")
  public List<TaskDefinition> getAllTasks() {
    return scheduledTaskManager.getAllTasks();
  }

  @PostMapping("/registerTask")
  public RestResponse registerTask(@RequestBody TaskDefinition taskDefinition) {
    RestResponse resp = new RestResponse();
    if (!checkRequiredFields(taskDefinition)) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage(
          "At least one of the required fields taskId, cron, jobClass are missing the value.");
      return resp;
    }
    if (!CommonValidateUtils.isCronValid(taskDefinition.getCron())) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage("Bad Cron expression, please correct it and try again.");
      return resp;
    }

    try {
      checkJobClass(taskDefinition.getJobClass());
    } catch (Exception e) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage("Job class provided is neither existing nor defined correctly.");
      return resp;
    }

    taskDefinition.setStatus(SchedulerConstant.TASK_STATUS_ACTIVE);
    boolean result;
    try {
      result = scheduledTaskManager.registerTask(taskDefinition);
      if (!result) {
        resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
        resp.setMessage("Register task failed, please try again.");
      } else {
        resp.setMessage("Register task successfully.");
      }
    } catch (Exception e) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage(e.getLocalizedMessage());
    }

    return resp;
  }

  @PutMapping("updateTask")
  public RestResponse updateTask(@RequestBody TaskDefinition taskDefinition) {
    RestResponse resp = new RestResponse();
    if (!checkRequiredFields(taskDefinition)) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage(
          "At least one of the required fields taskId, cron, jobClass are missing the value.");
      return resp;
    }
    if (!CommonValidateUtils.isCronValid(taskDefinition.getCron())) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage("Bad Cron expression, please correct it and try again.");
      return resp;
    }

    try {
      checkJobClass(taskDefinition.getJobClass());
    } catch (Exception e) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage("Job class provided is neither existing nor defined correctly.");
      return resp;
    }

    boolean result;
    try {
      result = scheduledTaskManager.updateTask(taskDefinition);
      if (!result) {
        resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
        resp.setMessage("Update task failed, please try again.");
      } else {
        resp.setMessage("Update task successfully.");
      }
    } catch (Exception e) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage(e.getLocalizedMessage());
    }
    return resp;
  }

  @DeleteMapping("deleteTask")
  public RestResponse deleteTask(@RequestParam(name = "taskId") int taskId) {
    RestResponse resp = new RestResponse();

    try {
      boolean result = scheduledTaskManager.deleteTask(taskId);
      if (!result) {
        resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
        resp.setMessage("Delete task failed, please try again.");
      } else {
        resp.setMessage("Delete task successfully.");
      }
    } catch (Exception e) {
      resp.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
      resp.setMessage("Delete task failed. Error : " + e.getLocalizedMessage());
    }
    return resp;
  }

  private boolean checkRequiredFields(TaskDefinition taskDefinition) {
    return taskDefinition.getTaskId() != null
        && !isEmpty(taskDefinition.getCron())
        && !isEmpty(taskDefinition.getJobClass());
  }

  private boolean isEmpty(String cron) {
    return cron == null || "".equals(cron.trim());
  }

  private void checkJobClass(String jobClass) throws Exception {
    Class clazz = Class.forName(jobClass);

    Class supperClass = clazz.getSuperclass();
    if (supperClass != ScheduledJob.class) {
      throw new Exception("Class provided is not correct.");
    }
  }
}

 

8. 自动配置类

@Configuration
@EnableConfigurationProperties(ScheduleProperties.class)
@ConditionalOnProperty(prefix = "schedule", name = "enabled", havingValue = "true" , matchIfMissing = true)
public class TaskConfigure {

    @Autowired
    private ScheduleProperties scheduleProperties;

    @Bean
    ThreadPoolTaskScheduler jobScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(scheduleProperties.getPoolSize());
        return taskScheduler;
    }
}

 

9. 在resources下新建文件夹META-INF, 新建文件spring.factories, 添加如下语句

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.demo.schedule.config.TaskConfigure

 

posted @ 2021-06-15 17:48  闪闪的星光  阅读(68)  评论(0)    收藏  举报