Spring3.0版本之后提供了@EnableScheduling和@Scheduled来进行定时任务的功能。
使用Spring创建定时任务非常简单,我们可以使用如下两种方式(当然不仅仅只包括这两种):
- 基于注解(@Scheduled)
- 基于接口(SchedulingConfigurer),这里可以自定义线程池的线程数和动态设置定时任务的属性
1. 基于注解的实现如下:
首先在Spring启动类添加注解@EnableScheduling
@SpringBootApplication
@EnableScheduling
public class SpringMain {
public static void main(String[] args) {
// ...
}
}
下面是@EnableScheduling的源码(省略注释):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {
}
其次在我们需要实现任务调度的地方添加@Scheduled注解
@Component
public class Jobs {
@Scheduled(fixedRate = 1000)
public void fixedRateJob() {
// ...
}
@Scheduled(fixedDelay = 1000)
public void fixedDelayJob() {
// ...
}
@Scheduled(initialDelay = 1000)
public void initDelayJob() {
// ...
}
@Scheduled(cron = "0/1 * * * * ? ")
public void cronJob() {
// ...
}
@Scheduled(fixedDelayString = "")
public void fixedDelayStringJob() {
// ...
}
@Scheduled(fixedRateString = "")
public void fixedRateStringJob() {
// ...
}
@Scheduled(initialDelayString = "")
public void initDelayStringJob() {
// ...
}
@Scheduled(zone = "")
public void zoneJob() {
// ...
}
}
下面是@Scheduled的源码(省略注释,添加解释):
// cron表达式
// 可以去 [Cron - 在线Cron表达式生成器 (ciding.cc)](http://cron.ciding.cc/) 查看
String cron() default "";
// 时区,一般留空
String zone() default "";
// 上一次执行完毕时间点之后多长时间再执行,单位是毫秒
long fixedDelay() default -1;
// 与fixedDelay一样,只是使用了字符串的形式,可以支持占位符
String fixedDelayString() default "";
// 上一次开始执行时间点之后多长时间再执行,单位是毫秒
long fixedRate() default -1;
// 与fixedRate一样,只是使用字符串的形式,可以支持占位符
String fixedRateString() default "";
// 第一次延迟多长时间后再执行
long initialDelay() default -1;
// 与initialDelay一样,只是使用字符串的形式,可以支持占位符
String initialDelayString() default "";
2. 基于接口(SchedulingConfigurer),这里可以自定义线程池的线程数和动态设置定时任务的属性
为什么基于接口去实现,因为通过查看Spring的源码,发现Spring提供的任务调度是创建了只有一个线程数的线程池。
具体代码如下:
if (this.taskScheduler == null) {
this.localExecutor = Executors.newSingleThreadScheduledExecutor();
this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
}
这样就会带来一些问题,比如假设我们有个调度任务A,每次执行的时间间隔是5分钟,但是这个调度任务A执行的时间会大于5分钟,因为只有一个线程在执行任务,那么本来5分钟之后应该再次执行的任务会产生延迟,会产生任务无法执行的错觉。
所以我们这里可以通过实现Spring提供的SchedulingConfigurer接口来设置:
- 自定义线程池
- 动态添加任务和修改任务属性
代码如下:
@Configuration
public class DynamicScheduleConfigurer implements SchedulingConfigurer
{
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar)
{
// 1. 自定义Scheduler
// ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
// taskScheduler.setPoolSize(50);
// taskRegistrar.setTaskScheduler(taskScheduler);
// 2. 添加任务,修改属性
taskRegistrar.addTriggerTask(
// 添加定时任务
() -> System.out.println("xxx"),
// 设置执行周期(Trigger)
triggerContext -> {
String cron = "0/5 * * * * ? *";
return new CronTrigger(cron).nextExecutionTime(triggerContext);
}
);
}
}
通过实现这个方法,我们可以将任务的信息放在数据库当中,当程序启动的时候读取数据库任务的信息进行任务的添加,运行时更改任务的信息。