您的位置:首页 > 编程语言 > Java开发

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

2017-06-27 16:38 666 查看
范培忠 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
136d2
">
<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的一些常用的属性和含义

参数名类型备注
nameString任务的名称,必须
groupString任务所在组,默认为DEFAULT
jobClassClass任务的实现类,必须
descriptionString描述
jobDataMapJobDataMap用来给作业提供数据支持的数据结构
volatilityBoolean重启应用之后是否删除任务的相关信息,默认false
durabilityBoolean任务完成之后是否依然保留到数据库,默认false
shouldRecoverBoolean应用重启之后时候忽略过期任务,默认false
jobListenersSet监听器
如:
[java] view
plain copy

<bean id="myjob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">  

        <property name="jobClass" value="com.tgb.lk.demo.quartz.MyJob1" />  

        <property name="durability" value="true" />  

    </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
这是一个简单的触发器,通过它我们可以定义触发的时间,并选择性的设定重复的次数和间隔时间。它有以下常用的属性

参数名参数类型备注
nameString触发器名称
groupString触发器组名称
repeatCountint重复次数,注意:如果为0表示不执行,-1表示不限制次数(直到过期),默认为0
repeatIntervallong间隔时间,注意:是以毫秒为单位
startTimeDate开始时间,默认当前时间
endTimeDate过期时间,默认一直执行(直到执行次数已达到repeatCount)
CronTrigger
这个触发器的功能非常强大,而且非常灵活,但需要掌握有关的Cron表达式知识

参数名参数类型备注
nameString触发器名称
groupString触发器组名称
cronExCronExpression规则表达式
startTimeDate开始时间,默认当前时间
endTimeDate过期时间,默认一直执行(直到执行次数已达到repeatCount)
PS:corn表达式配置 corn配置
  一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素。
  按顺序依次为:

秒(0~59)
分钟(0~59)
小时(0~23)
天(月)(0~31,但是你需要考虑你月的天数)
月(0~11)
天(星期)(1~7 1=SUN 或 SUN,MON,TUE,WED,THU,FRI,SAT)
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”参数时,不要指定列表或范围,因为这会导致问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: