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

相关文章

  • JavaSE反射、枚举、lambda表达式常用方法举例

    JavaSE反射、枚举、lambda表达式常用方法举例

    这篇文章主要介绍了JavaSE反射、枚举、lambda表达式常用方法的相关资料,反射允许在运行时检查和操作类、方法和属性,枚举提供了一种更安全和优雅的方式来定义常量,而Lambda表达式则简化了函数式接口的实现,需要的朋友可以参考下
    2024-12-12
  • SpringBoot四种读取properties文件的方式(小结)

    SpringBoot四种读取properties文件的方式(小结)

    这篇文章主要介绍了SpringBoot四种读取properties文件的方式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • 如何在Spring中自定义scope的方法示例

    如何在Spring中自定义scope的方法示例

    这篇文章主要介绍了如何在Spring中自定义scope的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-02-02
  • JAVA自定义注解详情

    JAVA自定义注解详情

    这篇文章主要介绍了Java自定义注解,结合实例形式总结分析了java常见的自定义注解类型、功能、用法及操作注意事项,需要的朋友可以参考下
    2021-10-10
  • IntelliJ IDEA设置显示内存指示器和设置内存大小的方法

    IntelliJ IDEA设置显示内存指示器和设置内存大小的方法

    这篇文章主要介绍了IntelliJ IDEA设置显示内存指示器和设置内存大小的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-04-04
  • Java 添加Word目录的2种方法示例代码详解

    Java 添加Word目录的2种方法示例代码详解

    目录是一种能够快速、有效地帮助读者了解文档或书籍主要内容的方式。这篇文章主要介绍了Java 添加Word目录的2种方法 ,需要的朋友可以参考下
    2019-06-06
  • springboot项目mysql-connector-java默认版本如何查看

    springboot项目mysql-connector-java默认版本如何查看

    这篇文章主要介绍了springboot项目mysql-connector-java默认版本如何查看问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • Spring Boot利用Thymeleaf发送Email的方法教程

    Spring Boot利用Thymeleaf发送Email的方法教程

    spring Boot默认就是使用thymeleaf模板引擎的,下面这篇文章主要给大家介绍了关于在Spring Boot中利用Thymeleaf发送Email的方法教程,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-08-08
  • Java 18 新特性之Web服务器 jwebserver功能

    Java 18 新特性之Web服务器 jwebserver功能

    JEP 408: Simple Web Server,是这次Java 18推出的一个比较独立的全新功能点。我们可以通过命令行工具来启动一个提供静态资源访问的迷你Web服务器,本文通过一个构建HTML页面的例子,来尝试一下jwebserver的功能
    2022-04-04
  • 如何解决Java程序包不存在问题

    如何解决Java程序包不存在问题

    在使用IDEA 2022版本和Maven项目中,可能遇到Java程序包不存在的问题,这是由于IDEA的小bug引起的,文章提供了两种解决方法:第一种是在IDEA的Settings中配置,并在pom.xml中添加特定代码刷新Maven;第二种是通过IDEA的设置菜单修改Maven导入配置
    2024-10-10

最新评论