本文主要詳細介紹了SpringBoot架構下搭配Quartz動態定時任務的使用,定時任務表達式配置在數據庫中。
支持查看任務狀態,動態修改任務時間,停止任務等。啟動類啟動后任務自啟動,一個字,方便!下面我們來看看如何實現:
按我的步驟一步一步來即可,先在springboot項目建一個quartz模塊,配置好配置文件(服務名等,可參照你其他模塊的配置文件),然后執行以下步驟
1.前置准備
1.1數據庫創建任務表
CREATE TABLE `scheduled_task` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`task_key` varchar(128) NOT NULL COMMENT '任務key值(使用bean名稱)',
`task_desc` varchar(128) DEFAULT NULL COMMENT '任務描述',
`task_cron` varchar(128) NOT NULL COMMENT '任務表達式',
`init_start_flag` int(2) NOT NULL DEFAULT '1' COMMENT '程序初始化是否啟動 1 是 0 否',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
PRIMARY KEY (`id`),
UNIQUE KEY `uniqu_task_key` (`task_key`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
1.2添加初始化測試數據
INSERT INTO `cron`.`scheduled_task`(`id`, `task_key`, `task_desc`, `task_cron`, `init_start_flag`, `create_time`, `update_time`) VALUES (1, 'scheduledTask01', '定時任務01', '0/1 * * * * ?', 1, NOW(), NOW());
INSERT INTO `cron`.`scheduled_task`(`id`, `task_key`, `task_desc`, `task_cron`, `init_start_flag`, `create_time`, `update_time`) VALUES (2, 'scheduledTask02', '定時任務02', '0/1 * * * * ?', 0, NOW(), NOW());
INSERT INTO `cron`.`scheduled_task`(`id`, `task_key`, `task_desc`, `task_cron`, `init_start_flag`, `create_time`, `update_time`) VALUES (3, 'scheduledTask03', '定時任務03', '0/1 * * * * ?', 1, NOW(), NOW());
我這里corn配置的是每分鍾執行一次,目的:方便自測
1.3創建Maven項目並添加Maven依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</version>
</parent>
<!--版本號 -->
<properties>
<fastjson.version>1.2.47</fastjson.version>
<mybatis-spring-boot.version>1.3.1</mybatis-spring-boot.version>
<mybatis-spring-boot-starter.version>1.3.0</mybatis-spring-boot-starter.version>
<mybatis-plus.version>2.1-gamma</mybatis-plus.version>
<druid.version>1.1.2</druid.version>
</properties>
<dependencies>
<!--依賴管理 -->
<dependency><!--添加Web依賴 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><!--添加Mybatis依賴 -->
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot.version}</version>
</dependency>
<dependency><!--添加MySql依賴 -->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency><!--添加Test依賴 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- json 工具 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
</dependencies>
1.4添加配置文件application.properties
server.port=8081
spring.datasource.url:jdbc:mysql://localhost:3306/cron?useSSL=false
spring.datasource.username:root
spring.datasource.password:root
1.5添加SpringBoot啟動類
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2.任務調度
2.1創建定時任務線程池,初始化任務Map
注:任務map,存的是你的任務信息,項目啟動即把這些任務初始化
import com.scheduled.dynamic.enums.ScheduledTaskEnum;
import com.scheduled.dynamic.service.ScheduledTaskJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.Map;
@Configuration
public class ScheduledTaskConfig {
/**
* 日志
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTaskConfig.class);
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
LOGGER.info("創建定時任務調度線程池 start");
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(20);
threadPoolTaskScheduler.setThreadNamePrefix("taskExecutor-");
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
LOGGER.info("創建定時任務調度線程池 end");
return threadPoolTaskScheduler;
}
/**
* 初始化定時任務Map
* key :任務key
* value : 執行接口實現
*/
@Bean(name = "scheduledTaskJobMap")
public Map<String, ScheduledTaskJob> scheduledTaskJobMap() {
return ScheduledTaskEnum.initScheduledTask();
}
}
2.2創建任務實體Bean
public class ScheduledTaskBean {
/**
* 任務key值 唯一
*/
private String taskKey;
/**
* 任務描述
*/
private String taskDesc;
/**
* 任務表達式
*/
private String taskCron;
/**
* 程序初始化是否啟動 1 是 0 否
*/
private Integer initStartFlag;
/**
* 當前是否已啟動
*/
private boolean startFlag;
//省略 get/set get/set你可以加@Data就行了
2.3Mapper接口
/**
* 定時任務表 mapper
* 這里我就用注解寫了,偷偷懶
*/
@Mapper
public interface ScheduledTaskMapper {
/**
* 根據key 獲取 任務信息
*/
@Select("select task_key as taskKey,task_desc as taskDesc,task_cron as taskCron,init_start_flag as initStartFlag from scheduled_task where task_key = '${taskKey}' ")
ScheduledTaskBean getByKey(@Param("taskKey") String taskKey);
/**
* 獲取程序初始化需要自啟的任務信息
*/
@Select("select task_key as taskKey,task_desc as taskDesc,task_cron as taskCron,init_start_flag as initStartFlag from scheduled_task where init_start_flag=1 ")
List<ScheduledTaskBean> getAllNeedStartTask();
/**
* 獲取所有任務
*/
@Select("select task_key as taskKey,task_desc as taskDesc,task_cron as taskCron,init_start_flag as initStartFlag from scheduled_task ")
List<ScheduledTaskBean> getAllTask();
}
2.4創建調度任務公共父接口
/**
* 調度任務公共父接口
*/
public interface ScheduledTaskJob extends Runnable {
}
2.5實現父接口,重寫run方法
注:ScheduledTask02和ScheduledTask03,我沒寫了,你復制01復制一下,把名字改了就行
import com.scheduled.dynamic.service.ScheduledTaskJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 任務 01
*/
public class ScheduledTask01 implements ScheduledTaskJob {
/**
* 日志
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTask01.class);
@Override
public void run() {
LOGGER.info("ScheduledTask => 01 run 當前線程名稱 {} ", Thread.currentThread().getName());
}
}
2.6創建定時任務枚舉類
注:方便管理任務
import com.scheduled.dynamic.service.ScheduledTaskJob;
import com.scheduled.dynamic.task.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 定時任務枚舉值
* 注:key 需要與數據庫保持一致!!!(非常重要,不然報錯讓你懷疑人生)
*/
public enum ScheduledTaskEnum {
/**
* 任務1
*/
TASK_01("scheduledTask01", new ScheduledTask01()),
/**
* 任務2
*/
TASK_02("scheduledTask02", new ScheduledTask02()),
/**
* 任務3
*/
TASK_03("scheduledTask03", new ScheduledTask03()),
;
/**
* 定時任務key
*/
private String taskKey;
/**
* 定時任務 執行實現類
*/
private ScheduledTaskJob scheduledTaskJob;
ScheduledTaskEnum(String taskKey, ScheduledTaskJob scheduledTaskJob) {
this.taskKey = taskKey;
this.scheduledTaskJob = scheduledTaskJob;
}
/**
* 初始化 所有任務
*/
public static Map<String, ScheduledTaskJob> initScheduledTask() {
if (ScheduledTaskEnum.values().length < 0) {
return new ConcurrentHashMap<>();
}
Map<String, ScheduledTaskJob> scheduledTaskJobMap = new ConcurrentHashMap<>();
for (ScheduledTaskEnum taskEnum : ScheduledTaskEnum.values()) {
scheduledTaskJobMap.put(taskEnum.taskKey, taskEnum.scheduledTaskJob);
}
return scheduledTaskJobMap;
}
}
2.7功能核心接口
/**
* 定時任務接口
*/
public interface ScheduledTaskService {
/**
* 所有任務列表
*/
List<ScheduledTaskBean> taskList();
/**
* 根據任務key 啟動任務
*/
Boolean start(String taskKey);
/**
* 根據任務key 停止任務
*/
Boolean stop(String taskKey);
/**
* 根據任務key 重啟任務
*/
Boolean restart(String taskKey);
/**
* 程序啟動時初始化 ==> 啟動所有正常狀態的任務
*/
void initAllTask(List<ScheduledTaskBean> scheduledTaskBeanList);
}
/**
* 定時任務實現
*/
@Service
public class ScheduledTaskServiceImpl implements ScheduledTaskService {
/**
* 日志
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTaskServiceImpl.class);
@Autowired
private ScheduledTaskMapper taskMapper;
/**
* 可重入鎖
*/
private ReentrantLock lock = new ReentrantLock();
/**
* 定時任務線程池
*/
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
/**
* 所有定時任務存放Map
* key :任務 key
* value:任務實現
*/
@Autowired
@Qualifier(value = "scheduledTaskJobMap")
private Map<String, ScheduledTaskJob> scheduledTaskJobMap;
/**
* 存放已經啟動的任務map
*/
private Map<String, ScheduledFuture> scheduledFutureMap = new ConcurrentHashMap<>();
/**
* 所有任務列表
*/
@Override
public List<ScheduledTaskBean> taskList() {
LOGGER.info(">>>>>> 獲取任務列表開始 >>>>>> ");
//數據庫查詢所有任務 => 未做分頁
List<ScheduledTaskBean> taskBeanList = taskMapper.getAllTask();
if (CollectionUtils.isEmpty(taskBeanList)) {
return new ArrayList<>();
}
for (ScheduledTaskBean taskBean : taskBeanList) {
String taskKey = taskBean.getTaskKey();
//是否啟動標記處理
taskBean.setStartFlag(this.isStart(taskKey));
}
LOGGER.info(">>>>>> 獲取任務列表結束 >>>>>> ");
return taskBeanList;
}
/**
* 根據任務key 啟動任務
*/
@Override
public Boolean start(String taskKey) {
LOGGER.info(">>>>>> 啟動任務 {} 開始 >>>>>>", taskKey);
//添加鎖放一個線程啟動,防止多人啟動多次
lock.lock();
LOGGER.info(">>>>>> 添加任務啟動鎖完畢");
try {
//校驗是否已經啟動
if (this.isStart(taskKey)) {
LOGGER.info(">>>>>> 當前任務已經啟動,無需重復啟動!");
return false;
}
//校驗任務是否存在
if (!scheduledTaskJobMap.containsKey(taskKey)) {
return false;
}
//根據key數據庫獲取任務配置信息
ScheduledTaskBean scheduledTask = taskMapper.getByKey(taskKey);
//啟動任務
this.doStartTask(scheduledTask);
} finally {
// 釋放鎖
lock.unlock();
LOGGER.info(">>>>>> 釋放任務啟動鎖完畢");
}
LOGGER.info(">>>>>> 啟動任務 {} 結束 >>>>>>", taskKey);
return true;
}
/**
* 根據 key 停止任務
*/
@Override
public Boolean stop(String taskKey) {
LOGGER.info(">>>>>> 進入停止任務 {} >>>>>>", taskKey);
//當前任務實例是否存在
boolean taskStartFlag = scheduledFutureMap.containsKey(taskKey);
LOGGER.info(">>>>>> 當前任務實例是否存在 {}", taskStartFlag);
if (taskStartFlag) {
//獲取任務實例
ScheduledFuture scheduledFuture = scheduledFutureMap.get(taskKey);
//關閉實例
scheduledFuture.cancel(true);
}
LOGGER.info(">>>>>> 結束停止任務 {} >>>>>>", taskKey);
return taskStartFlag;
}
/**
* 根據任務key 重啟任務
*/
@Override
public Boolean restart(String taskKey) {
LOGGER.info(">>>>>> 進入重啟任務 {} >>>>>>", taskKey);
//先停止
this.stop(taskKey);
//再啟動
return this.start(taskKey);
}
/**
* 程序啟動時初始化 ==> 啟動所有正常狀態的任務
*/
@Override
public void initAllTask(List<ScheduledTaskBean> scheduledTaskBeanList) {
LOGGER.info("程序啟動 ==> 初始化所有任務開始 !size={}", scheduledTaskBeanList.size());
if (CollectionUtils.isEmpty(scheduledTaskBeanList)) {
return;
}
for (ScheduledTaskBean scheduledTask : scheduledTaskBeanList) {
//任務 key
String taskKey = scheduledTask.getTaskKey();
//校驗是否已經啟動
if (this.isStart(taskKey)) {
continue;
}
//啟動任務
this.doStartTask(scheduledTask);
}
LOGGER.info("程序啟動 ==> 初始化所有任務結束 !size={}", scheduledTaskBeanList.size());
}
/**
* 執行啟動任務
*/
private void doStartTask(ScheduledTaskBean scheduledTask) {
//任務key
String taskKey = scheduledTask.getTaskKey();
//定時表達式
String taskCron = scheduledTask.getTaskCron();
//獲取需要定時調度的接口
ScheduledTaskJob scheduledTaskJob = scheduledTaskJobMap.get(taskKey);
LOGGER.info(">>>>>> 任務 [ {} ] ,cron={}", scheduledTask.getTaskDesc(), taskCron);
ScheduledFuture scheduledFuture = threadPoolTaskScheduler.schedule(scheduledTaskJob,
new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
CronTrigger cronTrigger = new CronTrigger(taskCron);
return cronTrigger.nextExecutionTime(triggerContext);
}
});
//將啟動的任務放入 map
scheduledFutureMap.put(taskKey, scheduledFuture);
}
/**
* 任務是否已經啟動
*/
private Boolean isStart(String taskKey) {
//校驗是否已經啟動
if (scheduledFutureMap.containsKey(taskKey)) {
if (!scheduledFutureMap.get(taskKey).isCancelled()) {
return true;
}
}
return false;
}
}
2.8 定時任務Controller
/**
* 定時任務 controller
*/
@RestController
@RequestMapping("/scheduled")
public class ScheduledController {
@Autowired
private ScheduledTaskService scheduledTaskService;
/**
* 所有任務列表
*/
@RequestMapping("/taskList")
public List<ScheduledTaskBean> taskList() {
return scheduledTaskService.taskList();
}
/**
* 根據任務key => 啟動任務
*/
@RequestMapping("/start")
public String start(@RequestParam("taskKey") String taskKey) {
scheduledTaskService.start(taskKey);
return "start success";
}
/**
* 根據任務key => 停止任務
*/
@RequestMapping("/stop")
public String stop(@RequestParam("taskKey") String taskKey) {
scheduledTaskService.stop(taskKey);
return "stop success";
}
/**
* 根據任務key => 重啟任務
*/
@RequestMapping("/restart")
public String restart(@RequestParam("taskKey") String taskKey) {
scheduledTaskService.restart(taskKey);
return "restart success";
}
}
到這里,恭喜你,CV大法又精進了,咳咳,偏了偏了,是已經可以實現,動態管理任務了,包括動態修改啟動表達式,停止任務,啟動任務,重啟任務等。
但是,還有一個問題,項目重啟后,需要人工來啟動需要啟動的任務。誰不想偷點懶呢,讓它自啟動多好,打包扔上去啟動就行了,誰還一個一個去啟,麻煩,於是我們再加一步:
2.9實現項目啟動時自啟動
注:SpringBoot給我們提供了兩個接口來幫助我們實現這種需求。這兩個接口分別為CommandLineRunner和ApplicationRunner。他們的執行時機為容器啟動完成的時候。
/**
* @see @Order注解的執行優先級是按value值從小到大順序。
* 項目啟動完畢后開啟需要自啟的任務
*/
@Component
@Order(value = 1)
public class ScheduledTaskRunner implements ApplicationRunner {
/**
* 日志
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTaskRunner.class);
@Autowired
private ScheduledTaskMapper taskMapper;
@Autowired
private ScheduledTaskService scheduledTaskService;
/**
* 程序啟動完畢后,需要自啟的任務
*/
@Override
public void run(ApplicationArguments applicationArguments) throws Exception {
LOGGER.info(" >>>>>> 項目啟動完畢, 開啟 => 需要自啟的任務 開始!");
List<ScheduledTaskBean> scheduledTaskBeanList = taskMapper.getAllNeedStartTask();
scheduledTaskService.initAllTask(scheduledTaskBeanList);
LOGGER.info(" >>>>>> 項目啟動完畢, 開啟 => 需要自啟的任務 結束!");
}
}
到這里,算是沒啥問題了,只需要把你的任務模塊代碼寫到run方法里就行
福利:
https://github.com/mengq0815/spring-boot-example/tree/master/spring-boot-scheduled-task
---------------------
作者:君子博學而日參省乎己
來源:博客園