定时任务之Timer,Quartz,Spring task

定时任务可以通过三种方式实现:

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在线生成器,自动生成。

具体地址:http://cron.qqe2.com/

image.png

五、定时任务在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任务。

image.png

简单任务:

任务类名:定时任务任务的类名,唯一标示

重复间隔:每次执行定时任务的间隔时间,以秒s 为单位。

重复次数:执行定时任务的重复次数(如果不填重复次数,代表无限循环,按照执行间隔时间执行)

如下图所示,预警测试从2017-10-28 18:53:04 开始,每6分钟执行一次。

image.png

CRON任务:

以工作流批量提交为例,通过Cron表达式设置任务的执行频度

如下图所示,批量提交任务每小时的0分开始,时间间隔30分钟。

image.png

其中执行记录可以在该界面查看。

image.png

如果对于一个任务有特殊需求,可以在任务明细界面点击操作按钮,对任务暂停、恢复、或者删除。

image.png

猜你喜欢

转载自blog.csdn.net/a1786223749/article/details/78403705