Striveonger

vuePress-theme-reco Mr.Lee    2015 - 2026
Striveonger Striveonger
主页
分类
  • 笔记
  • 文章
  • 工具
  • 读书/工具
标签
时间轴
简历
author-avatar

Mr.Lee

282

Article

153

Tag

主页
分类
  • 笔记
  • 文章
  • 工具
  • 读书/工具
标签
时间轴
简历

在Spring中实现任务调度自由

vuePress-theme-reco Mr.Lee    2015 - 2026

在Spring中实现任务调度自由

Mr.Lee 2026-06-04 11:16:23 Spring

早就该整理成文章了, 今天看之间的代码想到的....就来水一篇吧

之前要用定时任务, 常规的做法是不是要在Application 类上加@EnableScheduling注解, 然后在任务方法上@Scheduled(cron = "${sync.interval.cron}")

这种方式不能灵活的取消或改变任务内容.

所以...就有下面的尝试

❯ tree own-ext
own-ext
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── striveonger
│   │   │           └── common
│   │   │               └── ext
│   │   │                   ├── config
│   │   │                   │   └── ExtAutoConfiguration.java
│   │   │                   ├── expr
│   │   │                   │   └── package-info.java
│   │   │                   └── schedule
│   │   │                       └── ScheduleRegistrar.java
└─  └─  └── resources
            └── META-INF
                └── spring
                    └── org.springframework.boot.autoconfigure.AutoConfiguration.imports

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

org.springframework.boot.autoconfigure.AutoConfiguration.imports 这可以 SpringBoot 自动配置的关键

# 自动装载
com.striveonger.common.ext.config.ExtAutoConfiguration
1
2

# 自动装载类

/**
 * @author Mr.Lee
 * @since 2024-10-27 13:19
 */
@AutoConfiguration
@AutoConfigurationPackage(basePackages = {"com.striveonger.common.ext.*"})
@EnableScheduling
public class ExtAutoConfiguration {
    private final Logger log = LoggerFactory.getLogger(ExtAutoConfiguration.class);

    /**
     * 定时任务注册器
     */
    @Bean
    public ScheduledTaskRegistrar scheduledTaskRegistrar() {
        return new ScheduledTaskRegistrar();
    }

    /**
     * 定时任务注解处理器
     */
    @Bean
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationBeanPostProcessor(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        return new ScheduledAnnotationBeanPostProcessor(scheduledTaskRegistrar);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 定时任务注册器

/**
 * @author Mr.Lee
 * @since 2025-10-15 15:58
 */
@Component
public class ScheduleRegistrar {
    private final Logger log = LoggerFactory.getLogger(ScheduleRegistrar.class);

    /**
     * 定时任务注册器
     */
    @Resource
    private ScheduledTaskRegistrar registrar;

    /**
     * 已注册的定时任务(分布式环境下 可以考虑在Redis中做存储抢号执行的操作, 谁抢到key的锁, 谁来执行这次的任务)
     */
    private final Map<String, ScheduledTask> tasks = new ConcurrentHashMap<>();

    /**
     * 注册定时任务
     * @param key 任务键
     * @param cron 定时表达式
     * @param runnable 任务 Runnable
     */
    public void register(String key, String cron, Runnable runnable) {
        synchronized (key.intern()) {
            if (tasks.containsKey(key)) {
                log.warn("Schedule already registered: {}", key);
                throw new OwnException("Schedule already registered: " + key);
            }
            log.info("Register schedule: {} -> {}", key, cron);
            ScheduledTask task = registrar.scheduleCronTask(new CronTask(runnable, cron));
            tasks.put(key, task);
        }
    }

    /**
     * 取消注册定时任务
     * @param key 任务键
     */
    public void unregister(String key) {
        synchronized (key.intern()) {
            log.info("Unregister schedule: {}", key);
            ScheduledTask task = tasks.remove(key);

            if (task != null) {
                task.cancel();
            }
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# 单元测试

@SpringBootTest(classes = {ExtAutoConfiguration.class, ScheduleRegistrar.class})
public class ScheduleRegistrarTest {

    private final Logger log = LoggerFactory.getLogger(ScheduleRegistrarTest.class);
    
    @Resource
    private ScheduleRegistrar registrar;

    @Test
    public void test() {
        registrar.register("a", "*/5 * * * * ?", () -> log.info("a"));
        registrar.register("b", "*/5 * * * * ?", () -> log.info("b"));
        ThreadKit.sleep(15, TimeUnit.SECONDS);
        registrar.unregister("a");
        ThreadKit.sleep(10, TimeUnit.SECONDS);
        registrar.unregister("b");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

测试结果:

2026-06-04 21:52:10.801  INFO 62145 --- [           main] c.s.c.ext.schedule.ScheduleRegistrar     :[  46]: Register schedule: a -> */5 * * * * ?
2026-06-04 21:52:10.804  INFO 62145 --- [           main] o.s.s.config.TaskSchedulerRouter         :[ 225]: No TaskScheduler/ScheduledExecutorService bean found for scheduled processing
2026-06-04 21:52:10.805  INFO 62145 --- [           main] c.s.c.ext.schedule.ScheduleRegistrar     :[  46]: Register schedule: b -> */5 * * * * ?
2026-06-04 21:52:15.005  INFO 62145 --- [pool-3-thread-1] c.s.c.e.schedule.ScheduleRegistrarTest   :[  26]: b
2026-06-04 21:52:15.006  INFO 62145 --- [pool-3-thread-1] c.s.c.e.schedule.ScheduleRegistrarTest   :[  25]: a
2026-06-04 21:52:20.003  INFO 62145 --- [pool-3-thread-1] c.s.c.e.schedule.ScheduleRegistrarTest   :[  25]: a
2026-06-04 21:52:20.004  INFO 62145 --- [pool-3-thread-1] c.s.c.e.schedule.ScheduleRegistrarTest   :[  26]: b
2026-06-04 21:52:25.005  INFO 62145 --- [pool-3-thread-1] c.s.c.e.schedule.ScheduleRegistrarTest   :[  26]: b
2026-06-04 21:52:25.006  INFO 62145 --- [pool-3-thread-1] c.s.c.e.schedule.ScheduleRegistrarTest   :[  25]: a
2026-06-04 21:52:25.808  INFO 62145 --- [           main] c.s.c.ext.schedule.ScheduleRegistrar     :[  58]: Unregister schedule: a
2026-06-04 21:52:30.001  INFO 62145 --- [pool-3-thread-1] c.s.c.e.schedule.ScheduleRegistrarTest   :[  26]: b
2026-06-04 21:52:35.004  INFO 62145 --- [pool-3-thread-1] c.s.c.e.schedule.ScheduleRegistrarTest   :[  26]: b
2026-06-04 21:52:35.814  INFO 62145 --- [           main] c.s.c.ext.schedule.ScheduleRegistrar     :[  58]: Unregister schedule: b
1
2
3
4
5
6
7
8
9
10
11
12
13