動態化定時任務主要是為了方便任務的實時開啟、暫停、重啟、停止。
下面主要記錄下具體實現步驟:
本例quartz的版本
<!--定時任務框架-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>複製程式碼
1.自定義實現動態任務工廠類
程式碼 如下
該類的作用是用來統一化管理任務的開啟、暫停、重啟、停止。
注意:該類使用了@Componemt註解,因此專案配置必須保證DynamicJobFactory的包能被註解掃描到,
程式碼中直接使用了job的class名稱作為了job名稱,因此加了一個jobName字首,它並沒有其它含義
#省略了包名
import org.quartz.*;
import org.quartz.impl.triggers.CronTriggerImpl;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class DynamicJobFactory {
@Resource
private Scheduler scheduler;
/**
* 新增job
*
* @param className 類名
* @param cronExpression cron表示式
* @throws Exception
*/
public void addJob(String className, String cronExpression) throws Exception {
Class clazz = Class.forName(className);
JobDetail jobDetail = JobBuilder.newJob(clazz)
.withIdentity("JobName:" + className, Scheduler.DEFAULT_GROUP)
.withDescription("A test job")
//如果需要給任務傳遞引數,可以用setJobData來設定引數
.setJobData(new JobDataMap())
.build();
CronTriggerImpl cronTrigger = new CronTriggerImpl();
cronTrigger.setName("JobTrigger:" + className);
cronTrigger.setCronExpression(cronExpression);
JobKey jobKey = new JobKey("JobName:" + className, Scheduler.DEFAULT_GROUP);
if(!scheduler.checkExists(jobKey)){
scheduler.scheduleJob(jobDetail, cronTrigger);
}
}
/**
* 暫停job
*
* @param className 類名
* @throws Exception
*/
public void pause(String className) throws Exception {
JobKey jobKey = new JobKey("JobName:" + className, Scheduler.DEFAULT_GROUP);
scheduler.pauseJob(jobKey);
}
/**
* 重啟job
*
* @param className
* @throws Exception
*/
public void resume(String className) throws Exception {
JobKey jobKey = new JobKey("JobName:" + className, Scheduler.DEFAULT_GROUP);
scheduler.resumeJob(jobKey);
}
/**
* 停止job
*
* @param className
* @throws Exception
*/
public void stop(String className) throws Exception {
JobKey jobKey = new JobKey("JobName:" + className, Scheduler.DEFAULT_GROUP);
scheduler.deleteJob(jobKey);
}
/**
* 修改job的執行時間
*
* @param className
* @param cronExpression
* @throws Exception
*/
public void updateJobTime(String className, String cronExpression) throws Exception {
TriggerKey triggerKey = new TriggerKey("JobTrigger:" + className,Scheduler.DEFAULT_GROUP);
CronTriggerImpl trigger = (CronTriggerImpl) scheduler.getTrigger(triggerKey);
if (trigger == null) {
return;
}
String oldTime = trigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(cronExpression)) {
trigger.setCronExpression(cronExpression);
scheduler.rescheduleJob(triggerKey, trigger);
}
}
/**
* 獲取job資訊
* @param className
* @return
* @throws Exception
*/
public JobDetail getJobDetail(String className) throws Exception {
JobKey jobKey = new JobKey("JobName:" + className, Scheduler.DEFAULT_GROUP);
JobDetail detail = scheduler.getJobDetail(jobKey);
return detail;
}
/**
* 啟動所有的任務
*
* @throws SchedulerException
*/
public void startJobs() throws SchedulerException {
scheduler.start();
}
/**
* shutdown所有的任務
*
* @throws SchedulerException
*/
public void shutdownJobs() throws SchedulerException {
if (!scheduler.isShutdown()) {
scheduler.shutdown();
}
}
}
複製程式碼
2.自定義一個任務
public class MyJob extends QuartzJobBean {
private static final Logger logger = LoggerFactory.getLogger(OpenConnectionJob.class);
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
if(logger.isDebugEnabled()){
logger.debug("Validate Job Running task thread:{} ",Thread.currentThread().getName());
}
JobDetail jobDetail = context.getJobDetail();
JobDataMap dataMap = jobDetail.getJobDataMap();
System.out.println("jobData:"+ JSON.toJSONString(dataMap));
JobKey jobKey = jobDetail.getKey();
System.out.println("jobName:"+jobKey.getName());
}
}複製程式碼
在MyJob中如果再用Spring 註解注入我們自己所寫的服務時,將會出現注入失敗,需要編寫額外的程式碼時實現自動注入
public class JobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//呼叫父類的方法
Object jobInstance = super.createJobInstance(bundle);
//進行注入
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}複製程式碼
3.通過controller來測試任務
注意:涉及的CommonResult請自定修改為自己喜歡的返回方式
@RestController
@RequestMapping("job")
public class JobController {
@Resource
private DynamicJobFactory dynamicJobFactory;
String className = "com.hunao.log.Job.MyJob";
String cronExpression = "0/3 * * * * ?";
/**
* 新增一個新任務
* @return
*/
@RequestMapping(value = "/add")
public CommonResult add(){
CommonResult result = new CommonResult();
try {
dynamicJobFactory.addJob(className,cronExpression);
result.setMessage("新增動態任務成功!");
result.setSuccess(true);
}catch (Exception e){
e.printStackTrace();
result.setMessage("新增任務出現異常");
}
return result;
}
/**
* 暫停任務
* @return
*/
@RequestMapping(value="/pause")
public CommonResult pause(){
CommonResult result = new CommonResult();
try {
dynamicJobFactory.pause(className);
result.setMessage("暫停任務成功!");
result.setSuccess(true);
}catch (Exception e){
e.printStackTrace();
result.setMessage("暫停任務出現異常");
}
return result;
}
/**
* 重啟任務
* @return
*/
@RequestMapping(value="/resume")
public CommonResult resume(){
CommonResult result = new CommonResult();
try {
dynamicJobFactory.resume(className);
result.setMessage("重啟任務成功!");
result.setSuccess(true);
}catch (Exception e){
e.printStackTrace();
result.setMessage("重啟任務出現異常");
}
return result;
}
/**
* 停止任務
* @return
*/
@RequestMapping(value = "/stop")
public CommonResult stop(){
CommonResult result = new CommonResult();
try{
dynamicJobFactory.stop(className);
result.setMessage("任務停止成功");
result.setSuccess(true);
}catch (Exception e){
e.printStackTrace();
result.setMessage("任務停止失敗");
}
return result;
}
/**
* 修改任務執行時間
* @return
*/
@RequestMapping(value = "/update")
public CommonResult update(){
CommonResult result = new CommonResult();
try {
dynamicJobFactory.updateJobTime(className,"0/30 * * * * ?");
result.setMessage("更新任務執行時間成功");
result.setSuccess(true);
}catch (Exception e){
e.printStackTrace();
result.setMessage("更新任務執行時間失敗");
}
return result;
}
/**
* 檢視任務狀態
* @return
*/
@RequestMapping(value = "/detail")
public CommonResult getJobDetail(){
CommonResult result = new CommonResult();
JobDetail jobDetail;
try {
jobDetail = dynamicJobFactory.getJobDetail(className);
result.setMessage("獲取任務資訊成功");
result.setSuccess(true);
if(null != jobDetail){
//JobDetail中的JobBuilder是不能序列化的。因此不能放JobDetail
result.setData(jobDetail.getDescription());
}
}catch (Exception e){
e.printStackTrace();
result.setMessage("獲訊息異常");
}
return result;
}
}複製程式碼
4.配置quartz
編寫一個配置檔案叫spring-quartz.xml(ps:叫什麼名字並不重要,重要的是根據功能切分配置檔案,方便識別)
裡面的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--自動注入工廠-->
<bean id="jobFactory" class="com.hunao.log.Job.JobFactory"/>
<!-- 總配置 -->
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobFactory" ref="jobFactory"></property>
<!-- 載入 quartz.properties-->
<property name="configLocation" value="classpath:quartz.properties"/>
</bean>
</beans>複製程式碼
注意:quartz.properties是quartz框架預設載入的配置檔案,我們可以在該配置檔案中自定義quartz的任務執行緒數,因此也說明quartz預設就是多執行緒來跑定時任務的,如果需要單執行緒執行的,則需要將jobDetail的concurrent屬性改為false
quartz.properties中的大致內容:
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
org.quartz.scheduler.instanceName: DefaultQuartzScheduler
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 16
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
org.quartz.jobStore.misfireThreshold: 60000
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore複製程式碼
5.在web.xml中載入spring-quartz.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring-quartz.xml
</param-value>
</context-param>複製程式碼
6.啟動web服務測試動態定時任務
在瀏覽器中資料:http://localhost:8080/xx/job/add回車後新增一個定時任務,然後觀察控制檯就能看到MyJob的輸出列印了,同理想要控制任務的執行,從瀏覽器端輸入相應url即可。
最後提示:
本例是在一個配置好的spring mvc專案想構建起來的,因此需要保證專案的web環境正常,對於
DynamicJobFactory這個裡面只有四個基礎方法,有時候並不能滿足實際需求,需要新增一些額外的操作介面,例如:動態修改正在執行任務的cron表示式(修改任務執行時間)、start或者shutdown所有的任務等等
在真實專案場景中,可將class名稱和cron表示式從資料庫讀取過來,實現任務的靈活配置