在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;
}
}
添加ScheduledFuture的包裝類。ScheduledFuture是ScheduledExecutorService定時任務線程池的執行結果。
public final class ScheduledTask {
volatile ScheduledFuture <?> future;
/**
* 取消定時任務
*/
public void cancel() {
ScheduledFuture <?> future = this. future;
if ( future != null) {
future. cancel( true);
}
}
}
添加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);
}
}
添加定時任務注册類,用來增加、删除定時任務。
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();
}
}
( "demoTask")
public class DemoTask {
public void taskWithParams( String params) {
System. out. println( "執行有參示例任務:" + params);
}
public void taskNoParams() {
System. out. println( "執行無參示例任務");
}
}
定時任務數據庫錶設計
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;
}
}
新增定時任務
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();
修改定時任務,先移除原來的任務,再啟動新任務
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();
删除定時任務
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();
定時任務啟動/停止狀態切換
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);
}
添加實現了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( "定時任務已加載完畢...");
}
}
}
工具類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);
}
}