Spring实现定时任务的两种方法详解
作者:JWASX
1. 概要
时间轮的文章在上几篇文章中就已经介绍完了,那么 Java 中 Spring 的定时任务肯定也是跑不掉的,那首先要学 Spring 里面定时任务的逻辑,就得先学会用法。关于 Spring,其实提供了两种方式实现定时任务,一种是注解,还有一种就是接口了,下面我就会讲一下这两种方式的用法,在下一篇文章中我们会继续深入 Spring 源码,去讲解 Spring 里面的定时任务到底是怎么实现的。
2. 接口方式动态配置
2.1 抽象类
通过接口的方式可以实现时间的动态配置,先看下抽象类:
@Slf4j
@Configuration
@EnableScheduling
public abstract class ScheduledConfig implements SchedulingConfigurer {
//定时任务周期表达式
private String cron;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
//设置线程池,可开启多线程
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addTriggerTask(
// 执行定时任务
() -> {
taskService();
},
triggerContext -> {
// 这里就是动态获取任务队列的逻辑
cron = getCron();
if(cron == null){
throw new RuntimeException("cron not exist");
}
// 重新获取cron表达式
CronTrigger trigger = new CronTrigger(cron);
return trigger.nextExecutionTime(triggerContext);
}
);
}
private Executor taskExecutor() {
return BeanUtils.getBean(ThreadPoolTaskScheduler.class);
}
/**
* @Description: 执行定时任务
* @param:
* @return: void
* @Author:
* @Date: 2020/8/28
*/
public abstract void taskService();
/**
* @Description: 获取定时任务周期表达式
* @param:
* @return: java.lang.String
* @Author:
* @Date: 2020/8/28
*/
public abstract String getCron();
/**
* 判断某任务是否开启
* @return
*/
public abstract int getOpen();
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
executor.setPoolSize(16);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
return executor;
}
}
实现 SchedulingConfigurer 接口之后重写 configureTasks 方法,可以设置任务的具体调度逻辑,也就是 taskRegistrar.addTriggerTask。那么里面的具体逻辑是什么呢?
// 执行定时任务
() -> {
taskService();
},
triggerContext -> {
// 这里就是动态获取任务队列的逻辑
cron = getCron();
if(cron == null){
throw new RuntimeException("cron not exist");
}
// 重新获取cron表达式
CronTrigger trigger = new CronTrigger(cron);
return trigger.nextExecutionTime(triggerContext);
}
定时任务里面执行具体的任务,同时重写触发器的 nextExecutionTime 方法,在里面重新获取 cron 表达式然后更新下一次的执行时间。
同时最后设置了执行任务的线程池,这样定时任务执行的逻辑就会交给线程池去执行,不需要阻塞当前的工作线程。
2.2 具体实现类
@Component
public class ScheduledJob extends ScheduledConfig {
String cron = "0/1 * * * * ?";
long count = 0;
@Override
public void taskService() {
int open = getOpen();
if(open == 1){
count++;
PrintUtils.printLog("执行任务!!!, 当前表达式:%s", cron);
}
}
@Override
public String getCron() {
if(count == 5){
cron = "0/5 * * * * ?";
}
return cron;
}
@Override
public int getOpen() {
return 1;
}
}
里面的 getOpen() 就是去获取定时任务是否开启的,getCron() 是获取任务执行 cron 表达式,这两个配置都可以写在数据库里面去更改。
2.3 工具类
首先是 Beanutils,从 Spring 容器中获取对应的 bean
@Component
public class BeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
synchronized (BeanUtils.class){
BeanUtils.applicationContext = applicationContext;
}
}
public static <T> T getBean(String beanName) {
synchronized (BeanUtils.class){
if(applicationContext.containsBean(beanName)){
return (T) applicationContext.getBean(beanName);
}else{
return null;
}
}
}
public static <T> Map<String, T> getBeansOfType(Class<T> baseType){
synchronized (BeanUtils.class) {
return applicationContext.getBeansOfType(baseType);
}
}
public static <T> T getBean(Class<T> baseType){
synchronized (BeanUtils.class) {
return applicationContext.getBean(baseType);
}
}
}
然后就是打印的工具类 PrintUtils
public class PrintUtils {
static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS");
public static String now(){
return LocalDateTime.now().format(FORMATTER);
}
public static void printLog(String str, Object...args){
System.out.println(now() + ": " + String.format(str, args));
}
}
2.4 执行结果

