在spring boot项目中,可以通过@EnableScheduling注解和@Scheduled注解实现定时任务,也可以通过SchedulingConfigurer接口来实现定时任务。但是这两种方式不能动态添加、删除、启动、停止任务。
要实现动态增删启停定时任务功能,比较广泛的做法是集成Quartz框架。但是本人的开发原则是:在满足项目需求的情况下,尽量少的依赖其它框架,避免项目过于臃肿和复杂。
查看spring-context这个jar包中org.springframework.scheduling.ScheduledTaskRegistrar这个类的源代码,发现可以通过改造这个类就能实现动态增删启停定时任务功能。
定时任务列表页
定时任务执行日志
添加执行定时任务的线程池配置类
public class SchedulingConfig {
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;
}
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);
}
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);
}
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.
添加定时任务注册类,用来增加、删除定时任务。
public class CronTaskRegistrar implements DisposableBean {
private final Map < Runnable, ScheduledTask > scheduledTasks = new ConcurrentHashMap <>( 16);
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;
}
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.
添加定时任务示例类
( "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.
定时任务数据库表设计
定时任务数据库表设计
添加定时任务实体类
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.
新增定时任务
新增定时任务
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项目启动完成后,加载数据库里状态为正常的定时任务。
public class SysJobRunner implements CommandLineRunner {
private static final Logger logger = LoggerFactory. getLogger( SysJobRunner. class);
private ISysJobRepository sysJobRepository;
private CronTaskRegistrar cronTaskRegistrar;
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
public class SpringContextUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
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://blog.51cto.com/u_15437298/4689890