SpringBoot定时任务可以用于周期性重复工作的编写,其应用简单,能满足绝大多数需求。在Java中实现定时任务主要有三种实现形式:一是使用JDK 自带的 Timer,二是使用第三方组件 Quartz,三是使用 Spring Task。 前两个不是我们今天讨论的重点,暂且不提,今天只讲一下spring定时任务的三个应用场景:一是最简单的使用注解配置定时任务;二是通过配置文件开关定时任务;三是通过服务动态开启开启关闭定时任务。
1. @Scheduled注解
使用注解方式进行开发是最简单的应用方式,一般只需要两步即可实现:在需要定时启动的服务上添加@Scheduled(initialDelay=1000, fixedDelay = 2000)注解;在启动类上添加启动定时任务的注解@EnableScheduling
@Component
public class ScheduledTask {
@Scheduled(initialDelay=1000, fixedDelay = 2000)
public void task1() {
System.out.println("延迟1000毫秒后执行,任务执行完2000毫秒之后执行!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Scheduled(initialDelay=1000, fixedRate = 2000)
public void task2() {
System.out.println("延迟1000毫秒后执行,之后每2000毫秒执行一次!");
}
@Scheduled(cron = "*/2 * * * * ?")
public void task3() {
System.out.println("每2秒执行一次!");
}
}
启动类上配置注解
@EnableScheduling
@SpringBootApplication
public class SchedulingApplication {
public static void main(String[] args) {
SpringApplication.run(SchedulingApplication.class, args);
}
}
2.配置文件动态配置定时任务启动
很多时候我们开发的定时任务可能需要按照现场情况来去启动或者关闭,这就想通过配置文件来进行定时任务的启动和关闭,这个时候可以借助Condition 和ScheduledAnnotationBeanPostProcessor 来实现。在上面代码的基础上,我们需要去掉启动类上的@EnableScheduling注解,然后在配置文件中添加我们的自定义配置:
enable:
scheduling: false
添加SchedulerCondition获取配置文件配置
public class SchedulerCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return Boolean.valueOf(context.getEnvironment().getProperty("enable.scheduling"));
}
}
添加Scheduler
@Configuration
public class Scheduler {
@Conditional(SchedulerCondition.class)
@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
return new ScheduledAnnotationBeanPostProcessor();
}
}
然后再去启动项目时,程序就会根据我们的自定义配置来进行启动关闭定时任务。
3.服务启动中动态开启关闭定时任务
上面的两种实现形式其配置粒度都是整个服务的,然而,有时候服务中可能存在不同的定时任务,其开启关闭的要求不同,所以就需要对单个定时任务进行开启关闭,还要保证服务正常运行,这就需要使用另外的设计了。
package com.lordum.threadpoolpractice.controller;
import com.lordum.threadpoolpractice.config.AppConfiguration;
import com.lordum.threadpoolpractice.tasks.MyRunnable1;
import com.lordum.threadpoolpractice.tasks.MyRunnable2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.concurrent.ScheduledFuture;
@Slf4j
@RestController
@Api("定时任务")
@RequestMapping("/quartz/task")
public class DynamicTaskController {
@Autowired
private AppConfiguration appConfiguration;
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
private ScheduledFuture<?> future1;
private ScheduledFuture<?> future2;
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
return new ThreadPoolTaskScheduler();
}
@PostMapping("/startCron1")
@ApiOperation("开始定时任务1")
public ResponseEntity<String> startCron1() {
if (future1 != null) {
return ResponseEntity.ok("定时任务1已经启动,请不要重复启动");
}
future1 = threadPoolTaskScheduler.schedule(new MyRunnable1("url"), triggerContext ->
new CronTrigger(appConfiguration.getCorn1()).nextExecutionTime(triggerContext));
System.out.println("DynamicTask.startCron1()");
return ResponseEntity.ok().build();
}
@PostMapping("/stopCron1")
@ApiOperation("关闭定时任务1")
public ResponseEntity<String> stopCron1() {
if (future1 != null) {
future1.cancel(true);
}
System.out.println("DynamicTask.stopCron1()");
future1 = null;
return ResponseEntity.ok().build();
}
@PostMapping("/startCron2")
@ApiOperation("开始定时任务2")
public ResponseEntity<String> startCron2() {
if (future2 != null) {
return ResponseEntity.ok("定时任务2已经启动,请不要重复启动");
}
future2 = threadPoolTaskScheduler.schedule(new MyRunnable2(), triggerContext ->
new CronTrigger(appConfiguration.getCorn2()).nextExecutionTime(triggerContext));
System.out.println("DynamicTask.startCron2()");
return ResponseEntity.ok().build();
}
@PostMapping("/stopCron2")
@ApiOperation("关闭定时任务2")
public ResponseEntity<String> stopCron2() {
if (future2 != null) {
future2.cancel(true);
}
System.out.println("DynamicTask.stopCron2()");
future2 = null;
return ResponseEntity.ok().build();
}
}
这里面的MyRunnable1和MyRunnable2是我们要执行的定时任务,需要实现Runnable接口
package com.lordum.threadpoolpractice.tasks;
import lombok.AllArgsConstructor;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Callable;
@AllArgsConstructor
public class MyRunnable1 implements Runnable {
private String testParam;
@Override
public void run() {
System.out.println("first DynamicTask,获取到参数:[" +testParam+ "]-" + Thread.currentThread().getName() + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
package com.lordum.threadpoolpractice.tasks;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MyRunnable2 implements Runnable {
@Override
public void run() {
System.out.println("second DynamicTask," + Thread.currentThread().getName() + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
Controller类中添加了配置了,需要读取配置文件中的corn配置。
package com.lordum.threadpoolpractice.config;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "app")
@EqualsAndHashCode(callSuper = false)
public class AppConfiguration {
private String corn1;
private String corn2;
}
自定义配置:
app:
corn1: 0/2 * * * * ?
corn2: 0/5 * * * * ?
4.Cron表达式
Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域。每一个域代表一个含义,Cron有如下两种语法格式:
Seconds Minutes Hours DayofMonth Month DayofWeek Year
Seconds Minutes Hours DayofMonth Month DayofWeek
corn从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份
eg:
*/2 * * * * ? 表示每2秒执行一次!
0 0 2 1 * ? * 表示在每月的1日的凌晨2点调度任务
0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作业