使用quartz和Spring-Task实现计划任务的四种方式

范培忠 2017-06-27

  在我们日常开发中,有一些应用场景有实现计划任务的需要,如在夜晚启动对账处理,当日数据推历史及次日初始化,定时放出某可用资源等操作。

  使用quartz和Spring-Task都可在Java后端实现此计划任务功能,而每种方式又可细分成两种小的方式,本文进行简明的介绍。其中方式一、二基于quartz,区别是使用JobDetailFactoryBean或MethodInvokingJobDetailFactoryBean,后者是由Spring提供支持,不需要集成quartz的抽象类要更灵活一些;Spring从3之后就提供了Spring-Task模块直接实现了计划任务支持,但它属于轻量化的。方式三、四使用Spring-Task实现,区别是使用xml或注解配置。四种方式均亲测有源码。


方式一:继承quartz的JobDetailFactoryBean类
一、项目结构


二、编写具体的任务内容类
package util;

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

import java.time.LocalTime;

public class TaskWork extendsQuartzJobBean {

    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 {
        LocalTime lt = LocalTime.now();
        String sTime = lt.getHour() + ":" + lt.getMinute() + ":" + lt.getSecond();
        System.out.println("定时任务执行中,[" + sTime + "]");
        System.out.println("定时任务执行中,[" + sTime + "]");
        System.out.println("定时任务执行中,[" + sTime + "]");
        System.out.println("定时任务执行中,[" + sTime + "]");
        System.out.println("定时任务执行中,[" + sTime + "]");
        System.out.println("定时任务执行中,[" + sTime + "]");
        System.out.println("定时任务执行中,[" + sTime + "]");
        System.out.println("定时任务执行中,[" + sTime + "]");
        System.out.println("定时任务执行中,[" + sTime + "]");
        System.out.println("定时任务执行中,[" + sTime + "]");
        System.out.println("定时任务执行中,[" + sTime + "]");
    }
}


三、Spring配置文件
(一)定义任务内容
<!--Spring 定时任务配置-->
<bean name="job1" id="job1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
    <!-- durability 如果一个job是非持久的,当没有活跃的trigger与之关联的时候,会被自动地从scheduler中删除。
    也就是说,非持久的job的生命期是由trigger的存在与否决定的;默认false -->
    <property name="durability" value="true"/>
    <property name="jobClass" value="util.TaskWork"/>
    <!-- jobDataAsMap没有用,此目标类中接受的参数 ,若参数为service,则可以在此进行参数配置,类似struts2 -->
    <!--<property name="jobDataAsMap">
        <map>
            <entry key="timeout" value="0"/>
        </map>
    </property>-->
</bean>

(二)定义CornTrigger触发器
<!-- 定义CornTrigger触发器 定时执行-->
<bean id="cornTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="job1"/>
    <!-- 在每天13点46分触发,正则表示的配置可以使用 http://cron.qqe2.com/ -->
    <property name="cronExpression" value="0 46 13 * * ? "/>
</bean>

<!-- 定义simpleTrigger触发器 间隔执行-->
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
    <property name="jobDetail" ref="jobDetail"></property>
    <property name="repeatCount">
        <value>8</value>
    </property>
    <property name="repeatInterval">
        <value>1000</value>
    </property>
    <property name="startDelay">
        <value>4</value>
    </property>
</bean>

(三)配置核心调度器
<!--核心调度器-->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cornTrigger"/>
        </list>
    </property>
</bean>

四、执行效果



方式二 基于Spring的MethodInvokingJobDetailFactoryBean
此种方式,不用继承quartz类,但在pom里仍然要依赖quartz,否则配置调度工厂时不能识别。
一、项目结构


二、编写具体的任务内容类
package util;

import org.quartz.JobExecutionException;

import java.time.LocalTime;

