Spring Quartz 定时器任务

本文详细介绍了如何在 Spring 中使用 Quartz 实现定时任务,包括基于 XML 配置和注解的方式,并提供了 Cron 表达式的使用示例。通过 JobDetail 和 Trigger 的配置,实现定时任务的调度执行。此外,还探讨了 Cron 表达式的特殊字符及其用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 部门内容:

  1. 配置 Job 类的 Bean
<!-- 自定义的Job类 --> <bean id="xmlBasedJob" class="cn.upshi.springquartz.job.XMLBasedJob" />
  1. 配置 JobDetail
<!-- 一个可执行的定时器 -->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <!-- 指定Job类 -->
    <property name="targetObject" ref="xmlBasedJob" />
    <!-- 指定Job执行的方法 -->
    <property name="targetMethod" value="execute" />
</bean>
  1. 配置 Job 触发器
<!-- Job的触发器 -->
<bean id="jobTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="jobDetail" />
    <!-- 每5秒运行一次 -->
    <property name="cronExpression" value="0/5 * * * * ?" />
</bean>
  1. 配置 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的表达式是字符串,实际上是由七子表达式,描述个别细节的时间表。这些子表达式是分开的空白,代表:

  1. Seconds
  2. Minutes
  3. Hours
  4. Day-of-Month
  5. Month
  6. Day-of-Week
  7. 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 在该域的所有有效值上被激发时使用 * 字符。

  1. ? 问号
    ? 号只能用在日和周域上,但是不能在这两个域上同时使用。你可以认为 ? 字符是 “我并不关心在该域上是什么值。” 这不同于星号,星号是指示着该域上的每一个值。? 是说不为该域指定值。

不能同时这两个域上指定值的理由是难以解释甚至是难以理解的。基本上,假定同时指定值的话,意义就会变得含混不清了:考虑一下,如果一个表达式在日域上有值11,同时在周域上指定了 WED。那么是要 trigger 仅在每个月的11号,且正好又是星期三那天被激发?还是在每个星期三的11号被激发呢?要去除这种不明确性的办法就是不能同时在这两个域上指定值。

只要记住,假如你为这两域的其中一个指定了值,那就必须在另一个字值上放一个 ?。

表达式样例:

0 10,44 14 ? 3 WEB

意义:在三月中的每个星期三的14:10 和 14:44 被触发。

  1. , 逗号
    逗号 (,) 是用来在给某个域上指定一个值列表的。例如,使用值 0,15,30,45 在秒域上意味着每15秒触发一个 trigger。

表达式样例:

0 0,15,30,45 * * * ?

意义:每刻钟触发一次 trigger。

  1. / 斜杠
    斜杠 (/) 是用于时间表的递增的。我们刚刚用了逗号来表示每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。

  1. 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 连用。这会产生不可预知的结果。

  1. W 字母
    W 字符代表着平日 (Mon-Fri),并且仅能用于日域中。它用来指定离指定日的最近的一个平日。大部分的商业处理都是基于工作周的,所以 W 字符可能是非常重要的。例如,日域中的 15W 意味着 “离该月15号的最近一个平日。” 假如15号是星期六,那么 trigger 会在14号(星期四)触发,因为距15号最近的是星期一,这个例子中也会是17号(译者Unmi注:不会在17号触发的,如果是15W,可能会是在14号(15号是星期六)或者15号(15号是星期天)触发,也就是只能出现在邻近的一天,如果15号当天为平日直接就会当日执行)。W 只能用在指定的日域为单天,不能是范围或列表值。
  2. #井号
    #字符仅能用于周域中。它用于指定月份中的第几周的哪一天。例如,如果你指定周域的值为 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触发
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值