Spring Quartz 实现原理与示例


JAVA 针对定时任务,有 Timer,Scheduler, Quartz 等几种实现方式,其中最常用的应该就是 Quartz 了。

本文旨在简要介绍 Quartz 的基本概念,在xml中添加定时器,以及使用JAVA代码动态地修改定时器配置。并附带了源代码,可以下载并运行。

1. Quartz的基本概念

在开始之前,我们必须了解以下的几个基本概念:Job、Trigger、Cron expression、JobStores

1.1. Job

对于任务内容的构建,我们需要创建 Job 的继承类,并实现 execute 方法。

1.2. Trigger

其作用是设置调度策略。Quartz 设计了多种类型的 Trigger,其中最常用的是 SimpleTrigger 和 CronTrigger。
SimpleTrigger 适用于在某一特定的时间以某一特定时间间隔执行多次。

CronTrigger 的用途更广,主要适用于基于日历的调度安排。例如:每星期二的 16:38:10 执行,每月一号执行,等等。本文使用的是CronTrigger。

1.3. Cron表达式

由七个字段组成:

Seconds
Minutes
Hours
Day-of-Month
Month
Day-of-Week
Year (Optional field)

举例如下:

创建一个每三小时执行的 CronTrigger,且从每小时的整点开始执行:

0 0 0/3 * * ?

这里不打算对 Cron 表达式作过多的阐述,更多的内容请查看Oracle官方文档:

http://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.htm

对于初学者,推荐使用在线的Cron生成器:
http://cron.qqe2.com

1.4. JobStores

负责对Quartz的持久化,即将任务调度的相关数据保存在数据库中。这样,在系统重启后,任务的调度状态依然存在系统中。当任务错过了触发时间时,还可以重新触发并执行(需要配置)。

好了,基础概念介绍完了以后,我们开始搭建一个工程来完成定时任务吧。

 

2. 新建 Quartz 工程

我们会新建一个 Maven 工程,并在启动Web应用之后运行定时任务。

2.1. 新建工程

在 Eclipse 中,新建 Maven 项目并引入 Jar 包

工程的相关细节,大家请直接参考源码。

2.2. 版本信息

Java 1.8
Tomcat 8.5
Spring 4.3.3.RELEASE
Quartz 2.2.1

 

 

 

 

2.3. 工程结构

2.4. Spring Quartz 的配置

主要是定义了数据源,调度工厂以及定时任务的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-4.3.xsd"
            default-autowire="no" >
    
    <!-- 读取属性文件 -->
    <context:property-placeholder location="classpath*:properties/quartz.properties" 
        ignore-unresolvable="true" />
    
    <!-- 扫描包 -->
    <context:component-scan base-package="org.stevexie" />
    
    <!-- 数据源 -->
    <bean id="quartzDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName">
            <value>${jdbc.driverclass}</value>
        </property>
        <property name="url">
            <value>${jdbc.url}</value>
        </property>
        <property name="username">
            <value>${jdbc.username}</value>
        </property>
        <property name="password">
            <value>${jdbc.password}</value>
        </property>
    </bean>
    
    <!-- ======================== 调度工厂开始 ======================== -->
    <bean id="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false" autowire="no">
        <!-- 设置持久化的数据源 -->
        <property name="dataSource">
            <ref bean="quartzDataSource" />
        </property>
        
        <!-- 设置Quartz的属性 -->
        <property name="quartzProperties">
            <props>
                <!-- 线程池配置 -->
                <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
                <!-- 初始化线程数为20 -->
                <prop key="org.quartz.threadPool.threadCount">20</prop>
                <!-- 设置作业的优先级是5 -->
                <prop key="org.quartz.threadPool.threadPriority">5</prop>
                <!-- 初始化线程数为20 -->
                <prop key="org.quartz.jobStore.misfireThreshold">60000</prop>
                
                <!-- JobStore 配置,通过数据库存储最终调度程序的配置 -->
                <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
                <prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</prop>
                
                <!-- 数据表名的前缀设置 -->
                <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>
            </props>
        </property>
        
        <!-- 设置应用初始化之后,延迟30秒再启动scheduler -->
        <property name="startupDelay" value="30" />
        
        <property name="applicationContextSchedulerContextKey" value="applicationContext" />
        
        <!-- 设置定时任务随web启动 -->
        <property name="autoStartup" value="true" />
        
        <property name="triggers">
            <list>
                <ref bean="definedInXMLcronTrigger"/>
            </list>
        </property>
    </bean>
    <!-- ======================== 调度工厂结束 ======================== -->
    
    <!-- ======================== 定时任务1 开始 ======================== -->
    <bean id="definedInXMLJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <!-- 必填项:在此处定义job detail -->
        <property name="jobClass" value="org.stevexie.jobdetail.DefinedInXMLJobDetail" />
        <!-- 必填项:在此处定义job name -->
        <property name="name" value="jobName1"></property>
        <!-- 必填项:在此处定义job group name -->
        <property name="group" value="jobGroupName1"></property>
        <!-- 选填项:设置该job是持久性的 -->
        <property name="durability" value="true" />
        <!-- 选填项:设置该job是中断后可恢复的 -->
        <property name="requestsRecovery" value="true" />
    </bean>
    
    <bean id="definedInXMLcronTrigger"
        class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <!-- 必填项:在此处定义trigger name -->
        <property name="name" value="triggerName1" />
        <!-- 必填项:在此处定义trigger group name -->
        <property name="group" value="triggerGroupName1" />
        <property name="jobDetail" ref="definedInXMLJobDetail" />
        <!-- 必填项:在此处定义定时任务时间 -->
        <property name="cronExpression" value="0/10 * * * * ? " />
        <!-- 选填项:在此处定义市区 -->
        <property name="timeZone" ref="timeZone" />
        <property name="misfireInstruction" value="1" />
    </bean>
    <!-- ======================== 定时任务1 结束 ======================== -->
    
    <!-- 时区 -->
    <bean id="timeZone" class="java.util.TimeZone" factory-method="getTimeZone">
        <constructor-arg value="GMT+08:00" />
    </bean>