public class TaskWork {
    //要调度的具体任务,必须要设置成public
    public void execute() throws JobExecutionException {
        LocalTime lt = LocalTime.now();
        String sTime = lt.getHour() + ":" + lt.getMinute() + ":" + lt.getSecond();
        System.out.println("定时任务执行中2,[" + sTime + "]");
        System.out.println("定时任务执行中2,[" + sTime + "]");
        System.out.println("定时任务执行中2,[" + sTime + "]");
        System.out.println("定时任务执行中2,[" + sTime + "]");
        System.out.println("定时任务执行中2,[" + sTime + "]");
        System.out.println("定时任务执行中2,[" + sTime + "]");
        System.out.println("定时任务执行中2,[" + sTime + "]");
        System.out.println("定时任务执行中2,[" + sTime + "]");
        System.out.println("定时任务执行中2,[" + sTime + "]");
        System.out.println("定时任务执行中2,[" + sTime + "]");
        System.out.println("定时任务执行中2,[" + sTime + "]");
    }
}

三、Spring配置文件
(一)定义任务内容
<!--Spring 定时任务配置-->
<bean id="job2"
      class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <property name="targetObject">
        <bean class="util.TaskWork"/>
    </property>
    <property name="targetMethod">
        <value>execute</value>
    </property>
</bean>

(二)定义CornTrigger触发器
<!-- 定义CornTrigger触发器 定时执行-->
<bean id="cornTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
    <property name="jobDetail" ref="job2"/>
    <property name="cronExpression" value="0 58 13 * * ? "/>
</bean>

<!-- 定义simpleTrigger触发器 间隔执行-->
<bean id="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
    <property name="jobDetail" ref="jobDetail"></property>
    <property name="repeatCount">
        <value>8</value>
    </property>
    <property name="repeatInterval">
        <value>1000</value>
    </property>
    <property name="startDelay">
        <value>4</value>
    </property>
</bean>

(三)配置调度工厂
<!--配置调度工厂-->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="cornTrigger"/>
        </list>
    </property>
</bean>

四、执行效果



方式三:Spring-Task
  Spring-Task是Spring3.0以后自主开发的定时任务工具,spring task,可以将它比作一个轻量级的Quartz,而且使用起来很简单,除spring相关的包外不需要额外的包,而且支持注解和配置文件两种。注解比较方便,代码量少,但是他人运维时不容易找到配置的地方。
方式三-1:配置文件方式
一、项目结构


二、编写具体的任务内容类
package util;

import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component(value = "taskWork")
public class TaskWork {
    //要调度的具体任务,必须要设置成public
    public void execute1() {
        System.out.printf("execute: %s, Current time: %s\n", 1, LocalDateTime.now());
    }

    public void execute2() {
        System.out.printf("execute: %s, Current time: %s\n", 2, LocalDateTime.now());
    }

    public void execute3() {
        System.out.printf("execute: %s, Current time: %s\n", 3, LocalDateTime.now());
    }

    public void execute4() {
        System.out.printf("execute: %s, Current time: %s\n", 4, LocalDateTime.now());
    }

    public void execute5() {
        System.out.printf("execute: %s, Current time: %s\n", 5, LocalDateTime.now());
    }

    public void execute6() {
        System.out.printf("execute: %s, Current time: %s\n", 6, LocalDateTime.now());
    }

    public void execute7() {
        System.out.printf("execute: %s, Current time: %s\n", 7, LocalDateTime.now());
    }

    public void execute8() {
        System.out.printf("execute: %s, Current time: %s\n", 8, LocalDateTime.now());
    }

    public void execute9() {
        System.out.printf("execute: %s, Current time: %s\n", 9, LocalDateTime.now());
    }

    public void execute10() {
        System.out.printf("execute: %s, Current time: %s\n", 10, LocalDateTime.now());
    }

    public void execute11() {
        System.out.printf("execute: %s, Current time: %s\n", 11, LocalDateTime.now());
    }
}

