6年老猿帶你掌握Spring Boot實現定時任務的動態增删啟停

一點唐城 2021-11-25 17:34:13
年老 掌握 spring boot 增删

在spring boot項目中,可以通過@EnableScheduling注解和@Scheduled注解實現定時任務,也可以通過SchedulingConfigurer接口來實現定時任務。但是這兩種方式不能動態添加、删除、啟動、停止任務。

要實現動態增删啟停定時任務功能,比較廣泛的做法是集成Quartz框架。但是本人的開發原則是:在滿足項目需求的情况下,盡量少的依賴其它框架,避免項目過於臃腫和複雜。

查看spring-context這個jar包中org.springframework.scheduling.ScheduledTaskRegistrar這個類的源代碼,發現可以通過改造這個類就能實現動態增删啟停定時任務功能。

6年老猿帶你掌握Spring Boot實現定時任務的動態增删啟停_spring boot

定時任務列錶頁

6年老猿帶你掌握Spring Boot實現定時任務的動態增删啟停_spring_02

定時任務執行日志

添加執行定時任務的線程池配置類


@Configuration
public class SchedulingConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
// 定時任務執行線程池核心線程數
taskScheduler. setPoolSize( 4);
taskScheduler. setRemoveOnCancelPolicy( true);
taskScheduler. setThreadNamePrefix( "TaskSchedulerThreadPool-");
return taskScheduler;
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

添加ScheduledFuture的包裝類。ScheduledFuture是ScheduledExecutorService定時任務線程池的執行結果。


