電子商務(wù)網(wǎng)站建設(shè)個人總結(jié)推廣學(xué)院seo教程
前段時間編寫了一篇博客SpringBoot 動態(tài)操作定時任務(wù)(啟動、停止、修改執(zhí)行周期,該篇博客還是幫助了很多同學(xué)。
但是該篇博客中的方法有些不足的地方:
- 只能通過前端控制器controller手動注冊任務(wù)?!揪唧w的應(yīng)該是我們提前配置好我們的任務(wù),配置完成后讓springboot應(yīng)用幫我們加載并注冊任務(wù)】
- 無法打印任務(wù)的啟動時間、下次執(zhí)行時間及任務(wù)耗時等情況。
所以針對以上的不足我對該方案進行了整改。
新方案涉及4個類:
- TaskSchedulerConfig 任務(wù)調(diào)度器及任務(wù)注冊器的配置
- ScheduledTaskRegistrar 任務(wù)注冊器,用于將定時任務(wù)注冊到調(diào)度器中
- ScheduledTaskHolder 任務(wù)的包裝類
- ITask 任務(wù)抽象類
提醒:該定時任務(wù)只能實現(xiàn)單機環(huán)境下使用,無法在分布式環(huán)境下使用。
一、核心實現(xiàn)如下:
1、配置類TaskSchedulerConfig
該配置類往spring容器中注冊了兩個bean,第一個bean為org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler,該bean為spring提供的基于線程池的任務(wù)調(diào)度器,其本質(zhì)是持有一個java.util.concurrent.ScheduledThreadPoolExecutor,這里需要注意初始化ScheduledThreadPoolExecutor的時候最大線程數(shù)為Integer.MAX_VALU。
setRemoveOnCancelPolicy方法如果設(shè)置為true,則在中斷當(dāng)前執(zhí)行的任務(wù)后會將其從任務(wù)等待隊列(如果隊列中有該任務(wù))中移除。
第二個bean為ScheduledTaskRegistrar即我們的任務(wù)注冊器,該bean需要持有一個任務(wù)調(diào)度器并且需要配置任務(wù)列表【目前任務(wù)列表需要手動配置如果同學(xué)們想增強的話可以自己實現(xiàn)注解掃描配置或者包掃描配置】。
package com.bbs.config.scheduled;import com.bbs.task.MoniterTask;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;import java.util.Arrays;/*** @Author whh* @Date: 2023/03/15/ 20:15* @description*/
@Configuration
public class TaskSchedulerConfig {/**** 本質(zhì)是ScheduledThreadPoolExecutor的包裝,其中初始化ScheduledThreadPoolExecutor的構(gòu)造函數(shù)如下* 特別的要注意最大線程數(shù)為 Integer.MAX_VALUE** public ScheduledThreadPoolExecutor(int corePoolSize,* ThreadFactory threadFactory,* RejectedExecutionHandler handler) {* super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,* new DelayedWorkQueue(), threadFactory, handler);* }* @return*/@Beanpublic ThreadPoolTaskScheduler threadPoolTaskScheduler(){ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();threadPoolTaskScheduler.setPoolSize(3);threadPoolTaskScheduler.setRemoveOnCancelPolicy(true);return threadPoolTaskScheduler;}@Beanpublic ScheduledTaskRegistrar taskRegistrar(ThreadPoolTaskScheduler scheduler){ScheduledTaskRegistrar taskRegistrar = new ScheduledTaskRegistrar(scheduler);MoniterTask moniterTask = new MoniterTask("*/30 * * * * ?");moniterTask.setTaskName("監(jiān)控任務(wù)");moniterTask.setTaskDescription("每隔30s對機器進行監(jiān)控");taskRegistrar.setTaskes(Arrays.asList(moniterTask));return taskRegistrar;}
}
2、任務(wù)注冊器ScheduledTaskRegistrar
該任務(wù)注冊器實現(xiàn)了org.springframework.beans.factory.InitializingBean接口,所以可以達到配置完成后讓springboot應(yīng)用幫我們加載并注冊任務(wù)。該bean主要有5個方法,包括注冊任務(wù)、查詢所有任務(wù)、立即執(zhí)行任務(wù)、暫停任務(wù)、任務(wù)恢復(fù)。其中立即執(zhí)行任務(wù)并未使用調(diào)度器的線程執(zhí)行而是使用用戶線程執(zhí)行。
package com.bbs.config.scheduled;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.util.StringUtils;import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;/*** @Author whh* @Date: 2023/03/15/ 19:44* @description 任務(wù)注冊*/
public class ScheduledTaskRegistrar implements InitializingBean {private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTaskRegistrar.class);/*** 任務(wù)調(diào)度器*/private ThreadPoolTaskScheduler scheduler;/*** 任務(wù)列表*/private List<ITask> taskes;private final Map<String, ScheduledTaskHolder> register = new ConcurrentHashMap<>();public ScheduledTaskRegistrar(ThreadPoolTaskScheduler scheduler) {this.scheduler = scheduler;}/*** 注冊任務(wù)*/public void register() {for (ITask task : taskes) {ScheduledFuture<?> future = this.scheduler.schedule(task, new CronTrigger(task.getCron()));ScheduledTaskHolder holder = new ScheduledTaskHolder();holder.setScheduledFuture(future);holder.setTask(task);register.put(task.getClass().getName(), holder);}}/*** 查詢所有任務(wù)** @return*/public Collection<ScheduledTaskHolder> list() {return register.values();}/*** 立即執(zhí)行任務(wù)** @param className*/public void start(String className) {ScheduledTaskHolder holder = register.get(className);if (holder != null) {holder.getTask().run();}}/*** 暫停任務(wù)** @param className*/public void pause(String className) {ScheduledTaskHolder holder = register.get(className);if (holder != null) {if(holder.terminate()){return;}ScheduledFuture<?> future = holder.getScheduledFuture();future.cancel(true);}}/***重啟任務(wù)* @param className* @param cron*/public void restart(String className,String cron){ScheduledTaskHolder holder = register.get(className);if (holder != null) {if(!holder.terminate()){//暫停原任務(wù)holder.getScheduledFuture().cancel(true);}ITask task = holder.getTask();if(!StringUtils.isEmpty(cron)){task.setCron(cron);}ScheduledFuture<?> future = this.scheduler.schedule(task, new CronTrigger(task.getCron()));holder.setScheduledFuture(future);}}public void setTaskes(List<ITask> taskes) {this.taskes = taskes;}private void log(){register.forEach((k,v)->{LOGGER.info("register {} complete,cron {}",k,v.getTask().getCron());});}@Overridepublic void afterPropertiesSet(){register();log();}
}
3、任務(wù)包裝類ScheduledTaskHolder
任務(wù)包裝類包含具體任務(wù)的實例、任務(wù)執(zhí)行的ScheduledFuture以及任務(wù)的運行狀態(tài)。
package com.bbs.config.scheduled;import java.util.concurrent.ScheduledFuture;/*** @Author whh* @Date: 2023/03/15/ 19:45* @description*/
public class ScheduledTaskHolder {/*** 具體任務(wù)*/private ITask task;/***result of scheduling*/private ScheduledFuture<?> scheduledFuture;public ITask getTask() {return task;}public void setTask(ITask task) {this.task = task;}public ScheduledFuture<?> getScheduledFuture() {return scheduledFuture;}public void setScheduledFuture(ScheduledFuture<?> scheduledFuture) {this.scheduledFuture = scheduledFuture;}public boolean terminate() {return scheduledFuture.isCancelled();}
}
4、 任務(wù)抽象類ITask
任務(wù)抽象類ITask實現(xiàn)了Runnable接口同時提供了抽象方法execute用于自定義任務(wù)實現(xiàn),同時對任務(wù)進行了增強,增加了任務(wù)執(zhí)行信息的打印。
package com.bbs.config.scheduled;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.SimpleTriggerContext;import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Date;/*** @Author whh* @Date: 2023/03/15/ 19:46* @description* 任務(wù)的抽象類* 用于做切面打印任務(wù)執(zhí)行的時間,耗時等信息*/
public abstract class ITask implements Runnable{private static final Logger LOGGER = LoggerFactory.getLogger("ITask");private String cron;private String taskName;private String taskDescription;public ITask(String cron) {this.cron = cron;}@Overridepublic void run() {LocalDateTime start = LocalDateTime.now();execute();LocalDateTime end = LocalDateTime.now();Duration duration = Duration.between(start, end);long millis = duration.toMillis();Date date = new CronTrigger(this.cron).nextExecutionTime(new SimpleTriggerContext());LOGGER.info("任務(wù):[{}]執(zhí)行完畢,開始時間:{},結(jié)束時間:{},耗時:{}ms,下次執(zhí)行時間{}",this.taskName, start,end,millis,date);}/*** 用戶的任務(wù)實現(xiàn)*/public abstract void execute();public String getCron() {return cron;}public void setCron(String cron) {this.cron = cron;}public String getTaskName() {return taskName;}public void setTaskName(String taskName) {this.taskName = taskName;}public String getTaskDescription() {return taskDescription;}public void setTaskDescription(String taskDescription) {this.taskDescription = taskDescription;}
}
二、前端控制器TaskController
package com.bbs.task.controller;import com.bbs.config.scheduled.ITask;
import com.bbs.config.scheduled.ScheduledTaskRegistrar;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.SimpleTriggerContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;/*** @Author whh* @Date: 2023/03/15/ 21:18* @description*/
@RestController
@RequestMapping("/task")
public class TaskController {@Autowiredprivate ScheduledTaskRegistrar scheduledTaskRegistrar;@GetMappingpublic List<Map<String,Object>> listTask(){return scheduledTaskRegistrar.list().stream().map(e->{Map<String,Object> temp = new HashMap<>();ITask task = e.getTask();temp.put("任務(wù)名",task.getTaskName());temp.put("任務(wù)描述",task.getTaskDescription());temp.put("cron表達式",task.getCron());temp.put("任務(wù)狀態(tài)",e.terminate()?"已停止":"運行中");temp.put("任務(wù)類名",task.getClass().getName());temp.put("下次執(zhí)行時間",new CronTrigger(task.getCron()).nextExecutionTime(new SimpleTriggerContext()));return temp;}).collect(Collectors.toList());}@PostMapping("/pause")public void pause(String className){scheduledTaskRegistrar.pause(className);}@PostMapping("/start")public void start(String className){scheduledTaskRegistrar.start(className);}@PostMapping("/restart")public void restart(String className,String cron){scheduledTaskRegistrar.restart(className,cron);}
}
三、自定義任務(wù)實現(xiàn)
package com.bbs.task;import com.bbs.config.scheduled.ITask;/*** @Author whh* @Date: 2023/03/15/ 21:16* @description*/
public class MonitorTask extends ITask {public MonitorTask(String cron) {super(cron);}@Overridepublic void execute() {System.out.println("hello world ....");}
}
四、測試
1、應(yīng)用啟動時控制臺會打印我們注冊的任務(wù)信息
2、任務(wù)執(zhí)行情況
3、前端控制器查詢?nèi)蝿?wù)
調(diào)用暫停API后任務(wù)停止執(zhí)行,重啟任務(wù)后任務(wù)又重新開始執(zhí)行。