三、Spring配置文件
<!--扫描计划任务类-->
<context:component-scan base-package="util"/>
<!--定义计划任务资源池-->
<task:scheduler id="taskScheduler" pool-size="100"/>
<!--具体任务配置-->
<task:scheduled-tasks scheduler="taskScheduler">
    <!-- 每半分钟触发任务 -->
    <task:scheduled ref="taskWork" method="execute1" cron="30 * * * * ?"/>
    <!-- 每小时的10分30秒触发任务 -->
    <task:scheduled ref="taskWork" method="execute2" cron="30 10 * * * ?"/>
    <!-- 每天1点10分30秒触发任务 -->
    <task:scheduled ref="taskWork" method="execute3" cron="30 10 1 * * ?"/>
    <!-- 每月20号的1点10分30秒触发任务 -->
    <task:scheduled ref="taskWork" method="execute4" cron="30 10 1 20 * ?"/>
    <!-- 每年10月20号的1点10分30秒触发任务 -->
    <task:scheduled ref="taskWork" method="execute5" cron="30 10 1 20 10 ?"/>
    <!-- 每15秒、30秒、45秒时触发任务 -->
    <task:scheduled ref="taskWork" method="execute6" cron="15,30,45 * * * * ?"/>
    <!-- 15秒到45秒每隔1秒触发任务 -->
    <task:scheduled ref="taskWork" method="execute7" cron="15-45 * * * * ?"/>
    <!-- 每分钟的每15秒时任务任务,每隔5秒触发一次 -->
    <task:scheduled ref="taskWork" method="execute8" cron="15/5 * * * * ?"/>
    <!-- 每分钟的15到30秒之间开始触发,每隔5秒触发一次 -->
    <task:scheduled ref="taskWork" method="execute9" cron="15-30/5 * * * * ?"/>
    <!-- 每小时的0分0秒开始触发,每隔3分钟触发一次 -->
    <task:scheduled ref="taskWork" method="execute10" cron="0 0/3 * * * ?"/>
    <!-- 星期一到星期五的10点15分0秒触发任务 -->
    <task:scheduled ref="taskWork" method="execute11" cron="0 15 10 ? * MON-FRI"/>
</task:scheduled-tasks>

四、执行效果


方式三-2:注解方式
一、项目结构


二、编写具体的任务内容类
package util;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component(value = "taskWork")
public class TaskWork {
    //要调度的具体任务,必须要设置成public
/*每半分钟触发任务*/
    @Scheduled(cron = "30 * * * * ?")
    public void execute1() {
        System.out.printf("execute: %s, Current time: %s\n", 1, LocalDateTime.now());
    }

    /*每小时的10分30秒触发任务*/
    @Scheduled(cron = "30 10 * * * ?")
    public void execute2() {
        System.out.printf("execute: %s, Current time: %s\n", 2, LocalDateTime.now());
    }

    /*每天1点10分30秒触发任务*/
    @Scheduled(cron = "30 10 1 * * ?")
    public void execute3() {
        System.out.printf("execute: %s, Current time: %s\n", 3, LocalDateTime.now());
    }

    /*每月20号的1点10分30秒触发任务*/
    @Scheduled(cron = "30 10 1 20 * ?")
    public void execute4() {
        System.out.printf("execute: %s, Current time: %s\n", 4, LocalDateTime.now());
    }

    /*每年10月20号的1点10分30秒触发任务*/
    @Scheduled(cron = "30 10 1 20 10 ?")
    public void execute5() {
        System.out.printf("execute: %s, Current time: %s\n", 5, LocalDateTime.now());
    }

    /*每15秒、30秒、45秒时触发任务*/
    @Scheduled(cron = "15,30,45 * * * * ?")
    public void execute6() {
        System.out.printf("execute: %s, Current time: %s\n", 6, LocalDateTime.now());
    }

    /*15秒到45秒每隔1秒触发任务*/
    @Scheduled(cron = "15-45 * * * * ?")
    public void execute7() {
        System.out.printf("execute: %s, Current time: %s\n", 7, LocalDateTime.now());
    }