输出结果如上所示:可以看到在第五次输出之后 cron 被改成了 5s 执行一次,但是注意第 6 次距离第 5 次是 3s,可能是内部的一些其他的处理,后面源码再看。
3. 注解方式静态配置
在 Spring 中,默认情况下,@Scheduled 注解标记的任务是由 Spring 内部的一个单线程调度器(TaskScheduler)来执行的。但是如果我们需要用自定义的线程池来执行这些任务,可以通过配置自定义的 TaskScheduler 或 ThreadPoolTaskScheduler 来实现。ThreadPoolTaskScheduler 里面是通过 ScheduledExecutorService 来实现的。
@Scheduled 注解可以实现定时任务、固定速率任务、固定延时任务三种,下面就一种一种来看,不过在看任务之前,先来看下一些必要的配置。
1.首先是开启 @EnableScheduling 注解
@SpringBootApplication
@EnableScheduling
public class ScheduleApplication {
public static void main(String[] args) {
SpringApplication.run(ScheduleApplication.class, args);
}
}
2.然后是配置线程池
@Configuration
public class SchedulerConfig {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10); // 设置线程池大小
scheduler.setThreadNamePrefix("MyTaskScheduler-"); // 设置线程名称前缀
scheduler.initialize(); // 初始化调度器
return scheduler;
}
}
3.1 定时任务
@Service
public class ScheduledService {
@Scheduled(cron = "0/5 * * * * ?")
public void runEvery15Minutes() {
PrintUtils.printLog( "This task runs every 5 seconds");
}
}
定时任务通过 cron 表达式来实现,输出结果如下:

3.2 固定延时任务
定时/延时任务-ScheduledThreadPoolExecutor的使用,固定速率和固定延时就看上面的区别就行了,因为 Spring 底层 Debug 了下默认创建出来的就是 ScheduledThreadPoolExecutor。不过其实核心思想都是一样的,就算自己去实现了,固定速率和固定延时的核心都是一样的,只是实现上会不一样,可以对比下 ScheduledThreadPoolExecutor 和 Timer 的实现。Timer 的解析也在往期文章里面。定时/延时任务-Timer用法
代码如下所示:
@Service
public class ScheduledService {
@Scheduled(fixedDelay = 5000)
public void runEvery15Minutes() throws InterruptedException {
Thread.sleep(6000);
PrintUtils.printLog("This task runs every 5 seconds, sleep 6 seconds");
}
}结果输出如下,固定延时就是要确保本次任务执行的时间距离上一次任务执行完成的时间相差 5s

3.3 固定速率任务
@Service
public class ScheduledService {
@Scheduled(fixedRate = 5000)
public void runEvery15Minutes() throws InterruptedException {
Thread.sleep(6000);
PrintUtils.printLog("This task runs every 5 seconds, sleep 6 seconds");
}
}
执行结果如下:

固定速率就是确保本次任务执行时间距离上次任务执行的时间是期望时间 + fixedRate,如果看不懂可以去看上面那两篇文章,很清晰。
3.4 动态配置
@Scheduled 注解使用固定的时间间隔或 cron 表达式来定义任务的执行频率,下面就来演示下。
- 在 application.properties 或 application.yml 中定义动态配置
- 在 @Scheduled 注解中引用这些字段
application.properties 配置文件如下:
# 动态配置时间间隔(单位:毫秒) custom.fixedRate=5000 # 动态配置cron表达式 custom.cronExpression=0/5 * * * * ?
@Service
public class ScheduledService {
@Scheduled(fixedRateString = "${custom.fixedRate}")
public void runEvery15Minutes() throws InterruptedException {
Thread.sleep(6000);
PrintUtils.printLog("This task runs every 5 seconds, sleep 6 seconds");
}
}
@Scheduled(cron = "${custom.cronExpression}")
public void runEvery15Minutes() throws InterruptedException {
Thread.sleep(6000);
PrintUtils.printLog("This task runs every 5 seconds, sleep 6 seconds");
}其中一个的输出结果如下,可以看到都是能正常调度的。

以上就是Spring实现定时任务的两种方法详解的详细内容,更多关于Spring定时任务的资料请关注脚本之家其它相关文章!
