深入浅出Spring task定时任务
在工作中有用到spring task作为定时任务的处理,spring通过接口TaskExecutor
和TaskScheduler
这两个接口的方式为异步定时任务提供了一种抽象。这就意味着spring容许你使用其他的定时任务框架,当然spring自身也提供了一种定时任务的实现:spring task。spring task支持线程池,可以高效处理许多不同的定时任务。同时,spring还支持使用Java自带的Timer
定时器和Quartz
定时框架。限于篇幅,这里将只介绍spring task的使用。
其实,官方文档已经介绍地足够详细,只不过都是英文版,所以为了更好地理解并使用spring task,首先会对spring task的实现原理做一个简单的介绍,然后通过实际代码演示spring task是如何使用的。这里会涉及到一个很重要的知识点:cron表达式。
TaskExecutor和TaskScheduler
TaskExecutor是spring task的第一个抽象,它很自然让人联想到jdk中concurrent包下的Executor
,实际上TaskExecutor就是为区别于Executor
才引入的,而引入TaskExecutor的目的就是为定时任务的执行提供线程池的支持,那么,问题来了,为什么spring不直接使用jdk自带的Executor呢?TaskExecutor源码如下?
public interface TaskExecutor extends Executor {
void execute(Runnable var1);
}
那么,答案很显然,TaskExecutor提供的线程池支持也是基于jdk自带的Executor的。用法于Executor没有什么不同。
TaskScheduler是spring task的第二个抽象,那么从字面的意义看,TaskScheduler就是为了提供定时任务的支持咯。TaskScheduler需要传入一个Runnable的任务做为参数,并指定需要周期执行的时间或者触发器,这样Runnable任务就可以周期性执行了。传入时间很好理解,有意思的是传入一个触发器(Trigger
)的情况,因为这里需要使用cron表达式去触发一个定时任务,所以有必要先了解下cron表达式的使用。
在spring 4.x中已经不支持7个参数的cronin表达式了,要求必须是6个参数(具体哪个参数后面会说)。cron表达式的格式如下:
{秒} {分} {时} {日期(具体哪天)} {月} {星期}
,
-
*
/
,,
表示特定的某一秒才会触发任务,-
表示一段时间内会触发任务,*
表示每一秒都会触发,/
表示从哪一个时刻开始,每隔多长时间触发一次任务。?
,表示与{星期}互斥,即意味着若明确指定{星期}触发,则表示{日期}无意义,以免引起冲突和混乱。?
,表达的含义是与{日期}互斥,即意味着若明确指定{日期}触发,则表示{星期}无意义。比如下面这个cron表达式:
// 表达的含义是:每半分钟触发一次任务
30 * * * * ?
spring提供了一个CronTrigger
,通过传入一个Runnable任务和CronTrigger,就可以使用cron表达式去指定定时任务了,是不是非常方面。实际上,在工程实践上,cron表达式也是使用很多的。实际上,是执行了下面的代码:
scheduler.schedule(task, new CronTrigger("30 * * * * ?"));
TaskScheduler抽象的好处是让需要执行定时任务的代码不需要指定特定的定时框架(比如Timer和Quartz)。TaskScheduler的更简单的实现是ThreadPoolTaskScheduler
,它实际上代理一个jdk中的SchedulingTaskExecutor
,并且也实现了TaskExecutor接口,所以需要经常执行定时任务的场景可以使用这个实现(Spring推荐)。我们再来看一下TaskExecutor和TaskScheduler的类继承关系:
通常而言,使用spring task实现定时任务有两种方式:注解和xml配置文件。这里使用xml配置文件的方式加以说明。
实战
创建Maven工程,pom.xml:
4.0.0
com.rhwayfun
sring-task-demo
1.0-SNAPSHOT
org.springframework
spring-context
4.2.4.RELEASE
org.apache.maven.plugins
maven-compiler-plugin
3.5.1
1.8
开发需要执行定时任务的方法:
package com.rhwayfun.task;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @author ZhongCB
* @date 2016年09月10日 14:30
* @description
*/
@Component
public class App {
public void execute1(){
System.out.printf("Task: %s, Current time: %s
", 1, LocalDateTime.now());
}
public void execute2(){
System.out.printf("Task: %s, Current time: %s
", 2, LocalDateTime.now());
}
public void execute3(){
System.out.printf("Task: %s, Current time: %s
", 3, LocalDateTime.now());
}
public void execute4(){
System.out.printf("Task: %s, Current time: %s
", 4, LocalDateTime.now());
}
public void execute5(){
System.out.printf("Task: %s, Current time: %s
", 5, LocalDateTime.now());
}
public void execute6(){
System.out.printf("Task: %s, Current time: %s
", 6, LocalDateTime.now());
}
public void execute7(){
System.out.printf("Task: %s, Current time: %s
", 7, LocalDateTime.now());
}
public void execute8(){
System.out.printf("Task: %s, Current time: %s
", 8, LocalDateTime.now());
}
public void execute9(){
System.out.printf("Task: %s, Current time: %s
", 9, LocalDateTime.now());
}
public void execute10(){
System.out.printf("Task: %s, Current time: %s
", 10, LocalDateTime.now());
}
public void execute11(){
System.out.printf("Task: %s, Current time: %s
", 11, LocalDateTime.now());
}
}
spring配置文件如下:
编写测试代码:
package com.rhwayfun.task;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author ZhongCB
* @date 2016年09月10日 14:55
* @description
*/
public class AppTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/app-context-task.xml");
}
}
运行测试代码,控制台会定时输出每个定时任务的日志信息,说明测试通过。
小插曲
由于项目使用jdk 1.8进行开发,所以初始的时候每次pom文件发生修改,编译器的版本又变成了jdk 1.5,后面发现需要在pom文件中添加build便签那部分才能将默认的编译器进行修改。也算一个小收获了。