定时任务调度

一、cron4j

特点:最大的特点就是小巧,简单,功能说实话没什么可说的,就是模仿unix的crontab,门槛非常低,编程非常简单. 可以执行一些简单的定时调度功能,太复杂的还是用quartz比较好。

定时任务:

public class CronJob implements Runnable{
    public void run() {
        System.out.println("任务执行开始");
        System.out.println(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
        System.out.println("任务执行结束");
    }
}

调度进程:

import it.sauronsoftware.cron4j.Scheduler;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TestCron {
    public static void main(String[] args) {
        CronJob cronJob = new CronJob();
        Scheduler scheduler = new Scheduler();
        scheduler.schedule("*/1 * * * *",cronJob);//每一分钟执行一次
        System.out.println("线程开始:"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
        scheduler.start();
        try {
            Thread.sleep(300000);//主线程休眠5分钟
        }catch (InterruptedException e){

        }
        scheduler.stop();
        System.out.println("线程结束:"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
    }
}

执行结果:

线程开始:2017-06-01 05:22:44
任务执行开始
2017-06-01 05:23:00
任务执行结束
任务执行开始
2017-06-01 05:24:00
任务执行结束
任务执行开始
2017-06-01 05:25:00
任务执行结束
任务执行开始
2017-06-01 05:26:00
任务执行结束
任务执行开始
2017-06-01 05:27:00
任务执行结束
线程结束:2017-06-01 05:27:44

就是这么简单。

二、Quartz

Quartz 可以满足更多更复杂的调度需求,首先让我们看看如何用 Quartz 实现每星期二 16:38 的调度安排:

清单 4. 使用 Quartz 进行任务调度

package com.ibm.scheduler;
import java.util.Date;

import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.helpers.TriggerUtils;

public class QuartzTest implements Job {

    @Override
    //该方法实现需要执行的任务
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
        System.out.println("Generating report - "
                + arg0.getJobDetail().getFullName() + ", type ="
                + arg0.getJobDetail().getJobDataMap().get("type"));
        System.out.println(new Date().toString());
    }
    public static void main(String[] args) {
        try {
            // 创建一个Scheduler
            SchedulerFactory schedFact = 
            new org.quartz.impl.StdSchedulerFactory();
            Scheduler sched = schedFact.getScheduler();
            sched.start();
            // 创建一个JobDetail,指明name,groupname,以及具体的Job类名,
            //该Job负责定义需要执行任务
            JobDetail jobDetail = new JobDetail("myJob", "myJobGroup",
                    QuartzTest.class);
            jobDetail.getJobDataMap().put("type", "FULL");
            // 创建一个每周触发的Trigger,指明星期几几点几分执行
            Trigger trigger = TriggerUtils.makeWeeklyTrigger(3, 16, 38);
            trigger.setGroup("myTriggerGroup");
            // 从当前时间的下一秒开始执行
            trigger.setStartTime(TriggerUtils.getEvenSecondDate(new Date()));
            // 指明trigger的name
            trigger.setName("myTrigger");
            // 用scheduler将JobDetail与Trigger关联在一起,开始调度任务
            sched.scheduleJob(jobDetail, trigger);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
} 
Output:
Generating report - myJobGroup.myJob, type =FULL
Tue Feb 8 16:38:00 CST 2011
Generating report - myJobGroup.myJob, type =FULL
Tue Feb 15 16:38:00 CST 2011

清单 4 非常简洁地实现了一个上述复杂的任务调度。Quartz 设计的核心类包括 Scheduler, Job 以及Trigger。其中,Job 负责定义需要执行的任务,Trigger 负责设置调度策略,Scheduler 将二者组装在一起,并触发任务开始执行。

1、Job

使用者只需要创建一个 Job 的继承类,实现 execute 方法。JobDetail 负责封装 Job 以及 Job 的属性,并将其提供给 Scheduler 作为参数。每次 Scheduler 执行任务时,首先会创建一个 Job 的实例,然后再调用 execute 方法执行。Quartz 没有为 Job 设计带参数的构造函数,因此需要通过额外的 JobDataMap 来存储 Job 的属性。JobDataMap 可以存储任意数量的 Key,Value 对,例如:

清单 5. 为 JobDataMap 赋值

jobDetail.getJobDataMap().put("myDescription", "my job description"); 
jobDetail.getJobDataMap().put("myValue", 1998); 
ArrayList<String> list = new ArrayList<String>(); 
list.add("item1"); 
jobDetail.getJobDataMap().put("myArray", list);

JobDataMap 中的数据可以通过下面的方式获取:

清单 6. 获取 JobDataMap 的值

public class JobDataMapTest implements Job {

    @Override
    public void execute(JobExecutionContext context)
            throws JobExecutionException {
        //从context中获取instName,groupName以及dataMap
        String instName = context.getJobDetail().getName();
        String groupName = context.getJobDetail().getGroup();
        JobDataMap dataMap = context.getJobDetail().getJobDataMap();
        //从dataMap中获取myDescription,myValue以及myArray
        String myDescription = dataMap.getString("myDescription");
        int myValue = dataMap.getInt("myValue");
        ArrayList<String> myArray = (ArrayListlt;Strin>) dataMap.get("myArray");
        System.out.println("
                Instance =" + instName + ", group = " + groupName
                + ", description = " + myDescription + ", value =" + myValue
                + ", array item0 = " + myArray.get(0));

    }
}
Output:
Instance = myJob, group = myJobGroup, 
description = my job description, 
value =1998, array item0 = item1

2、Trigger

Trigger 的作用是设置调度策略。Quartz 设计了多种类型的 Trigger,其中最常用的是 SimpleTrigger 和 CronTrigger。

SimpleTrigger 适用于在某一特定的时间执行一次,或者在某一特定的时间以某一特定时间间隔执行多次。上述功能决定了 SimpleTrigger 的参数包括 start-time, end-time, repeat count, 以及 repeat interval。

Repeat count 取值为大于或等于零的整数,或者常量 SimpleTrigger.REPEAT_INDEFINITELY。

Repeat interval 取值为大于或等于零的长整型。当 Repeat interval 取值为零并且 Repeat count 取值大于零时,将会触发任务的并发执行。

Start-time 与 dnd-time 取值为 java.util.Date。当同时指定 end-time 与 repeat count 时,优先考虑 end-time。一般地,可以指定 end-time,并设定 repeat count 为 REPEAT_INDEFINITELY。

以下是 SimpleTrigger 的构造方法:

public SimpleTrigger(String name, 
                      String group, 
                      Date startTime, 
                      Date endTime, 
                      int repeatCount, 
                      long repeatInterval)

举例如下:
创建一个立即执行且仅执行一次的 SimpleTrigger:

SimpleTrigger trigger=
new SimpleTrigger("myTrigger", "myGroup", new Date(), null, 0, 0L);

创建一个半分钟后开始执行,且每隔一分钟重复执行一次的 SimpleTrigger:

SimpleTrigger trigger=
new SimpleTrigger("myTrigger", "myGroup", 
   new Date(System.currentTimeMillis()+30*1000), null, 0, 60*1000);

创建一个 2011 年 6 月 1 日 8:30 开始执行,每隔一小时执行一次,一共执行一百次,一天之后截止的 SimpleTrigger:

Calendar calendar = Calendar.getInstance(); 
calendar.set(Calendar.YEAR, 2011); 
calendar.set(Calendar.MONTH, Calendar.JUNE); 
calendar.set(Calendar.DAY_OF_MONTH, 1); 
calendar.set(Calendar.HOUR, 8); 
calendar.set(Calendar.MINUTE, 30); 
calendar.set(Calendar.SECOND, 0); 
calendar.set(Calendar.MILLISECOND, 0); 
Date startTime = calendar.getTime(); 
Date endTime = new Date (calendar.getTimeInMillis() +24*60*60*1000); 
SimpleTrigger trigger=new SimpleTrigger("myTrigger", 
       "myGroup", startTime, endTime, 100, 60*60*1000);

上述最后一个例子中,同时设置了 end-time 与 repeat count,则优先考虑 end-time,总共可以执行二十四次。

CronTrigger 的用途更广,相比基于特定时间间隔进行调度安排的 SimpleTrigger,CronTrigger 主要适用于基于日历的调度安排。例如:每星期二的 16:38:10 执行,每月一号执行,以及更复杂的调度安排等。

CronTrigger 同样需要指定 start-time 和 end-time,其核心在于 Cron 表达式,由七个字段组成:

Seconds
Minutes
Hours
Day-of-Month
Month
Day-of-Week
Year (Optional field)

举例如下:
创建一个每三小时执行的 CronTrigger,且从每小时的整点开始执行:

0 0 0/3 * * ?

创建一个每十分钟执行的 CronTrigger,且从每小时的第三分钟开始执行:

0 3/10 * * * ?

创建一个每周一,周二,周三,周六的晚上 20:00 到 23:00,每半小时执行一次的 CronTrigger:

0 0/30 20-23 ? * MON-WED,SAT

创建一个每月最后一个周四,中午 11:30-14:30,每小时执行一次的 trigger:

0 30 11-14/1 ? * 5L

解释一下上述例子中各符号的含义:

首先所有字段都有自己特定的取值,例如,Seconds 和 Minutes 取值为 0 到 59,Hours 取值为 0 到 23,Day-of-Month 取值为 0-31, Month 取值为 0-11,或者 JAN,FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC,Days-of-Week 取值为 1-7 或者 SUN, MON, TUE, WED, THU, FRI, SAT。每个字段可以取单个值,多个值,或一个范围,例如 Day-of-Week 可取值为“MON,TUE,SAT”,“MON-FRI”或者“TUE-THU,SUN”。

通配符 * 表示该字段可接受任何可能取值。例如 Month 字段赋值 * 表示每个月,Day-of-Week 字段赋值 * 表示一周的每天。

/ 表示开始时刻与间隔时段。例如 Minutes 字段赋值 2/10 表示在一个小时内每 20 分钟执行一次,从第 2 分钟开始。

? 仅适用于 Day-of-Month 和 Day-of-Week。? 表示对该字段不指定特定值。适用于需要对这两个字段中的其中一个指定值,而对另一个不指定值的情况。一般情况下,这两个字段只需对一个赋值。

L 仅适用于 Day-of-Month 和 Day-of-Week。L 用于 Day-of-Month 表示该月最后一天。L 单独用于 Day-of-Week 表示周六,否则表示一个月最后一个星期几,例如 5L 或者 THUL 表示该月最后一个星期四。

W 仅适用于 Day-of-Month,表示离指定日期最近的一个工作日,例如 Day-of-Month 赋值为 10W 表示该月离 10 号最近的一个工作日。

#仅适用于 Day-of-Week,表示该月第 XXX 个星期几。例如 Day-of-Week 赋值为 5#2 或者 THU#2,表示该月第二个星期四。

CronTrigger 的使用如下:

CronTrigger cronTrigger = new CronTrigger("myTrigger", "myGroup"); 
try { 
    cronTrigger.setCronExpression("0 0/30 20-13 ? * MON-WED,SAT"); 
} catch (Exception e) { 
    e.printStackTrace(); 
}

Job 与 Trigger 的松耦合设计是 Quartz 的一大特点,其优点在于同一个 Job 可以绑定多个不同的 Trigger,同一个 Trigger 也可以调度多个 Job,灵活性很强。

Quartz除了以上功能之外,还具有listen功能(当系统发生故障,相关人员需要被通知时,Listener 便能发挥它的作用。最常见的情况是,当任务被执行时,系统发生故障,Listener 监听到错误,立即发送邮件给管理员。),数据持久化功能(即将任务调度的相关数据保存下来)。在这里不详细介绍了。
想要关注的可以参考:几种任务调度的 Java 实现方法与比较


然而

Quartz虽然强大,但是实现起来过于繁琐。好在强大的Spring已经将这一常用的Quartz整合进去,方便了我们的使用。

特别注意一点:Quartz1.*和Quartz2.*这两个版本之间差别还是蛮大的。与Spring3.1以下版本整合必须使用Quartz1,最初我拿2.1.3的,怎么搞都报错:

Caused by: org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [org.springframework.scheduling.quartz.CronTriggerBean] for bean with name ‘mytrigger’ defined in class path resource [applicationContext.xml]: problem with class file or dependent class; nested exception is java.lang.IncompatibleClassChangeError: class org.springframework.scheduling.quartz.CronTriggerBean has interface org.quartz.CronTrigger as super class

查看发现spring3.0.5中org.springframework.scheduling.quartz.CronTriggerBean继承了org.quartz.CronTrigger(public class CronTriggerBeanextends CronTrigger),而在quartz2.1.3中org.quartz.CronTrigger是个接口(publicabstract interface CronTrigger extends Trigger),而在quartz1.8.5及1.8.4中org.quartz.CronTrigger是个类(publicclass CronTrigger extends Trigger),从而造成无法在applicationContext中配置触发器。这是spring3.1以下版本和quartz2版本不兼容的一个bug。(spring3.1以及以后版本支持quartz2)

在Spring中使用Quartz有两种方式实现:第一种是任务类继承QuartzJobBean,第二种则是在配置文件里定义任务类和要执行的方法,类和方法仍然是普通类。很显然,第二种方式远比第一种方式来的灵活。

第一种方式的JAVA代码:

package com.ncs.hj;  

import org.quartz.JobExecutionContext;  
import org.quartz.JobExecutionException;  
import org.springframework.scheduling.quartz.QuartzJobBean;  

public class SpringQtz extends QuartzJobBean{  
    private static int counter = 0;  
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {  
        System.out.println();  
        long ms = System.currentTimeMillis();  
        System.out.println("\t\t" + new Date(ms));  
        System.out.println(ms);  
        System.out.println("(" + counter++ + ")");  
        String s = (String) context.getMergedJobDataMap().get("service");  
        System.out.println(s);  
        System.out.println();  
    }  
} 

第二种方式的JAVA代码:

public class InnoScheduler {

    private InnoIndexService innoIndexService;
    private InnoReviewCalenderService innoReviewCalenderService;
    private InnovationReviewService innovationReviewService;

    public void calculateResult(){
        //此处为要定时执行的方法。
    }

    public InnoIndexService getInnoIndexService() {
        return innoIndexService;
    }

    public void setInnoIndexService(InnoIndexService innerCallbackService) {
        this.innoIndexService = innerCallbackService;
    }

    public InnoReviewCalenderService getInnoReviewCalenderService() {
        return innoReviewCalenderService;
    }

    public void setInnoReviewCalenderService(InnoReviewCalenderService innoReviewCalenderService) {
        this.innoReviewCalenderService = innoReviewCalenderService;
    }

    public InnovationReviewService getInnovationReviewService() {
        return innovationReviewService;
    }

    public void setInnovationReviewService(InnovationReviewService innovationReviewService) {
        this.innovationReviewService = innovationReviewService;
    }
}

Spring的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd"
       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:tx="http://www.springframework.org/schema/tx">


    <!-- 消息回调执行任务 -->
    <bean id="innoIndexScheduler" class="com.sf.sfpp.web.scheduler.InnoScheduler">
        <property name="innoIndexService" ref="innoIndexService"></property>
        <property name="innoReviewCalenderService" ref="innoReviewCalenderService"></property>
        <property name="innovationReviewService" ref="innovationReviewService"></property>
    </bean>

    <!-- 引用,配置要运行的方法 -->
    <bean id="calculateIndexJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" >
            <ref bean="innoIndexScheduler"/>
        </property>
        <property name="concurrent" value="false" />
        <property name="targetMethod" value="calculateResult" />
    </bean>
    <!-- 引用,配置触发任务调用时间 -->
    <bean id="calculateIndexJobTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
        <property name="jobDetail">
            <ref bean="calculateIndexJobDetail"/>
        </property>
        <property name="cronExpression">
            <!--秒 分 时 天 月  星期 年 -->
            <value>0 0 2 * * ?</value>
        </property>
    </bean>

    <!-- 每个定义的任务都要在这里进行引用才能运行 -->
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref local="calculateIndexJobTrigger" />
            </list>
        </property>
    </bean>

</beans>

可以看到配置文件里面配置了

  • 任务bean
  • Job:MethodInvokingJobDetailFactoryBean 其中配置了关联的任务bean,以及调用的方法。
  • Trigger:calculateIndexJobTrigger其中配置了关联的Job,已经触发规则cron。
  • SchedulerFactoryBean(上两个的整合者)其中配置了关联的Trigger。

可以看出通过xml的方式,可以灵活的配置你要调度的任务。


然而的然而

当任务没有那么复杂的时候,可以使用spring3的更方便的注解来实现,这个更为简单:

为了调度某个方法,你只需要使用@Scheduled注解来标注它。例如,为了让Spring每隔24小时(86400000毫秒)触发一个方法:

@Scheduled(fixedRate=86400000)
public void method(){
  //...
}

属性fixedRate表名这个方法需要每隔指定的毫秒数进行周期性的调用。在本示例中,内次方法开始调用之间要经历86400000毫秒。
如果你想要指定调用之间的间隔(也就是一次调用完成与下一次调用开始之间的间隔),那么需要使用fixedDelay属性。

@Scheduled(fixedDelay=86400000)
public void archiveOldSpittles(){
    //...
}

这两种属性在指定间隔后调用任务是很方便的。但是,为了指定什么时候调用方法,可以使用cron属性。

@Scheduled(cron="0 0 0 * * SAT"public void archiveOldSpittles(){
    //....
}

当然,这种各有各的好处,基于注解这种方式可能在面对多处任务调度的时候,可能就不如配置文件来的方便。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章