大数跨境
0
0

SpringBoot3优雅停止/重启定时任务

SpringBoot3优雅停止/重启定时任务 Spring全家桶实战案例
2024-07-30
0
导读:SpringBoot3优雅停止定时任务

环境:SpringBoot3.2.5



1. 简介

在Spring Boot中,使用@Scheduled注解可以方便地创建定时任务。然而,随着应用程序的复杂性和运维需求的增加,动态管理这些定时任务成为了一个重要的问题。针对这种动态管理定时任务Spring Boot中并没有提供相应的实现,所以就需要我们自己动手来实现定时任务的管理。

2. 执行原理

首先,我们要搞清楚Spring Boot定时任务的执行原理,其核心先通过ScheduledAnnotationBeanPostProcessor处理器,找到所有的Bean中使用了@Scheduled注解的方法,然后将对应的方法包装到Runnable中。

public class ScheduledAnnotationBeanPostProcessor {  public Object postProcessAfterInitialization(Object bean, String beanName) {    // 找到符合条件的方法    Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,      (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {        Set<Scheduled> scheduledAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations(            method, Scheduled.class, Schedules.class);        return (!scheduledAnnotations.isEmpty() ? scheduledAnnotations : null);      });    // 处理方法,在processScheduled方法中会将任务包装成ScheduledMethodRunnable对象        annotatedMethods.forEach((method, scheduledAnnotations) ->      scheduledAnnotations.forEach(scheduled -> processScheduled(scheduled, method, bean)));   }}

接下来,就是通过TaskScheduler来执行定时任务,该接口提供了一些列的方法:

public interface TaskScheduler {  // 这些调用任务都返回了Future  ScheduledFuture<?> schedule(Runnable task, Trigger trigger) ;
ScheduledFuture<?> schedule(Runnable task, Instant startTime);
ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
  // 还有其它方法。}

在默认情况下,Spring Boot定时任务的执行线程池使用的是ThreadPoolTaskSchedulerBean。内部真正任务调用是通过ScheduledExecutorService执行定时任务。

所以,要实现动态管理任务,就需要记录下每个任务信息。记录任务信息是为了停止任务及再次启动任务,在上面的调度方法都返回了Future对象,可以通过该Future对象来终止任务,可以通过再次调用schedule方法来再次启动任务。所以,我们需要自定义TaskScheduler,在自定义的实现中我们就能很方便的记录管理每个定时任务。

3. 实战案例

要管理任务,我们就必须为每个任务提供一个有意义的名称。@Scheduled注解并没有提供此功能。所以这块功能,需要自己实现。

3.1 自定义@Task注解

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Task {  /**任务名称*/  String value() default "" ;}

该注解用来对任务的说明。

3.2 任务信息TaskInfo

public class TaskInfo {  private Runnable task ;  private Instant startTime ;  private Trigger trigger ;  private Duration period ;  private Duration delay ;  private ScheduledFuture<?> future ;}

该类用来在执行任务前记录当前的信息,以便可以对任务进行停止和重启。

3.3 自定义线程池

@Componentpublic class PackTaskScheduler extends ThreadPoolTaskScheduler {    private static final Map<String, TaskInfo> TASK = new ConcurrentHashMap<>() ;  @Override  public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {    ScheduledFuture<?> schedule = super.schedule(task, trigger) ;    if (task instanceof ScheduledMethodRunnable smr) {      String taskName = parseTask(smr);      TASK.put(taskName, new TaskInfo(task, null, trigger, null, null, schedule)) ;    }    return schedule ;  }  // 还有其它重写的方法,自行实现  private String parseTask(ScheduledMethodRunnable smr) {    Method method = smr.getMethod();    Task t = method.getAnnotation(Task.class) ;    String taskName = method.getName() ;     if (t != null) {      String value = t.value() ;      if (StringUtils.hasLength(value)) {        taskName = value ;      }    }    return taskName ;  }
public void stop(String taskName) { TaskInfo task = TASK.get(taskName) ; if (task != null) { task.getFuture().cancel(true) ; } } public void start(String taskName) { TaskInfo task = TASK.get(taskName) ; if (task != null) { if (task.trigger != null) { this.schedule(task.getTask(), task.getTrigger()) ; } if (task.period != null) { this.scheduleAtFixedRate(task.getTask(), task.getPeriod()) ; } } }}

该类的核心作用就2个:
1. 重写任务调度方法,记录任务信息
2. 添加停止/重启任务调度
也可以考虑在该类中实现任务的持久化。

以上就完成了所有的核心操作。接下来写2个方法进行测试。

3.4 测试

定时任务

@Scheduled(cron = "*/3 * * * * *")@Task("测试定时任务-01")public void scheduler() throws Exception {  System.err.printf("当前时间: %s, 当前线程: %s, 是否虚拟线程: %b%n", new SimpleDateFormat("HH:mm:ss").format(new Date()), Thread.currentThread().getName(), Thread.currentThread().isVirtual()) ;}

停止/重启接口

private final PackTaskScheduler packTaskScheduler ;public SchedulerController(PackTaskScheduler packTaskScheduler) {  this.packTaskScheduler = packTaskScheduler ;}@GetMapping("stop")public Object stop(String taskName) {  this.packTaskScheduler.stop(taskName) ;  return String.format("停止任务【%s】成功", taskName) ;}@GetMapping("/start") public Object start(String taskName) {  this.packTaskScheduler.start(taskName) ;  return String.format("启动任务【%s】成功", taskName) ; }

分别调用上面2个方法可以对具体的任务进行停止及重启。

以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏

推荐文章

紧急!Spring Boot安全漏洞

SpringBoot非常实用的一个功能,你肯定不知道

总结7种JVM出现OOM时的原因及解决方案

SpringBoot3必须掌握的5个强大功能,其中JVM优化技巧太厉害了

提升系统吞吐量,详解JDK21虚拟线程,炸裂

玩转Redis!非常强大的Redisson分布式集合,少写60%代码

Spring Boot 3太强:全新Controller接口定义方式

SpringBoot优雅定制接口参数格式转换

Spring一个强大便捷的代理工厂类,你用过吗?

分享几个你不知道的高级SpringBoot最佳实践

完美!SpringBoot + HTML模板高效生成PDF文档

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读38
粉丝0
内容832