public final class ScheduledTask {

volatile ScheduledFuture <?> future;

/**
* 取消定時任務
*/
public void cancel() {
ScheduledFuture <?> future = this. future;
if ( future != null) {
future. cancel( true);
}
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

添加Runnable接口實現類,被定時任務線程池調用,用來執行指定bean裏面的方法。


public class SchedulingRunnable implements Runnable {

private static final Logger logger = LoggerFactory. getLogger( SchedulingRunnable. class);

private String beanName;

private String methodName;

private String params;

public SchedulingRunnable( String beanName, String methodName) {
this( beanName, methodName, null);
}

public SchedulingRunnable( String beanName, String methodName, String params) {
this. beanName = beanName;
this. methodName = methodName;
this. params = params;
}

@Override
public void run() {
logger. info( "定時任務開始執行 - bean:{},方法:{},參數:{}", beanName, methodName, params);
long startTime = System. currentTimeMillis();

try {
Object target = SpringContextUtils. getBean( beanName);

Method method = null;
if ( StringUtils. isNotEmpty( params)) {
method = target. getClass(). getDeclaredMethod( methodName, String. class);
} else {
method = target. getClass(). getDeclaredMethod( methodName);
}

ReflectionUtils. makeAccessible( method);
if ( StringUtils. isNotEmpty( params)) {
method. invoke( target, params);
} else {
method. invoke( target);
}
} catch ( Exception ex) {
logger. error( String. format( "定時任務執行异常 - bean:%s,方法:%s,參數:%s ", beanName, methodName, params), ex);
}

long times = System. currentTimeMillis() - startTime;
logger. info( "定時任務執行結束 - bean:{},方法:{},參數:{},耗時:{} 毫秒", beanName, methodName, params, times);
}

@Override
public boolean equals( Object o) {
if ( this == o) return true;
if ( o == null || getClass() != o. getClass()) return false;
SchedulingRunnable that = ( SchedulingRunnable) o;
if ( params == null) {
return beanName. equals( that. beanName) &&
methodName. equals( that. methodName) &&
that. params == null;
}

return beanName. equals( that. beanName) &&
methodName. equals( that. methodName) &&
params. equals( that. params);
}

@Override
public int hashCode() {
if ( params == null) {
return Objects. hash( beanName, methodName);
}

return Objects. hash( beanName, methodName, params);
}
}
  • 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.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.

添加定時任務注册類,用來增加、删除定時任務。

@Component
public class CronTaskRegistrar implements DisposableBean {

private final Map < Runnable, ScheduledTask > scheduledTasks = new ConcurrentHashMap <>( 16);

@Autowired
private TaskScheduler taskScheduler;

public TaskScheduler getScheduler() {
return this. taskScheduler;
}

public void addCronTask( Runnable task, String cronExpression) {
addCronTask( new CronTask( task, cronExpression));
}

public void addCronTask( CronTask cronTask) {
if ( cronTask != null) {
Runnable task = cronTask. getRunnable();
if ( this. scheduledTasks. containsKey( task)) {
removeCronTask( task);
}

this. scheduledTasks. put( task, scheduleCronTask( cronTask));
}
}

public void removeCronTask( Runnable task) {
ScheduledTask scheduledTask = this. scheduledTasks. remove( task);
if ( scheduledTask != null)
scheduledTask. cancel();
}

public ScheduledTask scheduleCronTask( CronTask cronTask) {
ScheduledTask scheduledTask = new ScheduledTask();
scheduledTask. future = this. taskScheduler. schedule( cronTask. getRunnable(), cronTask. getTrigger());

return scheduledTask;
}


@Override
public void destroy() {
for ( ScheduledTask task : this. scheduledTasks. values()) {
task. cancel();
}

this. scheduledTasks. clear();
}
}
  • 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.

添加定時任務示例類


@Component( "demoTask")
public class DemoTask {
public void taskWithParams( String params) {
System. out. println( "執行有參示例任務:" + params);
}

public void taskNoParams() {
System. out. println( "執行無參示例任務");
}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

定時任務數據庫錶設計

6年老猿帶你掌握Spring Boot實現定時任務的動態增删啟停_java_03

定時任務數據庫錶設計

添加定時任務實體類


public class SysJobPO {
/**
* 任務ID
*/
private Integer jobId;
/**
* bean名稱
*/
private String beanName;
/**
* 方法名稱
*/
private String methodName;
/**
* 方法參數
*/
private String methodParams;
/**
* cron錶達式
*/
private String cronExpression;
/**
* 狀態(1正常 0暫停)
*/
private Integer jobStatus;
/**
* 備注
*/
private String remark;
/**
* 創建時間
*/
private Date createTime;
/**
* 更新時間
*/
private Date updateTime;

public Integer getJobId() {
return jobId;
}

public void setJobId( Integer jobId) {
this. jobId = jobId;
}

public String getBeanName() {
return beanName;
}

public void setBeanName( String beanName) {
this. beanName = beanName;
}

public String getMethodName() {
return methodName;
}

public void setMethodName( String methodName) {
this. methodName = methodName;
}

public String getMethodParams() {
return methodParams;
}

public void setMethodParams( String methodParams) {
this. methodParams = methodParams;
}

public String getCronExpression() {
return cronExpression;
}

public void setCronExpression( String cronExpression) {
this. cronExpression = cronExpression;
}

public Integer getJobStatus() {
return jobStatus;
}

public void setJobStatus( Integer jobStatus) {
this. jobStatus = jobStatus;
}

public String getRemark() {
return remark;
}

public void setRemark( String remark) {
this. remark = remark;
}

public Date getCreateTime() {
return createTime;
}

public void setCreateTime( Date createTime) {
this. createTime = createTime;
}

public Date getUpdateTime() {
return updateTime;
}

public void setUpdateTime( Date updateTime) {
this. updateTime = updateTime;
}

}
  • 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.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.

新增定時任務

6年老猿帶你掌握Spring Boot實現定時任務的動態增删啟停_spring_04

新增定時任務


boolean success = sysJobRepository. addSysJob( sysJob);
if ( ! success)
return OperationResUtils. fail( "新增失敗");
else {
if ( sysJob. getJobStatus(). equals( SysJobStatus. NORMAL. ordinal())) {
SchedulingRunnable task = new SchedulingRunnable( sysJob. getBeanName(), sysJob. getMethodName(), sysJob. getMethodParams());
cronTaskRegistrar. addCronTask( task, sysJob. getCronExpression());
}
}

return OperationResUtils. success();
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

修改定時任務,先移除原來的任務,再啟動新任務


boolean success = sysJobRepository. editSysJob( sysJob);
if ( ! success)
return OperationResUtils. fail( "編輯失敗");
else {
//先移除再添加
if ( existedSysJob. getJobStatus(). equals( SysJobStatus. NORMAL. ordinal())) {
SchedulingRunnable task = new SchedulingRunnable( existedSysJob. getBeanName(), existedSysJob. getMethodName(), existedSysJob. getMethodParams());
cronTaskRegistrar. removeCronTask( task);
}

if ( sysJob. getJobStatus(). equals( SysJobStatus. NORMAL. ordinal())) {
SchedulingRunnable task = new SchedulingRunnable( sysJob. getBeanName(), sysJob. getMethodName(), sysJob. getMethodParams());
cronTaskRegistrar. addCronTask( task, sysJob. getCronExpression());
}
}

return OperationResUtils. success();
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

删除定時任務


boolean success = sysJobRepository. deleteSysJobById( req. getJobId());
if ( ! success)
return OperationResUtils. fail( "删除失敗");
else{
if ( existedSysJob. getJobStatus(). equals( SysJobStatus. NORMAL. ordinal())) {
SchedulingRunnable task = new SchedulingRunnable( existedSysJob. getBeanName(), existedSysJob. getMethodName(), existedSysJob. getMethodParams());
cronTaskRegistrar. removeCronTask( task);
}
}

return OperationResUtils. success();
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

定時任務啟動/停止狀態切換

if (existedSysJob.getJobStatus().equals(SysJobStatus.NORMAL.ordinal())) {
SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
cronTaskRegistrar.addCronTask(task, existedSysJob.getCronExpression());
} else {
SchedulingRunnable task = new SchedulingRunnable(existedSysJob.getBeanName(), existedSysJob.getMethodName(), existedSysJob.getMethodParams());
cronTaskRegistrar.removeCronTask(task);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

添加實現了CommandLineRunner接口的SysJobRunner類,當spring boot項目啟動完成後,加載數據庫裏狀態為正常的定時任務。

@Service
public class SysJobRunner implements CommandLineRunner {

private static final Logger logger = LoggerFactory. getLogger( SysJobRunner. class);

@Autowired
private ISysJobRepository sysJobRepository;

@Autowired
private CronTaskRegistrar cronTaskRegistrar;

@Override
public void run( String... args) {
// 初始加載數據庫裏狀態為正常的定時任務
List < SysJobPO > jobList = sysJobRepository. getSysJobListByStatus( SysJobStatus. NORMAL. ordinal());
if ( CollectionUtils. isNotEmpty( jobList)) {
for ( SysJobPO job : jobList) {
SchedulingRunnable task = new SchedulingRunnable( job. getBeanName(), job. getMethodName(), job. getMethodParams());
cronTaskRegistrar. addCronTask( task, job. getCronExpression());
}

logger. info( "定時任務已加載完畢...");
}
}
}
  • 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.

工具類SpringContextUtils,用來從spring容器裏獲取bean

@Component
public class SpringContextUtils implements ApplicationContextAware {

private static ApplicationContext applicationContext;

@Override
public void setApplicationContext( ApplicationContext applicationContext)
throws BeansException {
SpringContextUtils. applicationContext = applicationContext;
}

public static Object getBean( String name) {
return applicationContext. getBean( name);
}

public static < T > T getBean( Class < T > requiredType) {
return applicationContext. getBean( requiredType);
}

public static < T > T getBean( String name, Class < T > requiredType) {
return applicationContext. getBean( name, requiredType);
}

public static boolean containsBean( String name) {
return applicationContext. containsBean( name);
}

public static boolean isSingleton( String name) {
return applicationContext. isSingleton( name);
}

public static Class <? extends Object > getType( String name) {
return applicationContext. getType( name);
}
}
  • 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.


版权声明
本文为[一點唐城]所创,转载请带上原文链接,感谢
https://javamana.com/2021/11/20211125173308748a.html

  1. http://lx.gongxuanwang.com/sszt/32.htm
  2. 回顾我两个月面试阿里,携程,小红书,美团,网易等等(Java岗)
  3. JavaScript高级程序设计读后感(一)之零碎知识点查漏补缺
  4. http://lx.gongxuanwang.com/sszt/7.htm
  5. #yyds干货盘点#设计模式之【工厂模式】
  6. Java * SpringBoot实现万能文件在线预览,已开源,真香
  7. Redis | 第4章 Redis中的数据库《Redis设计与实现》
  8. Redis | 第4章 Redis中的数据库《Redis设计与实现》
  9. 关于centos docker版本过低导致 is not a valid repository/tag: invalid reference format
  10. Redis 源码简洁剖析 02 - SDS 字符串
  11. 回顧我兩個月面試阿裏,攜程,小紅書,美團,網易等等(Java崗)
  12. Rétrospectivement, j'ai passé deux mois à interviewer Ali, ctrip, Little Red Book, meituan, NetEase, etc. (Java post)
  13. Docker + webhook Automation Deployment Front End Project
  14. Java技术之Spring、Hibernate框架整合方法
  15. http://lx.gongxuanwang.com/sszt/32.htm
  16. 亚马逊自己的 Linux 发行版现在完全基于 Fedora 了
  17. Redis 源码简洁剖析 02 - SDS 字符串
  18. Java技術之Spring、Hibernate框架整合方法
  19. Méthode d'intégration des cadres de printemps et d'hibernation de la technologie Java
  20. Redis source Concise Analysis 02 - SDS String
  21. La distribution Linux d'Amazon est maintenant entièrement basée sur Fedora
  22. org.springframework.web.bind.MissingServletRequestParameterException
  23. Built in constraints and functions of MySQL Foundation (2)
  24. Basic operation of MySQL Foundation (I)
  25. Introduction to Java zero foundation 3: Java data types
  26. 从零开始搭建EasyDarwin环境——Linux系统开发环境搭建Golang
  27. Redis source Concise Analysis 02 - SDS String
  28. Construire l'environnement easydarwin à partir de zéro - - construire l'environnement de développement du système Linux golang
  29. javaweb代码是正确的,但是第一行代码就报错了
  30. **** | Java | 后端开挂:3行代码写出8个接口
  31. Java || 看了大二学长写的代码,我竟开始默默的模仿了。。。
  32. Java | 手把手教你实现一个抽奖系统(Java版)
  33. Java | Manuel pour vous apprendre à mettre en œuvre un système de loterie (version Java)
  34. Java | | après avoir lu le Code que j'ai écrit en deuxième année, j'ai commencé à imiter silencieusement...
  35. Java | back - end Pending: 3 - line Code write 8 Interfaces
  36. Le Code Web Java est correct, mais la première ligne de code est incorrecte
  37. Android网络编程之Http通信
  38. Android網絡編程之Http通信
  39. Http communication for Android Network Programming
  40. 数据结构实验八 领会图的两种主要储存结构和图的基本运算算法设计
  41. Hibernate数据校验简介
  42. The story of spring
  43. Il a dépensé 270 000 yuans pour soulever Xiaopeng p7 et a parcouru 3 627 km. Le propriétaire du véhicule a partagé 6 avantages et inconvénients.
  44. 阿里蚂蚁花呗团队面试题:spring+分布式+jvm+session+redis
  45. 【Java入门100例】14.字符串排序——compareTo()
  46. 【Java入门100例】13.修改文件扩展名——字符串替换
  47. Leetcode 79. Word Search [C + + / java detailed problem]
  48. Introduction à la vérification des données hibernantes
  49. Expérience de la structure des données
  50. Spring cloud gateway practice 2: more routing configuration methods
  51. Java network programming - summary overview
  52. 基于语法树的 Java 代码自动化插桩
  53. 云原生 Spring Boot 应用配置 Prometheus + Grafana 监控(保姆级)
  54. Spring cloud gateway practice 2: more routing configuration methods
  55. Jenkins file one line of code to deploy. Net program to k8s
  56. Java network programming - summary overview
  57. Cloud Native Spring Boot application configuration Prometheus + grafana Monitoring (baby - sitter)
  58. Insertion automatique de code Java basée sur l'Arbre syntaxique
  59. Le SUV phare de Xiaopeng, Xiaopeng G9, a fait ses débuts au salon de l'automobile et s'est tenu en position C dans la nouvelle force?
  60. Docker 从入门到实践系列四 - Docker 容器编排利器 Docker Compose