    /*每分钟的每15秒时任务任务,每隔5秒触发一次*/
    @Scheduled(cron = "15/5 * * * * ?")
    public void execute8() {
        System.out.printf("execute: %s, Current time: %s\n", 8, LocalDateTime.now());
    }

    /*每分钟的15到30秒之间开始触发,每隔5秒触发一次*/
    @Scheduled(cron = "15-30/5 * * * * ?")
    public void execute9() {
        System.out.printf("execute: %s, Current time: %s\n", 9, LocalDateTime.now());
    }

    /*每小时的0分0秒开始触发,每隔3分钟触发一次*/
    @Scheduled(cron = "0 0/3 * * * ?")
    public void execute10() {
        System.out.printf("execute: %s, Current time: %s\n", 10, LocalDateTime.now());
    }

    /*星期一到星期五的10点15分0秒触发任务*/
    @Scheduled(cron = "0 15 10 ? * MON-FRI")
    public void execute11() {
        System.out.printf("execute: %s, Current time: %s\n", 11, LocalDateTime.now());
    }
}

三、Spring配置文件
<!--扫描计划任务类-->
<context:component-scan base-package="util"/>
<!--定义计划任务资源池-->
<task:scheduler id="taskScheduler" pool-size="100"/>
<!--开启这个配置,spring才能识别@Scheduled注解 -->
<task:annotation-driven scheduler="taskScheduler" mode="proxy"/>

四、执行效果


ps:配置
JobDetail
JobDetail 用来保存我们作业的详细信息。一个JobDetail可以有多个Trigger,但是一个Trigger只能对应一个JobDetail。下面是JobDetail的一些常用的属性和含义

参数名 类型 备注
name String 任务的名称,必须
group String 任务所在组,默认为DEFAULT
jobClass Class 任务的实现类,必须
description String 描述
jobDataMap JobDataMap 用来给作业提供数据支持的数据结构
volatility Boolean 重启应用之后是否删除任务的相关信息,默认false
durability Boolean 任务完成之后是否依然保留到数据库,默认false
shouldRecover Boolean 应用重启之后时候忽略过期任务,默认false
jobListeners Set 监听器
如:
[java]   view plain   copy
  1. <bean id="myjob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">  
  2.         <property name="jobClass" value="com.tgb.lk.demo.quartz.MyJob1" />  
  3.         <property name="durability" value="true" />  
  4.     </bean>  

JobDataMap
这是一个给作业提供数据支持的数据结构,使用方法和 Java .util.Map一样,非常方便。当一个作业被分配给调度器时,JobDataMap实例就随之生成。
Job有一个StatefulJob子接口,代表有状态的任务,该接口是一个没有方法的标签接口,其目的是让Quartz知道任务的类型,以便采用不同的执行方案。无状态任务在执行时拥有自己的JobDataMap拷贝,对JobDataMap的更改不会影响下次的执行。而有状态任务共享共享同一个JobDataMap实例,每次任务执行对JobDataMap所做的更改会保存下来,后面的执行可以看到这个更改,也即每次执行任务后都会对后面的执行发生影响。
正因为这个原因,无状态的Job可以并发执行,而有状态的StatefulJob不能并发执行,这意味着如果前次的StatefulJob还没有执行完毕,下一次的任务将阻塞等待,直到前次任务执行完毕。有状态任务比无状态任务需要考虑更多的因素,程序往往拥有更高的复杂度,因此除非必要,应该尽量使用无状态的Job。
如果Quartz使用了数据库持久化任务调度信息,无状态的JobDataMap仅会在Scheduler注册任务时保持一次,而有状态任务对应的JobDataMap在每次执行任务后都会进行保存。
JobDataMap实例也可以与一个触发器相关联。这种情况下,对于同一作业的不同触发器,我们可以在JobDataMap中添加不同的数据,以便作业在不同时间执行时能够提供更为灵活的数据支持(学校上午放眼保健操录音第一版,下午放第二版)。
不管是有状态还是无状态的任务,在任务执行期间对Trigger的JobDataMap所做的更改都不会进行持久,也即不会对下次的执行产生影响。
SimpleTrigger
这是一个简单的触发器,通过它我们可以定义触发的时间,并选择性的设定重复的次数和间隔时间。它有以下常用的属性
参数名 参数类型 备注
name String 触发器名称
group String 触发器组名称
repeatCount int 重复次数,注意:如果为0表示不执行,-1表示不限制次数(直到过期),默认为0
repeatInterval long 间隔时间,注意:是以毫秒为单位
startTime Date 开始时间,默认当前时间
endTime Date 过期时间,默认一直执行(直到执行次数已达到repeatCount)

