天天看點

Spring MVC、Spring Boot 分别內建 Quartz 動态定時任務

為什麼要用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]
【移除全部定時】
           

最後

謝謝大家的參考、閱讀;

可能大家在實際寫代碼的過程中有不一樣的異常出錯,大家可以留言一起讨論學習。

繼續閱讀