定时任务可以通过三种方式实现:
1.Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。
2.使用Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂
3.Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多
一、Timer
在java中一个完整定时任务需要由Timer、TimerTask两个类来配合完成。 API中是这样定义他们的,Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。由TimerTask:Timer 安排为一次执行或重复执行的任务。我们可以这样理解Timer是一种定时器工具,用来在一个后台线程计划执行指定任务,而TimerTask一个抽象类,它的子类代表一个可以被Timer计划的任务。
当程序初始化完成Timer后,定时任务就会按照我们设定的时间去执行,Timer提供了schedule方法,该方法有多中重载方式来适应不同的情况,如下:
schedule(TimerTask task, Date time):安排在指定的时间执行指定的任务。
schedule(TimerTask task, Date firstTime, long period) :安排指定的任务在指定的时间开始进行重复的固定延迟执行。
schedule(TimerTask task, long delay) :安排在指定延迟后执行指定的任务。
schedule(TimerTask task, long delay, long period) :安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
同时也重载了scheduleAtFixedRate方法,scheduleAtFixedRate方法与schedule相同,只不过他们的侧重点不同,区别后面分析。
scheduleAtFixedRate(TimerTask task, Date firstTime, long period):安排指定的任务在指定的时间开始进行重复的固定速率执行。
scheduleAtFixedRate(TimerTask task, long delay, long period):安排指定的任务在指定的延迟后开始进行重复的固定速率执行。
指定延迟时间执行定时任务:
public class TimerTest01 {
Timer timer;
public TimerTest01(int time){
timer = new Timer();
timer.schedule(new TimerTaskTest01(), time * 1000); //调用TimerTaskTest01类
}
public static void main(String[] args) {
System.out.println("timer begin....");
new TimerTest01(3); //三秒后执行TimerTaskTest01中的run方法。
}
}
public class TimerTaskTest01 extends TimerTask{
public void run() {
System.out.println("Time's up!!!!");
}
}
打印结果:
首先打印:timer begin....
3秒后打印:Time's up!!!!
设置schedule中的方法指定定时任务的循环时间和开始时间。
二、Quartz
Quartz是一个任务调度框架,可以指定定时任务开始时间,也可以指定按照某个频度执行。
从作业类的继承方式来讲,可以分为两类:
A. 作业类需要继承自特定的作业类基类,
如Quartz中需要继承自org.springframework.scheduling.quartz.QuartzJobBean;
java.util.Timer中需要继承自java.util.TimerTask。
B. 作业类即普通的java类,不需要继承自任何基类。
下边从作业类和配置文件来详细介绍定时任务是如何实现的:
1)作业类继承自特定的基类:org.springframework.scheduling.quartz.QuartzJobBean
a.定义作业类,需要继承QuartzJobBean
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class Job1 extends QuartzJobBean {
private int timeout;
private static int i = 0;
//调度工厂实例化后,经过timeout时间开始执行调度
public void setTimeout(int timeout) {
this.timeout = timeout;
}
/**
* 要调度的具体任务
*/
@Override
protected void executeInternal(JobExecutionContext context)
throws JobExecutionException {
System.out.println("定时任务执行中…");
}
}
b. 在Spring配置文件中配置作业类
org.springframework.scheduling.quartz.JobDetailBean有两个属性,jobClass属性即我们在java代码中定义的作业类,jobDataAsMap属性即该作业类中需要注入的属性值。
<bean name="job1" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass" value="com.gy.Job1" />
<property name="jobDataAsMap">
<map>
<entry key="timeout" value="0" />
</map>
</property>
</bean>
c.配置触发器,调用作业类
Quartz的作业触发器有两种,分别是
org.springframework.scheduling.quartz.SimpleTriggerBean
org.springframework.scheduling.quartz.CronTriggerBean
SimpleTriggerBean,只支持按照一定频度调用任务,如每隔30分钟运行一次。
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<property name="jobDetail" ref="job1" /> <!-- 触发spring配置的bean -->
<property name="startDelay" value="0" /> <!-- 调度工厂实例化后,经过0秒开始执行调度 -->
<property name="repeatInterval" value="2000" /><!-- 每2秒调度一次 此处以毫秒为单位-->
</bean>
ronTriggerBean,支持到指定时间运行一次,如每天12:00运行一次等。具体Cron表达式后续讲解。
<bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="job1" />
<property name="cronExpression" value="0 0 12 * * ?" /> <!-— 每天12:00运行一次 -->
</bean>
d.配置调度工厂
bean=”cronTrigger” 该参数指定的就是之前配置的触发器的名字。
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTrigger" />
</list>
</property>
</bean>
2)作业类不继承特定基类
Spring能够支持这种方式,是因为以下两个类分别对应spring支持的两种实现任务调度的方式
org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean
org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean:此时我们只使用这个类,使用该类的好处是,我们的任务类不再需要继承自任何类,而是普通的dto。
a.定义作业类
不需要继承任何类
public class Job2 {
public void doJob2() {
System.out.println("不继承QuartzJobBean方式-调度进行中...");
}
}
b.Spring中配置作业类
MethodInvokingJobDetailFactoryBean,有两个关键属性:targetObject指定任务类,targetMethod指定运行的方法。
<bean id="job2"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject">
<bean class="com.gy.Job2" />
</property>
<property name="targetMethod" value="doJob2" />
<property name="concurrent" value="false" /><!-- 作业不并发调度 -->
</bean>
配置触发器和调度工厂和上一部分是一样的,可以参考。
采用Quartz时,需要导入相应的spring的包与Quartz的包。
三、Spring-Task
spring task,可以将它比作一个轻量级的Quartz,而且使用起来很简单,除spring相关的包外不需要额外的包,而且支持注解和配置文件两种,下边详细介绍:
1)配置文件方式
a.定义作业类
import org.springframework.stereotype.Service;
@Service
public class TaskJob {
public void job1() {
System.out.println(“任务进行中。。。”);
}
}
b.在Spring配置文件中配置作业类
在spring配置文件头中添加命名空间及描述
ref=”taskJob” 参数指定的即任务类,method指定的即需要运行的方法。
base-package=” com.gy.mytask ” spring扫描注解。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:task="http://www.springframework.org/schema/task"
。。。。。。
xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">
。。。。
<task:scheduled-tasks>
<task:scheduled ref="taskJob" method="job1" cron="0 * * * * ?"/>
</task:scheduled-tasks>
<context:component-scan base-package=" com.gy.mytask " />
2)注解方式
在方法上使用注解@Scheduled来执行定时任务
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component(“taskJob”)
public class TaskJob {
@Scheduled(cron = "0 0 3 * * ?")
public void job1() {
System.out.println(“任务进行中。。。”);
}
}
但是我们也需要在配置文件中进行相关配置,只要这样Spring才能识别注解@Scheduled
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"
default-lazy-init="false">
<context:annotation-config />
<!—spring扫描注解的配置 -->
<context:component-scan base-package="com.gy.mytask" />
<!—开启这个配置,spring才能识别@Scheduled注解 -->
<task:annotation-driven scheduler="qbScheduler" mode="proxy"/>
<task:scheduler id="qbScheduler" pool-size="10"/>
四、Cron表达式详解
在spring 4.x中已经不支持7个参数的cronin表达式了,要求必须是6个参数(具体哪个参数后面会说)。cron表达式的格式如下:
{秒} {分} {时} {日期(具体哪天)} {月} {星期}
秒:必填项,允许的值范围是0-59,支持的特殊符号包括
,
-
*
/
,,
表示特定的某一秒才会触发任务,-
表示一段时间内会触发任务,*
表示每一秒都会触发,/
表示从哪一个时刻开始,每隔多长时间触发一次任务。分:必填项,允许的值范围是0-59,支持的特殊符号和秒一样,含义类推
时:必填项,允许的值范围是0-23,支持的特殊符号和秒一样,含义类推
日期:必填项,允许的值范围是1-31,支持的特殊符号相比秒多了
?
,表示与{星期}互斥,即意味着若明确指定{星期}触发,则表示{日期}无意义,以免引起冲突和混乱。月:必填项,允许的值范围是1-12(JAN-DEC),支持的特殊符号与秒一样,含义类推
星期:必填项,允许值范围是1~7 (SUN-SAT),1代表星期天(一星期的第一天),以此类推,7代表星期六,支持的符号相比秒多了
?
,表达的含义是与{日期}互斥,即意味着若明确指定{日期}触发,则表示{星期}无意义。<!-- 每15秒、30秒、45秒时触发任务 --> <task:scheduled ref="app" method="execute6" cron="15,30,45 * * * * ?"/> <!-- 15秒到45秒每隔1秒触发任务 --> <task:scheduled ref="app" method="execute7" cron="15-45 * * * * ?"/> <!-- 每分钟的每15秒时任务任务,每隔5秒触发一次 --> <task:scheduled ref="app" method="execute8" cron="15/5 * * * * ?"/> <!-- 每分钟的15到30秒之间开始触发,每隔5秒触发一次 --> <task:scheduled ref="app" method="execute9" cron="15-30/5 * * * * ?"/> <!-- 每小时的0分0秒开始触发,每隔3分钟触发一次 --> <task:scheduled ref="app" method="execute10" cron="0 0/3 * * * ?"/> <!-- 星期一到星期五的10点15分0秒触发任务 --> <task:scheduled ref="app" method="execute11" cron="0 15 10 ? * MON-FRI"/>
如果Cron表达式掌握的不是很好,可以借助Cron在线生成器,自动生成。
五、定时任务在HAP中的使用
HAP封装了一个AbstractJob,我们使用的时候只需要继承AbstractJob然后在safeExecute()方法中加上一些逻辑就可以使用,具体的执行时间和频度可以在界面上配置。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.hand.hap.job;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
import org.quartz.SchedulerException;
public abstract class AbstractJob implements Job, JobListener {
public static final String JOB_RUNNING_INFO_ID = "JOB_RUNNING_INFO_ID";
private String executionSummary;
public AbstractJob() {
}
public final void execute(JobExecutionContext context) throws JobExecutionException {
try {
this.safeExecute(context);
} catch (Exception var6) {
if (StringUtils.isEmpty(this.getExecutionSummary())) {
this.setExecutionSummary(ExceptionUtils.getRootCauseMessage(var6));
}
JobExecutionException e2 = new JobExecutionException(var6);
if (this.isRefireImmediatelyWhenException()) {
e2.setRefireImmediately(true);
} else {
try {
context.getScheduler().pauseTrigger(context.getTrigger().getKey());
} catch (SchedulerException var5) {
var5.printStackTrace();
}
}
throw e2;
}
}
//在此方法中写具体的业务逻辑
public abstract void safeExecute(JobExecutionContext var1) throws Exception;
protected boolean isRefireImmediatelyWhenException() {
return false;
}
public String getExecutionSummary() {
return this.executionSummary;
}
public void setExecutionSummary(String executionSummary) {
this.executionSummary = executionSummary;
}
public String getName() {
return null;
}
public void jobToBeExecuted(JobExecutionContext jobExecutionContext) { }
public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {}
public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) { }
}
以工作流自动提交为例:
1)首先继承AbstractJob类,然后重写safeExecute() 方法
/**
* @description 工作流自动提交
* @author [email protected]
* @date 2017/10/19
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class SubmitServiceImpl extends AbstractJob {
@Override
public void safeExecute(JobExecutionContext jobExecutionContext) throws Exception {
gxpAutoSubmit("GXP_ITEM"); //物料工作流提交
gxpAutoSubmit("GXP_VENDOR"); //供应商工作流提交
gxpAutoSubmit("GXP_CUSTOMER"); //客户工作流提交
}
。。。
}
2)然后在界面配置触发器。
Quartz的作业触发器有两种,分别是
org.springframework.scheduling.quartz.SimpleTriggerBean
org.springframework.scheduling.quartz.CronTriggerBean
CronTrigger能处理更加复杂的时间表达格式,它的使用范围更大。因此在任务明细界面中,分为简单任务和CRON任务。
简单任务:
任务类名:定时任务任务的类名,唯一标示
重复间隔:每次执行定时任务的间隔时间,以秒s 为单位。
重复次数:执行定时任务的重复次数(如果不填重复次数,代表无限循环,按照执行间隔时间执行)
如下图所示,预警测试从2017-10-28 18:53:04 开始,每6分钟执行一次。
CRON任务:
以工作流批量提交为例,通过Cron表达式设置任务的执行频度
如下图所示,批量提交任务每小时的0分开始,时间间隔30分钟。
其中执行记录可以在该界面查看。
如果对于一个任务有特殊需求,可以在任务明细界面点击操作按钮,对任务暂停、恢复、或者删除。