diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java new file mode 100644 index 000000000..29287bdd8 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java @@ -0,0 +1,149 @@ +package cn.hutool.extra.spring; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.IdUtil; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; + +/** + * Spring 动态定时任务封装 + *
    + *
  1. 创建定时任务
  2. + *
  3. 修改定时任务
  4. + *
  5. 取消定时任务
  6. + *
  7. 高级操作
  8. + *
+ * 参考:Spring doc + * + * @author JC + * @date 03/13 + */ +@Component +public class SpringCronUtil { + /** + * 任务调度器 + */ + private static TaskScheduler taskScheduler; + + /** + * ID 与 Future 绑定 + */ + private static final Map> TASK_FUTURE = MapUtil.newConcurrentHashMap(); + + /** + * ID 与 Runnable 绑定 + */ + private static final Map TASK_RUNNABLE = MapUtil.newConcurrentHashMap(); + + /** + * 加入定时任务 + * + * @param task 任务 + * @param expression 定时任务执行时间的cron表达式 + * @return 定时任务ID + */ + public static String schedule(Runnable task, String expression) { + String id = IdUtil.fastUUID(); + return schedule(id, task, expression); + } + + /** + * 加入定时任务 + * + * @param id 定时任务ID + * @param expression 定时任务执行时间的cron表达式 + * @param task 任务 + * @return 定时任务ID + */ + public static String schedule(Serializable id, Runnable task, String expression) { + ScheduledFuture schedule = taskScheduler.schedule(task, new CronTrigger(expression)); + TASK_FUTURE.put(id, schedule); + TASK_RUNNABLE.put(id, task); + return id.toString(); + } + + /** + * 修改定时任务 + * + * @param id 定时任务ID + * @param expression 定时任务执行时间的cron表达式 + * @return 是否修改成功,{@code false}表示未找到对应ID的任务 + */ + public static boolean update(Serializable id, String expression) { + if (!TASK_FUTURE.containsKey(id)) { + return false; + } + ScheduledFuture future = TASK_FUTURE.get(id); + if (future == null) { + return false; + } + future.cancel(true); + schedule(id, TASK_RUNNABLE.get(id), expression); + return true; + } + + /** + * 移除任务 + * + * @param schedulerId 任务ID + * @return 是否移除成功,{@code false}表示未找到对应ID的任务 + */ + public static boolean cancel(Serializable schedulerId) { + if (!TASK_FUTURE.containsKey(schedulerId)) { + return false; + } + ScheduledFuture future = TASK_FUTURE.get(schedulerId); + boolean cancel = future.cancel(false); + if (cancel) { + TASK_FUTURE.remove(schedulerId); + TASK_RUNNABLE.remove(schedulerId); + } + return cancel; + } + + @Resource + public void setTaskScheduler(TaskScheduler taskScheduler) { + SpringCronUtil.taskScheduler = taskScheduler; + } + + /** + * @return 获得Scheduler对象 + */ + public static TaskScheduler getScheduler() { + return taskScheduler; + } + + /** + * 获得当前运行的所有任务 + * + * @return 所有任务 + */ + public static List getAllTask() { + if (CollUtil.isNotEmpty(TASK_FUTURE.keySet())) { + return new ArrayList<>(TASK_FUTURE.keySet()); + } + return new ArrayList<>(); + } + + /** + * 取消所有的任务 + */ + public static void destroy() { + for (ScheduledFuture future : TASK_FUTURE.values()) { + if (future != null) { + future.cancel(true); + } + } + TASK_FUTURE.clear(); + TASK_RUNNABLE.clear(); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java new file mode 100644 index 000000000..1f31d0832 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java @@ -0,0 +1,31 @@ +package cn.hutool.extra.spring.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +/** + * 可自行配置任务线程池, 修改默认参数 + * + * @author JC + * @date 03/13 + */ +@Configuration +@EnableScheduling +public class SpringCronConfig { + @Bean + @ConditionalOnMissingBean(value = TaskScheduler.class) + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + // 任务线程池初始化 + scheduler.setThreadNamePrefix("TaskScheduler-"); + scheduler.setPoolSize(Runtime.getRuntime().availableProcessors() / 3 + 1); + + // 保证能立刻丢弃运行中的任务 + scheduler.setRemoveOnCancelPolicy(true); + return scheduler; + } +} diff --git a/hutool-extra/src/main/resources/META-INF/spring.factories b/hutool-extra/src/main/resources/META-INF/spring.factories index 7b08ebdf8..0f0032ef8 100644 --- a/hutool-extra/src/main/resources/META-INF/spring.factories +++ b/hutool-extra/src/main/resources/META-INF/spring.factories @@ -1,3 +1,5 @@ # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -cn.hutool.extra.spring.SpringUtil \ No newline at end of file +cn.hutool.extra.spring.SpringUtil,\ +cn.hutool.extra.spring.config.SpringCronConfig,\ +cn.hutool.extra.spring.SpringCronUtil diff --git a/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java new file mode 100644 index 000000000..e4959da27 --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java @@ -0,0 +1,94 @@ +package cn.hutool.extra.spring; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.extra.spring.config.SpringCronConfig; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.io.Serializable; +import java.time.Duration; +import java.time.Instant; +import java.util.List; + +/** + * @author JC + * @date 03/13 + */ +@Slf4j +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = {SpringCronConfig.class, SpringCronUtil.class}) +public class SpringCronUtilTest { + /** + * 创建一个定时任务 + * 观察日志可进行验证 + */ + @Test + public void registerTask() { + String ID1 = SpringCronUtil.schedule(this::task, "0/1 * * * * ?"); + String ID2 = SpringCronUtil.schedule(888, this::task, "0/1 * * * * ?"); + log.info("taskId: {},{}", ID1, ID2); + } + + /** + * 修改一个定时任务 + */ + @Test + @SneakyThrows + public void updateTask() { + SpringCronUtil.schedule(888, this::task, "0/1 * * * * ?"); + Thread.sleep(5000); + boolean update = SpringCronUtil.update(888, "0/5 * * * * ?"); + log.info("update task result: {}", update); + } + + /** + * 取消一个定时任务 + */ + @Test + @SneakyThrows + public void cancelTask() { + SpringCronUtil.schedule(888, this::task, "0/1 * * * * ?"); + Thread.sleep(5000); + boolean cancel = SpringCronUtil.cancel(888); + log.info("cancel task result: {}", cancel); + } + + /** + * 高级用法 + * 参考:Spring doc + */ + @Test + public void senior() { + TaskScheduler scheduler = SpringCronUtil.getScheduler(); + // 给定时间 开始, 间隔时间.. + scheduler.scheduleAtFixedRate(this::task, Instant.now(), Duration.ofMinutes(10)); + // ... + } + + /** + * 取消全部定时任务 + * 查看当前所有的任务 + */ + @After + @SneakyThrows + public void cancelAll() { + Thread.sleep(10000); + List allTask = SpringCronUtil.getAllTask(); + log.info("allTask: {}", allTask); + + SpringCronUtil.destroy(); + + allTask = SpringCronUtil.getAllTask(); + log.info("allTask: {}", allTask); + } + + private void task() { + log.info("information only.. (date:{})", DateUtil.now()); + } +}