</beans>
View Code

2.5. 定时任务

实现两个定时任务:一个是在应用初始化的时候启动;另一个是通过运行代码中的main方法来实现对定时任务的增删改。

2.5.1. 定时任务1

随着应用的启动而实例化,每10秒打印一条语句:

 1 package org.stevexie.jobdetail;
 2 
 3 import org.quartz.JobExecutionContext;
 4 import org.springframework.scheduling.quartz.QuartzJobBean;
 5 import org.stevexie.util.DateUtil;
 6 
 7 public class DefinedInXMLJobDetail extends QuartzJobBean {
 8     
 9     @Override
10     protected void executeInternal(JobExecutionContext context) {
11         System.out.println("执行在XML中定义好的定时任务,当前时间是:" + 
12                                         DateUtil.currentDatetime());
13     }
14 }
View Code

2.5.2. 定时任务2

与定时任务1的内容基本一致,但主要是在 JAVA 代码中被实例化并实现定时任务的增删改。

 1 package org.stevexie.jobdetail;
 2 
 3 import org.quartz.JobExecutionContext;
 4 import org.springframework.scheduling.quartz.QuartzJobBean;
 5 import org.stevexie.util.DateUtil;
 6 
 7 public class DefinedInCodeJobDetail extends QuartzJobBean {
 8     
 9     @Override
10     protected void executeInternal(JobExecutionContext context) {
11         System.out.println("执行在代码中添加的定时任务,当前时间是:" + 
12                                     DateUtil.currentDatetime());
13     }
14 }
View Code

