在我们日常开发中,有一些应用场景有实现计划任务的需要,如在夜晚启动对账处理,当日数据推历史及次日初始化,定时放出某可用资源等操作。
使用quartz和Spring-Task都可在Java后端实现此计划任务功能,而每种方式又可细分成两种小的方式,本文进行简明的介绍。其中方式一、二基于quartz,区别是使用JobDetailFactoryBean或MethodInvokingJobDetailFactoryBean,后者是由Spring提供支持,不需要集成quartz的抽象类要更灵活一些;Spring从3之后就提供了Spring-Task模块直接实现了计划任务支持,但它属于轻量化的。方式三、四使用Spring-Task实现,区别是使用xml或注解配置。四种方式均亲测有源码。
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 定时任务配置-->
<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触发器 定时执行-->
<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>
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 定时任务配置-->
<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触发器 定时执行-->
<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>
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());
}
}
<!--扫描计划任务类-->
<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>
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());
}
}
<!--扫描计划任务类-->
<context:component-scan base-package="util"/>
<!--定义计划任务资源池-->
<task:scheduler id="taskScheduler" pool-size="100"/>
<!--开启这个配置,spring才能识别@Scheduled注解 -->
<task:annotation-driven scheduler="taskScheduler" mode="proxy"/>
参数名 | 类型 | 备注 |
name | String | 任务的名称,必须 |
group | String | 任务所在组,默认为DEFAULT |
jobClass | Class | 任务的实现类,必须 |
description | String | 描述 |
jobDataMap | JobDataMap | 用来给作业提供数据支持的数据结构 |
volatility | Boolean | 重启应用之后是否删除任务的相关信息,默认false |
durability | Boolean | 任务完成之后是否依然保留到数据库,默认false |
shouldRecover | Boolean | 应用重启之后时候忽略过期任务,默认false |
jobListeners | Set | 监听器 |
- <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>
参数名 | 参数类型 | 备注 |
name | String | 触发器名称 |
group | String | 触发器组名称 |
repeatCount | int | 重复次数,注意:如果为0表示不执行,-1表示不限制次数(直到过期),默认为0 |
repeatInterval | long | 间隔时间,注意:是以毫秒为单位 |
startTime | Date | 开始时间,默认当前时间 |
endTime | Date | 过期时间,默认一直执行(直到执行次数已达到repeatCount) |
参数名 | 参数类型 | 备注 |
name | String | 触发器名称 |
group | String | 触发器组名称 |
cronEx | CronExpression | 规则表达式 |
startTime | Date | 开始时间,默认当前时间 |
endTime | Date | 过期时间,默认一直执行(直到执行次数已达到repeatCount) |
- 秒(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 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”参数时,不要指定列表或范围,因为这会导致问题。