文章目录
Spring Quartz 定时器任务
本文介绍了 Spring Quartz 定时器任务使用的两种方式
- 基于 XML 文件的方式
- 基于注解的方式
除此之外,还介绍了 Cron 表达式的基本使用
Spring Quartz 部分有如下一些核心概念:
- Scheduler 是一个计划调度器容器,容器里面可以有很多对儿 JobDetail 和 Trigger,当容器启动后,里面的每个 JobDetail 都会根据 Trigger 按部就班自动去执行。容器中有一个线程池,用来并行调度执行每个作业,提高容器效率。
- JobDetail 是一个可执行的工作,它本身可能是有状态的。
- Trigger 代表一个任务执行计划的配置,根据 Cron 表达式来决定什么时候去启动一个 Job。
- 当 JobDetail 和 Trigger 在 Scheduler 容器上注册后,形成了配置好的作业(JobDetail 和 Trigger 所组成的一对儿),就可以伴随容器启动儿调度执行了。
项目源码地址:
1. 环境介绍
Spring : 4.3.6.RELEASE
Quartz : 2.2.3
pom.xml 文件部分相关配置如下:
<properties>
<spring.version>4.3.6.RELEASE</spring.version>
<quartz.version>2.2.3</quartz.version>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<slf4j.version>1.7.21</slf4j.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- SPRING -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- QUARTZ -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>${quartz.version}</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>${quartz.version}</version>
</dependency>
<!-- LOG -->
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
2、基于 XML 文件的方式
2.1 创建 Job 类
使用 Quartz 常简单,只要写一个 Job 类,不需要继承或者实现任何类,包含定时器需要执行任务的代码即可,如下,我创建了一个类 XMLBasedJob.java,这里的逻辑非常简单,只是定时在控制台输出一段话。
package cn.upshi.springquartz.job;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* spring-quartz XMLBasedJob
*/
public class XMLBasedJob {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 具体执行定时器任务逻辑的方法
public void execute() {
System.out.println(sdf.format(new Date()) + " 执行了定时任务 XMLBasedJob");
}
}
2.2 配置 Spring 的 XML 文件
要使上述的 Job 类中的定时器任务生效,需要在 Spring 的配置文件里配置 4 部门内容:
- 配置 Job 类的 Bean
<!-- 自定义的Job类 --> <bean id="xmlBasedJob" class="cn.upshi.springquartz.job.XMLBasedJob" />
- 配置 JobDetail
<!-- 一个可执行的定时器 -->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!-- 指定Job类 -->
<property name="targetObject" ref="xmlBasedJob" />
<!-- 指定Job执行的方法 -->
<property name="targetMethod" value="execute" />
</bean>
- 配置 Job 触发器
<!-- Job的触发器 -->
<bean id="jobTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail" />
<!-- 每5秒运行一次 -->
<property name="cronExpression" value="0/5 * * * * ?" />
</bean>
- 配置 Job 调度器
<!-- Job调度器 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="jobTrigger" />
</list>
</property>
</bean>
至此,Spring 的配置文件就完成了
spring.xml 完整的配置文件如下:
<?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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd ">
<!-- 自动扫描(自动注入) -->
<context:component-scan base-package="cn.upshi.springquartz"/>
<!-- 自定义的Job类 -->
<bean id="xmlBasedJob" class="cn.upshi.springquartz.job.XMLBasedJob"/>
<!-- 一个可执行的定时器 -->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!-- 指定Job类 -->
<property name="targetObject" ref="xmlBasedJob"/>
<!-- 指定Job执行的方法 -->
<property name="targetMethod" value="execute"/>
</bean>
<!-- Job的触发器 -->
<bean id="jobTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail"/>
<!-- 每5秒运行一次 -->
<property name="cronExpression" value="0/5 * * * * ?"/>
</bean>
<!-- Job调度器 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="jobTrigger"/>
</list>
</property>
</bean>
</beans>
2.3 编写测试类
编写一个测试类查看运行效果
package cn.upshi.springquartz;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* spring-quartz cn.upshi.springquartz
*/
public class TestXMLBasedJob {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
}
}
执行结果如下:
2017-02-27 19:08:40 执行了定时任务 XMLBasedJob
2017-02-27 19:08:45 执行了定时任务 XMLBasedJob
2017-02-27 19:08:50 执行了定时任务 XMLBasedJob
2017-02-27 19:08:55 执行了定时任务 XMLBasedJob
2017-02-27 19:09:00 执行了定时任务 XMLBasedJob
2017-02-27 19:09:05 执行了定时任务 XMLBasedJob
2017-02-27 19:09:10 执行了定时任务 XMLBasedJob
3. 基于注解的方式
3.1 创建 Job 类
基于注解的方式比基于 XML 的方式更加简单,首先,还是需要创建一个 Job 类,如下:
package cn.upshi.springquartz.job;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* spring-quartz AnnotationBasedJob
*/
@Component
public class AnnotationBasedJob {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 执行计划,每 5 秒执行一次
@Scheduled(cron = "0/5 * * * * *")
public void execute() {
System.out.println(sdf.format(new Date()) + " 执行了定时任务 AnnotationBasedJob");
}
}
在以上代码中,使用 @Component 注解,Spring 会自动将该类加入 Spring 的容器中。 此外,最核心的一个注解是 @Scheduled,它定义了一个执行计划,每 5 秒执行一次。关于 cron 表达式的含义,将在后面进行介绍。
3.2 配置 Spring,以支持注解的方式
首先在 spring.xml 配置文件头部引入相应的命名空间
xlmns:
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation:
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
开启使用注解的配置
<!-- 基于注解方式的定时器 --> <task:annotation-driven/>
3.3 编写测试类
仍然使用 2.3 节的测试类查看运行效果
package cn.upshi.springquartz;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* spring-quartz cn.upshi.springquartz
*/
public class TestXMLBasedJob {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
}
}
执行结果如下:
2017-02-27 19:20:20 执行了定时任务 AnnotationBasedJob
2017-02-27 19:20:25 执行了定时任务 AnnotationBasedJob
2017-02-27 19:20:30 执行了定时任务 AnnotationBasedJob
2017-02-27 19:20:35 执行了定时任务 AnnotationBasedJob
2017-02-27 19:20:40 执行了定时任务 AnnotationBasedJob
2017-02-27 19:20:45 执行了定时任务 AnnotationBasedJob
2017-02-27 19:20:50 执行了定时任务 AnnotationBasedJob
...
3.4 两个 DEBUG 级别的异常问题 待发现并解决
4. Cron 表达式介绍
Cron的表达式被用来配置CronTrigger实例。 Cron的表达式是字符串,实际上是由七子表达式,描述个别细节的时间表。这些子表达式是分开的空白,代表:
- Seconds
- Minutes
- Hours
- Day-of-Month
- Month
- Day-of-Week
- Year (可选字段)
例:”0 0 9 ? * FRI” 在每星期五上午9:00 执行
名称 | 是否必须 | 允许值 | 特殊字符 |
---|---|---|---|
秒 | 是 | 0-59 | , - * / |
分 | 是 | 0-59 | , - * / |
时 | 是 | 0-23 | , - * / |
日 | 是 | 1-31 | ,-*?/LWC |
月 | 是 | 1-12 或 JAN-DEC | , - * / |
周 | 是 | 1-7 或 SUN-SAT | , - * ? / L C # |
年 | 否 | 空 或 1970-2099 | , - * / |
注意:
- 月份和星期的名称是不区分大小写的,FRI 和 fri 是一样的
- 域之间有空格分隔 * * * ? * *
- 这个表达会每秒钟(每分种的、每小时的、每天的)激发一个部署的 job
特殊字符解释
-
- 星号
使用星号(* ) 指示着你想在这个域上包含所有合法的值。例如,在月份域上使用星号意味着每个月都会触发这个 trigger。
- 星号
例:0 * 9 * * ?
意义:每天从上午9点到上午9:59中的每分钟激发一次 trigger。它停在上午 9:59 是因为值 9 在小时域上,在上午 10 点时,小时变为 10 了,也就不再理会这个 trigger,直到下一天的上午 9 点。
在你希望 trigger 在该域的所有有效值上被激发时使用 * 字符。
- ? 问号
? 号只能用在日和周域上,但是不能在这两个域上同时使用。你可以认为 ? 字符是 “我并不关心在该域上是什么值。” 这不同于星号,星号是指示着该域上的每一个值。? 是说不为该域指定值。
不能同时这两个域上指定值的理由是难以解释甚至是难以理解的。基本上,假定同时指定值的话,意义就会变得含混不清了:考虑一下,如果一个表达式在日域上有值11,同时在周域上指定了 WED。那么是要 trigger 仅在每个月的11号,且正好又是星期三那天被激发?还是在每个星期三的11号被激发呢?要去除这种不明确性的办法就是不能同时在这两个域上指定值。
只要记住,假如你为这两域的其中一个指定了值,那就必须在另一个字值上放一个 ?。
表达式样例:
0 10,44 14 ? 3 WEB
意义:在三月中的每个星期三的14:10 和 14:44 被触发。
- , 逗号
逗号 (,) 是用来在给某个域上指定一个值列表的。例如,使用值 0,15,30,45 在秒域上意味着每15秒触发一个 trigger。
表达式样例:
0 0,15,30,45 * * * ?
意义:每刻钟触发一次 trigger。
- / 斜杠
斜杠 (/) 是用于时间表的递增的。我们刚刚用了逗号来表示每15分钟的递增,但是我们也能写成这样 0/15。
表达式样例:
0/15 0/30 * * * ?
意义:在整点和半点时每15秒触发 trigger。
-
- 中划线
中划线 (-) 用于指定一个范围。例如,在小时域上的 3-8 意味着 “3,4,5,6,7 和 8 点。” 域的值不允许回卷,所以像 50-10 这样的值是不允许的。
- 中划线
表达式样例:
0 45 3-8 ? * *
意义:在上午的3点至上午的8点的45分时触发 trigger。
- L 字母
L 说明了某域上允许的最后一个值。它仅被日和周域支持。当用在日域上,表示的是在月域上指定的月份的最后一天。例如,当月域上指定了 JAN 时,在日域上的 L 会促使 trigger 在1月31号被触发。假如月域上是 SEP,那么 L 会预示着在9月30号触发。换句话说,就是不管指定了哪个月,都是在相应月份的时最后一天触发 trigger。
表达式 0 0 8 L * ? 意义是在每个月最后一天的上午 8:00 触发 trigger。在月域上的 * 说明是 “每个月”。
当 L 字母用于周域上,指示着周的最后一天,就是星期六 (或者数字7)。所以如果你需要在每个月的最后一个星期六下午的 11:59 触发 trigger,你可以用这样的表达式 0 59 23 ? * L。
当使用于周域上,你可以用一个数字与 L 连起来表示月份的最后一个星期 X。例如,表达式 0 0 12 ? * 2L 说的是在每个月的最后一个星期一触发 trigger。
不要让范围和列表值与 L 连用,虽然你能用星期数(1-7)与 L 连用,但是不允许你用一个范围值和列表值与 L 连用。这会产生不可预知的结果。
- W 字母
W 字符代表着平日 (Mon-Fri),并且仅能用于日域中。它用来指定离指定日的最近的一个平日。大部分的商业处理都是基于工作周的,所以 W 字符可能是非常重要的。例如,日域中的 15W 意味着 “离该月15号的最近一个平日。” 假如15号是星期六,那么 trigger 会在14号(星期四)触发,因为距15号最近的是星期一,这个例子中也会是17号(译者Unmi注:不会在17号触发的,如果是15W,可能会是在14号(15号是星期六)或者15号(15号是星期天)触发,也就是只能出现在邻近的一天,如果15号当天为平日直接就会当日执行)。W 只能用在指定的日域为单天,不能是范围或列表值。 - #井号
#字符仅能用于周域中。它用于指定月份中的第几周的哪一天。例如,如果你指定周域的值为 6#3,它意思是某月的第三个周五 (6=星期五,#3意味着月份中的第三周)。另一个例子 2#1 意思是某月的第一个星期一 (2=星期一,#1意味着月份中的第一周)。注意,假如你指定 #5,然而月份中没有第 5 周,那么该月不会触发。
表达式 意义
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 ? 3WED 每年三月的星期三的下午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 ? * 6 L 每月的最后一个星期五上午10:15触发
0 15 10 ? * 6L2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发