Spring实现定时任务的两种方法详解

 更新时间:2024年12月15日 11:17:12   作者:JWASX  
Spring提供了两种方式实现定时任务,一种是注解,还有一种就是接口了,这篇文章主要为大家介绍了这两种方法的具体实现方法,需要的可以参考下

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定时任务的资料请关注脚本之家其它相关文章!

相关文章

  • Java请求Http接口OkHttp超详细讲解(附带工具类)

    Java请求Http接口OkHttp超详细讲解(附带工具类)

    这篇文章主要给大家介绍了关于Java请求Http接口OkHttp超详细讲解的相关资料,OkHttp是一款优秀的HTTP客户端框架,文中通过代码示例介绍的非常详细,需要的朋友可以参考下
    2024-02-02
  • Java 线程的优先级(setPriority)案例详解

    Java 线程的优先级(setPriority)案例详解

    这篇文章主要介绍了Java 线程的优先级(setPriority)案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • 零基础写Java知乎爬虫之进阶篇

    零基础写Java知乎爬虫之进阶篇

    前面几篇文章,我们都是简单的实现了java爬虫抓取内容的问题,那么如果遇到复杂情况,我们还能继续那么做吗?答案当然是否定的,之前的仅仅是入门篇,都是些基础知识,给大家练手用的,本文我们就来点高大上的东西
    2014-11-11
  • 详解Java策略模式

    详解Java策略模式

    今天给大家带来的是关于Java的相关知识,文章围绕着Java策略模式展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • lombok注解@Data使用在继承类上时出现警告的问题及解决

    lombok注解@Data使用在继承类上时出现警告的问题及解决

    Lombok的@Data注解简化了实体类代码,但在子类中使用时会出现警告,指出equals和hashCode方法不会考虑父类属性,解决方法有两种:一是在父类上使用@EqualsAndHashCode(callSuper=true)注解;二是通过配置lombok.config文件,均能有效解决警告问题
    2024-10-10
  • SpringBoot项目打成War包部署的方法步骤

    SpringBoot项目打成War包部署的方法步骤

    这篇文章主要介绍了springboot项目如何打war包流程的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • springboot jpa之返回表中部分字段的处理详解

    springboot jpa之返回表中部分字段的处理详解

    这篇文章主要介绍了springboot jpa之返回表中部分字段的处理详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Springboot项目中单元测试时注入bean失败的解决方案

    Springboot项目中单元测试时注入bean失败的解决方案

    这篇文章主要介绍了Springboot项目中单元测试时注入bean失败的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • springboot对数据库密码加密的实现

    springboot对数据库密码加密的实现

    这篇文章主要介绍了springboot对数据库密码加密的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • SpringBoot文件上传与下载功能实现详解

    SpringBoot文件上传与下载功能实现详解

    文件上传与下载是Web应用开发中常用的功能之一。接下来我们将讨论如何在Spring Boot的Web应用开发中,如何实现文件的上传与下载,感兴趣的可以了解一下
    2022-10-10

最新评论