CronTrigger
这个触发器的功能非常强大,而且非常灵活,但需要掌握有关的Cron表达式知识
参数名 参数类型 备注
name String 触发器名称
group String 触发器组名称
cronEx CronExpression 规则表达式
startTime Date 开始时间,默认当前时间
endTime Date 过期时间,默认一直执行(直到执行次数已达到repeatCount)


PS:corn表达式配置 corn配置
  一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。
  按顺序依次为:
  1. 秒(0~59)
  2. 分钟(0~59)
  3. 小时(0~23)
  4. 天(月)(0~31,但是你需要考虑你月的天数)
  5. 月(0~11)
  6. 天(星期)(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT)
  7. 7.年份(1970-2099)

 

字段允许值 允许的特殊字符
========================================================
0-59 , - * /
0-59 , - * /
小时0-23 , - * /
日期1-31 , - * ? / L W C
月份1-12 或者 JAN-DEC , - * /
星期1-7 或者 SUN-SAT , - * ? / L C #
年(可选)留空, 1970-2099 , - * /

  其中每个元素可以是一个值(如6),一个连续区间(9-12),一个间隔时间(8-18/4)(/表示每隔4小时),一个列表(1,3,5),通配符。由于"月份中的日期"和"星期中的日期"这两个元素互斥的,必须要对其中一个设置?


0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
0 0/30 9-17 * * ?   朝九晚五工作时间内每半小时
0 0 12 ? * WED 表示每个星期三中午12点 
"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 ? 3 WED" 每年三月的星期三的下午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 ? * 6L" 每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6L 2002-2005" 2002年至2005年的每月的最后一个星期五上午10:15触发 
"0 15 10 ? * 6#3" 每月的第三个星期五上午10:15触发


  “-”有些子表达式能包含一些范围或列表

  例如:子表达式(天(星期))可以为 “MON-FRI”,“MON,WED,FRI”,“MON-WED,SAT”


  “*”字符代表所有可能的值

  因此,“*”在子表达式()里表示每个月的含义,“*”在子表达式(天(星期))表示星期的每一天

 

  “/”字符用来指定数值的增量

  例如:在子表达式(分钟)里的“0/15”表示从第0分钟开始,每15分钟

       在子表达式(分钟)里的“3/20”表示从第3分钟开始,每20分钟(它和“3,23,43”)的含义一样


  “?”字符仅被用于天(月)和天(星期)两个子表达式,表示不指定值

  当2个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为“?”

 

  “L” 字符仅被用于天(月)和天(星期)两个子表达式,它是单词“last”的缩写。但是它在两个子表达式里的含义是不同的。

  在天(月)子表达式中,“L”表示一个月的最后一天

  在天(星期)自表达式中,“L”表示一个星期的最后一天,也就是SAT

  如果在“L”前有具体的内容,它就具有其他的含义了

  例如:“6L”表示这个月的倒数第6天,“FRIL”表示这个月的最一个星期五

  注意:在使用“L”参数时,不要指定列表或范围,因为这会导致问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值