Java的定时任务实现有三种,一种是使用JDK自带的Timer那个类来实现,另一种是使用quartz框架来实现,最后一种是在项目使用Spring框架的情况下用Spring框架来实现。JDK自带的类是单线程的,而且只能定义特定时间去执行任务,而不能指定任务执行的频率,所以一般用的较少,只有处理一些简单的程序才会用到。而quartz是一个轻量级的框架,支持多线程,支持定义频率,所以非常方便。我目前用的是SSM框架,所以就用quartz来实现了。
Quartz是一个开源项目,它可以与j2EE和j2SE结合使用,也可以单独使用。Quartz可以用来创建许多Jobs这样的复杂的程序,并通过cron表达式自定义执行的规则。
本篇是配置的单机的,分布式的情况请参看基于spring+quartz的分布式定时任务框架
Quartz的几个核心概念
▶Job:是一个接口,此接口只有一个方法execute(JobExecutionContext context),开发者通过实现该接口来定义需要执行的任务,Job运行时的信息保存在JobDataMap实例中。JobExecutionContext类提供了调度上下文的各种信息。
▶JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不是直接接收一个Job实例,而是接收一个Job实现类,以便通过反射机制实例化Job,因此需要一个类来描述Job的实现类及其相关的静态信息(Job名称、描述等),JobDetail承担了这一角色。
▶Trigger:一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或以固定间隔周期性执行时,可以使用SimpleTrigger。当需要复杂的调度方案,可以使用CronTrigger(通过Cron表达式定义出各种复杂时间规则的调度方案)。
▶Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,二者在Scheduler中拥有各自的组和名称,可以注册多个JobDetial和Trigger。组及名称是Scheduler查找定位容器中某一对象的依据,因此,组及名称必须唯一,但是Trigger和JobDetail的组和名称可以相同,因为它们是不同的类型。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。
▶SchedulerFactory:代表一个调度工厂,用来创建一个scheduler调度器。
Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例。
下面这段了解即可:
Job有一个StatefulJob子接口,代表有状态的任务。它是一个没有方法的标签接口,目的是让Quartz知道任务的类型,以便执行不同的方案。无状态任务在执行时拥有自己的JobDataMap复制,对JobDataMap的更改不会影响下次的执行。而有状态任务共享同一个JobDataMap实例,每次执行任务对JobDataMap的更改会保存下来,会对后面的执行产生影响。因此,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行。如果前次的StatefulJob还没有执行完毕,则下次的任务将阻塞等待,直到前次任务执行完毕。避免使用有状态的Job。使用数据库持久化任务调度信息,则无状态的JobDataMap仅在Scheduler注册任务时保存一次,而有状态的任务的JobDataMap在每次执行任务后都会进行保存。
CRON表达式
CRON表达式用来自定义Quartz执行规则,在实际使用时可以借助在线自动生成工具:http://cron.qqe2.com/,所以下面的具体规则大致了解即可:
①CRON由7个域组成:秒 分 时 日 月 周 年 ,以空格作为分界。
②特殊字符:*(星号) ?(问号) ,(逗号) /(斜杠) -(中划线) L W #(井号)
■ *(星号)指示着你想在这个域上包含所有合法的值。例如,在月份域上使用星号意味着每个月都会触发这个 trigger。
■ ?(问号)只能用在日和周域上,但是不能在这两个域上同时使用,指不为该域指定值。
■ ,(逗号)是用来在给某个域上指定一个值列表的。例如,使用值 0,15,30,45在秒域上意味着每15秒触发一个 trigger。
■ /(斜杠)用于时间表的递增的。例如 0/15表示每15分钟的递增。
■ -(中划线)用于指定一个范围。例如,在小时域上的 3-8意味着 "3,4,5,6,7和 8 点
■ L 代表某域上允许的最后一个值。用在日表示一个月中的最后一天,用在周表示该月最后一个星期X。它仅被日和周域支持。
■ W 代表离给定日期最近的工作日,并且仅能用于日域中。
■ #(井号) 字符仅能用于周域中。它用于指定月份中的第几周的哪一天。表示该月第几个周X。6#3表示该月第3个周五。
详情参考:SpringQuartz定时任务的cron表达式书写
在Spring配置使用Quartz流程详解
① 导入相关依赖,这里我是用Maven管理jar包的(注意spring3.1及以上版本支持quartz2)!
了解最新的可以去官网:http://www.quartz-scheduler.org/downloads/
在pom.xml中添加如下,之后记得import changes下:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
② spring配置文件中的基本配置 (applicationContext.xml)
● xmlns和 xsi:schemaLocation配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
...
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="... http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd">
...
</beans>
● task任务扫描注解
<!-- 加载定时任务 -->
<task:annotation-driven/>
●扫描的位置
<!-- 自动扫描 -->
<context:component-scan base-package="cn.xx55xx.quartz"/>
③ spring配置文件中的任务相关配置
<!--线程执行器配置,用于任务注册-->
<bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="500" />
</bean>
<!-- Quartz Job 业务对象-->
<bean id="orderPayStatusJob" class="cn.xx55xx.quartz.OrderPayStatusJob"></bean>
<!-- Quartz JobDetail 调度业务-->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="orderPayStatusJob"/>
<property name="targetMethod" value="updateStatus" />
</bean>
<!-- Quartz CronTrigger 增加调用的触发器,触发时间 -->
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="jobDetail"/>
<property name="cronExpression" value="0/5 * * * * ?" /> <!-- 每5秒触发一次 -->
<!--<property name="cronExpression" value="0 0/5 * * * ?" /> --> <!-- 每5min触发一次 -->
</bean>
<!-- Quartz 设置调度 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger" />
</list>
</property>
<property name="taskExecutor" ref="executor" />
</bean>
④根据需要在类中写好自己业务逻辑代码
package cn.xx55xx.quartz;
import java.text.SimpleDateFormat;
import java.util.Date;
public class OrderPayStatusJob {
/**
* 业务逻辑处理
*
*/
private static int count = 0;
public void updateStatus() {
System.out.println(count++);
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); //打印当前时间
}
}
回想一下之前说过的Quartz三个要素:Scheduler、Trigger、JobDetai&Job。对应到xml文件,也就是上面的中的几个bean对象。几个点注意下:
一个scheduler可以对应多个Trigger:看bean对象也看出来了,它的属性是个list集合。
一个job可以对应多个JobDetail:这个其实也好理解,毕竟具体的实现是job实现,但是通过jobDetail去管理job。
job可以自己指定方法名: 之前job都是通过实现job接口,实现execute方法。现在只需要通过jobDetai的属性targetMethod指定执行方法,但是需要注意这个jobDetail它是使用MethodInvokingJobDetailFactoryBean作为具体实现。
补充几个CRON表达式例子:
每隔5秒执行一次:*/5 * * * * ?
每隔1分钟执行一次:0 */1 * * * ?
每天23点执行一次:0 0 23 * * ?
每天凌晨1点执行一次:0 0 1 * * ?
每月1号凌晨1点执行一次:0 0 1 1 * ?
每月最后一天23点执行一次:0 0 23 L * ?
每周星期天凌晨1点实行一次:0 0 1 ? * L
在26分、29分、33分执行一次:0 26,29,33 * * * ?
每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?