為什麼要用Quartz
我們都知道Spring Boot自帶定時器:
@Scheduled(cron="0/1 * * * * ?")
(記得在啟動類加上注解
@EnableScheduling
),這樣就已經實作了定時器的功能。
那麼為什麼還要用
Quartz
呢? Quartz更容易管理,在多任務時,更友善的去動态配置,能實作動态關閉開啟效果。
Quartz表達式(Cron)
cron="0/1 * * * * ?"
名稱 | 是否必須 | 允許值 | 特殊字元 |
---|---|---|---|
秒 | 是 | 0-59 | - * / |
分 | 是 | 0-59 | - * / |
時 | 是 | 0-23 | - * / |
日 | 是 | 1-31 | - * ? / L W C |
月 | 是 | 1-12 或 JAN-DEC | - * / |
周 | 是 | 1-7 或 SUN-SAT | - * ? / L C # |
年 | 否 | 空 或 1970-2099 | - * / |
特殊字元
特殊字元 | 意義 |
---|---|
* | 表示所有值 |
? | 表示未說明的值,即不關心它為何值 |
- | 表示一個指定的範圍 |
, | 表示附加一個可能值 |
/ | 符号前表示開始時間,符号後表示每次遞增的值 |
執行個體
表達式 | 意義 |
---|---|
“0 0 12 * * ?” | 每天中午12點觸發 |
“0 15 10 ? * *” | 每天上午10:15觸發 |
“0 15 10 * * ?” | 每天上午10:15觸發 |
“0 15 10 * * ? *” | 每天上午10:15觸發 |
“0 15 10 * * ? 2005” | 2005年的每天上午10:15觸發 |
“0 * 14 * * ?” | 在每天下午2點到下午2:59期間的每1分鐘觸發 |
“0 0/5 14 * * ?” | 在每天下午2點到下午2:55期間的每5分鐘觸發 |
“0 0/5 14,18 * * ?” | 在每天下午2點到2:55期間和下午6點到6:55期間的每5分鐘觸發 |
“0 0-5 14 * * ?” | 在每天下午2點到下午2:05期間的每1分鐘觸發 |
“0 10,44 14 ? 3 WED” | 每年三月的星期三的下午2:10和2:44觸發 |
“0 15 10 ? * MON-FRI” | 周一至周五的上午10:15觸發 |
“0 15 10 15 * ?” | 每月15日上午10:15觸發 |
“0 15 10 L * ?” | 每月最後一日的上午10:15觸發 |
“0 15 10 ? * 6L” | 每月的最後一個星期五上午10:15觸發 |
“0 15 10 ? * 6L 2002-2005” | 2002年至2005年的每月的最後一個星期五上午10:15觸發 |
“0 15 10 ? * 6#3” | 每月的第三個星期五上午10:15觸發 |
代碼走起
pom檔案添加依賴包
<!-- quartz 定時器 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
初始注入scheduler
Spring Boot
是在啟動類加上
@Bean
的注入
/**
* 初始注入scheduler
* @return
* @throws SchedulerException
*/
@Bean
public Scheduler scheduler() throws SchedulerException {
SchedulerFactory schedulerFactoryBean = new StdSchedulerFactory();
return schedulerFactoryBean.getScheduler();
}
Spring MVC
是在
applicationContext.xml
加上下面代碼
<!-- ============================================定時任務========================= -->
<!-- -------------這段是為了項目啟動自動執行定時任務-----開始----------- -->
<!-- 這個類用來做需要完成的業務-->
<bean id="myJob2" class="com.xxx.xxxx.xxxxx.myJob2"></bean>
<!--定義調用對象和調用對象的方法,這個配置和普通的一樣的,id是JobDetail的名字-->
<bean id="jobtask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!-- 調用的類 -->
<property name="targetObject" ref="myJob2" />
<!-- 調用類中的方法 -->
<property name="targetMethod" value="doSomething" />
<!-- 是否并發 -->
<property name ="concurrent" value ="false" />
</bean>
<!--定義觸發時間 ,這邊就不同了,這裡必須将時間設定成無限長,因為我們要去讀取資料庫的時間來做為定時器的觸發時間-->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean ">
<property name="jobDetail" ref="jobtask" />
<!-- cron表達式 -->
<property name="cronExpression" value="0/1 * * * * ?" />
</bean>
<!-- -------------這段是為了項目啟動自動執行定時任務-------結束--------------- -->
<!-- 總管理類 如果将lazy-init='false'那麼容器啟動就會執行排程程式 -->
<bean id="startQuertz" lazy-init="true" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<!--<ref bean="cronTrigger" />-->
</list>
</property>
</bean>
<!--這個類是用來設定觸發時間的, startJobs方法啟動排程容器,然後按照上面觸發器每隔1s執行所配置的myJob2.doSomething()方法 -->
<bean id="quartzManager" class="com.xxx.xxxxx.xxxx.QuartzScheduler" lazy-init="false" init-method="startJobs" >
<!--這個對象一定要注入,這樣類才能進行管理,還有在類型要用get set方法,不然會報錯。-->
<property name="scheduler" ref="startQuertz" />
</bean>
<!-- =================================定時任務================================== -->
注意:
其實不管是Spring Boot 還是Spring MVC 哪種方式,都是為了交給Spring這個容器進行管理
建立一個任務排程處理類
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
/**
* 任務排程處理
* Created by LMD on 2019/3/28.
*/
@Configuration
public class QuartzScheduler {
// 任務排程
@Autowired
private Scheduler scheduler;
/**
* @Description: 添加一個定時任務
*
* @param jobName 任務名
* @param jobGroupName 任務組名
* @param triggerName 觸發器名
* @param triggerGroupName 觸發器組名
* @param jobClass 任務
* @param cron 時間設定,參考quartz說明文檔
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void addJob(String jobName, String jobGroupName,
String triggerName, String triggerGroupName, Class jobClass, String cron) {
try {
// 任務名,任務組,任務執行類
JobDetail jobDetail= JobBuilder.newJob(jobClass).withIdentity(jobName, jobGroupName).build();
// 觸發器
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
// 觸發器名,觸發器組
triggerBuilder.withIdentity(triggerName, triggerGroupName);
triggerBuilder.startNow();
// 觸發器時間設定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
// 建立Trigger對象
CronTrigger trigger = (CronTrigger) triggerBuilder.build();
// 排程容器設定JobDetail和Trigger
scheduler.scheduleJob(jobDetail, trigger);
// 啟動
if (!scheduler.isShutdown()) {
scheduler.start();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description: 修改一個任務的觸發時間
*
* @param jobName
* @param jobGroupName
* @param triggerName 觸發器名
* @param triggerGroupName 觸發器組名
* @param cron 時間設定,參考quartz說明文檔
*/
public void modifyJobTime(String jobName,
String jobGroupName, String triggerName, String triggerGroupName, String cron) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
if (trigger == null) {
return;
}
String oldTime = trigger.getCronExpression();
if (!oldTime.equalsIgnoreCase(cron)) {
/** 方式一 :調用 rescheduleJob 開始 */
// 觸發器
TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
// 觸發器名,觸發器組
triggerBuilder.withIdentity(triggerName, triggerGroupName);
triggerBuilder.startNow();
// 觸發器時間設定
triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron));
// 建立Trigger對象
trigger = (CronTrigger) triggerBuilder.build();
// 方式一 :修改一個任務的觸發時間
scheduler.rescheduleJob(triggerKey, trigger);
/** 方式一 :調用 rescheduleJob 結束 */
/** 方式二:先删除,然後在建立一個新的Job */
//JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(jobName, jobGroupName));
//Class<? extends Job> jobClass = jobDetail.getJobClass();
//removeJob(jobName, jobGroupName, triggerName, triggerGroupName);
//addJob(jobName, jobGroupName, triggerName, triggerGroupName, jobClass, cron);
/** 方式二 :先删除,然後在建立一個新的Job */
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description: 移除一個任務
*
* @param jobName 任務名
* @param jobGroupName 任務組名
* @param triggerName 觸發器名
* @param triggerGroupName 觸發器組名
*/
public void removeJob(String jobName, String jobGroupName,
String triggerName, String triggerGroupName) {
try {
TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
scheduler.pauseTrigger(triggerKey);// 停止觸發器
scheduler.unscheduleJob(triggerKey);// 移除觸發器
scheduler.deleteJob(JobKey.jobKey(jobName, jobGroupName));// 删除任務
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description:啟動所有定時任務
*/
public void startJobs() {
try {
scheduler.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* @Description:關閉所有定時任務
*/
public void shutdownJobs() {
try {
if (!scheduler.isShutdown()) {
scheduler.shutdown();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
測試
@Autowired
private QuartzScheduler quartzScheduler;
/**
* 開啟 Quartz
* @return
* @throws MyException
*/
@RequestMapping(value = "/system/quartzAll")
public void quartzAll() throws MyException {
try {
System.out.println("【系統啟動】開始......");
System.out.println("【增加job1啟動】開始(每1秒輸出一次)...");//0/10 * * * * ?
quartzScheduler.addJob("job1", "job1", "job1", "job1", MyJobAppoint.class, "0/1 * * * * ? *");
Thread.sleep(5000);
System.out.println("【修改job1時間】開始(每2秒輸出一次)...");
quartzScheduler.modifyJobTime("job1", "job1", "job1", "job1", "0/2 * * * * ?");
Thread.sleep(10000);
System.out.println("【移除job1定時】開始...");
quartzScheduler.removeJob("job1", "job1", "job1", "job1");
System.out.println("【增加job2啟動】開始(每1秒輸出一次)...");//0/10 * * * * ?
quartzScheduler.addJob("job2", "job2", "job2", "job2", MyJobAppoint.class, "0/1 * * * * ? *");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 關閉 Quartz
* @return
* @throws MyException
*/
@RequestMapping(value = "/system/closeQuartz")
@ResponseBody
public void closeQuartz() throws MyException {
try {
System.out.println("【移除全部定時】");//0/10 * * * * ?
// 關掉任務排程容器
quartzScheduler.shutdownJobs();
} catch (Exception e) {
e.printStackTrace();
}
}
運作項目,調用對應的接口,控制台就列印如下日志:
2019-03-28 10:56:02.448 INFO 22948 --- [-nio-80-exec-10] c.s.companycms.config.LoginInterceptor : request: 請求位址 path [/api/system/quartzAll] uri [/api/system/quartzAll]
【系統啟動】開始......
【增加job1啟動】開始(每1秒輸出一次)...
2019-03-28 10:56:02.470 INFO 22948 --- [-nio-80-exec-10] org.quartz.core.QuartzScheduler : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
2019-03-28 10:56:02: MyJobAppoint 1 doing......
2019-03-28 10:56:03: MyJobAppoint 1 doing......
2019-03-28 10:56:04: MyJobAppoint 1 doing......
2019-03-28 10:56:05: MyJobAppoint 1 doing......
2019-03-28 10:56:06: MyJobAppoint 1 doing......
2019-03-28 10:56:07: MyJobAppoint 1 doing......
【修改job1時間】開始(每2秒輸出一次)...
2019-03-28 10:56:08: MyJobAppoint 1 doing......
2019-03-28 10:56:10: MyJobAppoint 1 doing......
2019-03-28 10:56:12: MyJobAppoint 1 doing......
2019-03-28 10:56:14: MyJobAppoint 1 doing......
2019-03-28 10:56:16: MyJobAppoint 1 doing......
【移除job1定時】開始...
【增加job2啟動】開始(每1秒輸出一次)...
2019-03-28 10:56:17.474 INFO 22948 --- [-nio-80-exec-10] org.quartz.core.QuartzScheduler : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
2019-03-28 10:56:17: MyJobAppoint 1 doing......
2019-03-28 10:56:18: MyJobAppoint 1 doing......
2019-03-28 10:56:19: MyJobAppoint 1 doing......
2019-03-28 10:56:20: MyJobAppoint 1 doing......
2019-03-28 10:56:21: MyJobAppoint 1 doing......
2019-03-28 10:56:22: MyJobAppoint 1 doing......
2019-03-28 10:56:23: MyJobAppoint 1 doing......
2019-03-28 10:56:24: MyJobAppoint 1 doing......
2019-03-28 10:56:25: MyJobAppoint 1 doing......
2019-03-28 10:56:26: MyJobAppoint 1 doing......
2019-03-28 10:56:27: MyJobAppoint 1 doing......
2019-03-28 10:56:28: MyJobAppoint 1 doing......
2019-03-28 10:56:29: MyJobAppoint 1 doing......
2019-03-28 10:56:30: MyJobAppoint 1 doing......
2019-03-28 10:56:31: MyJobAppoint 1 doing......
2019-03-28 10:56:32: MyJobAppoint 1 doing......
2019-03-28 10:56:32.550 INFO 22948 --- [p-nio-80-exec-1] c.s.companycms.config.LoginInterceptor : request: 請求位址 path [/api/system/closeQuartz] uri [/api/system/closeQuartz]
【移除全部定時】
最後
謝謝大家的參考、閱讀;
可能大家在實際寫代碼的過程中有不一樣的異常出錯,大家可以留言一起讨論學習。