2.6. 定时任务的增删改工具类

  1 package org.stevexie.util;
  2 
  3 import java.text.ParseException;
  4 
  5 import javax.annotation.Resource;
  6 
  7 import org.apache.commons.lang.StringUtils;
  8 import org.quartz.CronScheduleBuilder;
  9 import org.quartz.CronTrigger;
 10 import org.quartz.Job;
 11 import org.quartz.JobBuilder;
 12 import org.quartz.JobDetail;
 13 import org.quartz.JobKey;
 14 import org.quartz.Scheduler;
 15 import org.quartz.SchedulerException;
 16 import org.quartz.Trigger;
 17 import org.quartz.TriggerBuilder;
 18 import org.quartz.TriggerKey;
 19 import org.springframework.context.ApplicationContext;
 20 import org.springframework.context.support.ClassPathXmlApplicationContext;
 21 import org.springframework.stereotype.Component;
 22 import org.stevexie.jobdetail.DefinedInCodeJobDetail;
 23 import org.stevexie.jobdetail.DefinedInXMLJobDetail;
 24 
 25 @Component("quartzUtil")
 26 public class QuartzUtil {
 27     
 28     @Resource(name="quartzScheduler")
 29     private Scheduler scheduler;
 30     
 31     private static String JOB_GROUP_NAME = "ddlib";
 32     private static String TRIGGER_GROUP_NAME = "ddlibTrigger";
 33     
 34     /**
 35      * 添加一个定时任务,使用默认的任务组名,触发器名,触发器组名
 36      * @param job Job任务类实例
 37      * @param jobName job名字(请保证唯一性)
 38      * @param cronExpression cron时间表达式
 39      * @throws SchedulerException 
 40      */
 41     public void addJob(String jobName, Job job, String cronExpression)
 42             throws SchedulerException, ParseException{
 43         addJob(job, jobName, null, jobName, null, cronExpression, 5);
 44     }
 45     
 46     /**
 47      * 开始一个simpleSchedule()调度(创建一个新的定时任务)
 48      * @param job Job任务类实例
 49      * @param jobName  job名字(请保证唯一性)
 50      * @param jobGroupName  job组名(请保证唯一性)
 51      * @param cronExpression    cron时间表达式
 52      * @param triggerName   trigger名字(请保证唯一性)
 53      * @param triggerGroupName  triggerjob组名(请保证唯一性)
 54      * @throws SchedulerException 
 55      */
 56     public void addJob(Job job, String jobName, String jobGroupName, 
 57                                 String triggerName, String triggerGroupName, 
 58                                 String cronExpression, int triggerPriority) 
 59                                                 throws SchedulerException {
 60         
 61         if(StringUtils.isEmpty(jobGroupName)){
 62             jobGroupName = JOB_GROUP_NAME;
 63         }
 64         if(StringUtils.isEmpty(triggerGroupName)){
 65             triggerGroupName = TRIGGER_GROUP_NAME;
 66         }
 67         
 68         // 1、创建一个JobDetail实例,指定Quartz
 69         JobDetail jobDetail = JobBuilder.newJob(job.getClass())
 70                 // 任务执行类
 71                 .withIdentity(jobName, jobGroupName)
 72                 // 任务名,任务组
 73                 .build();
 74         
 75         // 2、创建Trigger
 76         // 优先级默认是5,数字越高优先级越高
 77         Trigger trigger = TriggerBuilder.newTrigger()
 78                 .withIdentity(triggerName, triggerGroupName).startNow()
 79                 .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
 80                 .withPriority(triggerPriority)
 81                 .build();
 82         
 83         // 3、移除job,避免因为job的重复添加导致错误
 84         this.removeJob(jobName, jobGroupName, triggerName, triggerGroupName);
 85         
 86         // 4、调度执行
 87         try {
 88             scheduler.scheduleJob(jobDetail, trigger);
 89         } catch (SchedulerException e) {
 90             e.printStackTrace();
 91             throw e;
 92         }
 93         
 94         // 4、启动  
 95         this.startSchedule();
 96     }
 97     
 98     /**
 99      * 开始任务
100      * @throws SchedulerException 
101      */
102     public void startSchedule() throws SchedulerException {
103         try {
104             if(scheduler.isShutdown()){
105                 scheduler.resumeAll();
106             } else {
107                 scheduler.start();
108             }
109         } catch (SchedulerException e) {
110             e.printStackTrace();
111             throw e;
112         }
113     }
114     
115     /**
116      * 暂停Job
117      * @param name job名字
118      * @param group  job组名
119      * @throws SchedulerException 
120      */
121     public void pauseJob(String jobName, String jobGroupName) 
122                                     throws SchedulerException {
123         JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
124         try {
125             scheduler.pauseJob(jobKey);
126         } catch (SchedulerException e) {
127             e.printStackTrace();
128             throw e;
129         }
130     }
131     
132     /**
133      * 恢复Job
134      * @param name  job名字
135      * @param group  job组名
136      * @throws SchedulerException 
137      */
138     public void resumeJob(String jobName, String jobGroupName) 
139                                     throws SchedulerException {
140         JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
141         try {
142             scheduler.resumeJob(jobKey);
143         } catch (SchedulerException e) {
144             e.printStackTrace();
145             throw e;
146         }
147     }
148     
149     /**  
150      * 修改一个任务的触发时间(使用默认的任务组名,触发器名,触发器组名) 
151      */  
152     public void modifyJobTime(String jobName, String cronExpression)
153             throws SchedulerException, ParseException {
154         rescheduleJob(jobName, null, cronExpression);
155     }
156     
157     /**
158      * 更新任务表达式
159      * @param triggerName  trigger名字
160      * @param triggerGroupName  trigger组名
161      * @param newCronExpression  cron时间表达式
162      * @throws SchedulerException 
163      */
164     public void rescheduleJob(    String triggerName, 
165                                 String triggerGroupName, 
166                                 String newCronExpression) 
167                                 throws SchedulerException {
168         
169         if(StringUtils.isBlank(triggerGroupName)) {
170             triggerGroupName = TRIGGER_GROUP_NAME;
171         }
172         
173         TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
174         
175         // 获取trigger
176         CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
177         
178         // 按新的cronExpression表达式重新构建trigger
179         trigger = trigger
180                 .getTriggerBuilder()
181                 .withIdentity(triggerKey)
182                 .withSchedule(CronScheduleBuilder.cronSchedule(newCronExpression))
183                 .build();
184         
185         // 按新的trigger重新设置job执行
186         try {
187             scheduler.rescheduleJob(triggerKey, trigger);
188         } catch (SchedulerException e) {
189             e.printStackTrace();
190             throw e;
191         }
192     }
193     
194     /** 移除一个任务和触发器(使用默认的任务组名,触发器名,触发器组名) */
195     public void removeJob(String jobName,String triggerName)
196             throws SchedulerException{
197         removeJob(jobName, null, triggerName, null);
198     }
199     
200     /** 移除一个任务和触发器  */
201     public void removeJob(String jobName, String jobGroupName, 
202             String triggerName, String triggerGroupName)
203                     throws SchedulerException {
204         
205         if(StringUtils.isEmpty(jobGroupName)) {
206             jobGroupName = JOB_GROUP_NAME;
207         }
208         if(StringUtils.isEmpty(triggerGroupName)) {
209             triggerGroupName = TRIGGER_GROUP_NAME;
210         }
211         
212         JobKey jobKey = JobKey.jobKey(jobName, jobGroupName);
213         TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroupName);
214         
215         scheduler.pauseTrigger(triggerKey);            // 停止触发器
216         scheduler.unscheduleJob(triggerKey);        // 移除触发器
217         scheduler.deleteJob(jobKey);                // 删除任务
218     }
219     
220     public static void main(String[] args) {
221         ApplicationContext context = null;
222         context = new ClassPathXmlApplicationContext("classpath:spring/spring-quartz.xml");
223         
224         QuartzUtil quartzUtil = (QuartzUtil) context.getBean("quartzUtil");
225         
226         // 删除定时器
227         try {
228             quartzUtil.removeJob(   "jobName", 
229                                     "jobGroupName", 
230                                     "triggerName", 
231                                     "triggerGroupName");
232             quartzUtil.removeJob(   "jobName1", 
233                                     "jobGroupName", 
234                                     "triggerName", 
235                                     "triggerGroupName");
236             quartzUtil.removeJob(   "jobName2", 
237                                     "jobGroupName", 
238                                     "triggerName", 
239                                     "triggerGroupName");
240         } catch (SchedulerException e1) {
241             e1.printStackTrace();
242         }
243         
244         // 添加定时器
245         try {
246             quartzUtil.addJob(new DefinedInXMLJobDetail(), 
247                     "jobName1", "jobGroupName1", 
248                     "triggerName1", "triggerGroupName1", 
249                     "0/20 * * * * ? ", 5);
250             
251             quartzUtil.addJob(new DefinedInCodeJobDetail(), 
252                     "jobName2", "jobGroupName2", 
253                     "triggerName2", "triggerGroupName2", 
254                     "0/10 * * * * ? ", 5);
255             
256         } catch (SchedulerException e2) {
257             e2.printStackTrace();
258         }
259         
260         // 修改定时器
261         try {
262             quartzUtil.rescheduleJob(   "triggerName1", 
263                                         "triggerGroupName1", 
264                                         "0/30 * * * * ? ");
265         } catch (SchedulerException e1) {
266             e1.printStackTrace();
267         }
268         
269         try {
270             Thread.sleep(100*1000);
271         } catch (InterruptedException e) {
272             e.printStackTrace();
273         }
274     }
275 }
View Code

2.7. Quartz的执行脚本

需要在数据库中建一些Quartz的表。Quartz 的 distribution 包已提供了建表语句,放在\docs\dbTables目录中。下载地址是:

http://www.quartz-scheduler.org/downloads/

本文附带的源码中也包含了定时器的 SQL 执行脚本。

 

3. 运行效果

3.1. 初次运行web项目

每十秒钟会执行一次定时任务。该任务是在XML中被定义的,随着Web应用的启动而实例化。

3.2. 修改定时任务的属性

停止 Web 项目,运行 QuartzUtil.java。结果显示,目前有两个定时任务正在运行:

    - 定时任务一,每隔30秒运行一次,并打印:执行在XML中定义好的定时任务...

    - 定时任务二,每隔10秒运行一次,并打印:执行在代码中添加的定时任务...

3.3. 再次运行 Web 项目,确认修改结果

执行结果与 3.2 一致,说明定时任务的触发时间已经成功被修改了:

 

3.4. 在数据库中,查询定时器信息,确认该调度程序已被成功地持久化。


4. 源代码下载

请移步百度网盘,下载源代码:

https://pan.baidu.com/s/1boRrS1T

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM