springboot如何配置定时任务

概述

在Java环境下创建定时任务有多种方式:

  • 使用while循环配合 Thread.sleep(),虽然稍嫌粗陋但也勉强可用
  • 使用 Timer和 TimerTask
  • 使用 ScheduledExecutorService
  • 定时任务框架,如Quartz

在SpringBoot下执行定时任务无非也就这几种方式(主要还是后两种)。只不过SpringBoot做了许多底层的工作,我们只需要做些简单的配置就行了。

通过注解实现定时任务

在SpringBoot中仅通过注解就可以实现常用的定时任务。步骤就两步:

在启动类中添加 @EnableScheduling注解

@EnableScheduling
@SpringBootApplication
public class MyApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
 
}

在目标方法中添加 @Scheduled注解,同时在 @Scheduled注解中添加触发定时任务的元数据。

    @Scheduled(fixedRate = 1000)
    public void job() {
        System.out.println(Thread.currentThread().getId() + " ----- job1 ----- " + System.currentTimeMillis());
    }

注意: 目标方法需要没有任何参数,并且返回类型为 void 。

这里的定时任务元数据是“fixRate=1000”,意思是固定间隔每1000毫秒即执行一次该任务。

再来看几个 @Schedule注解的参数:

  • fixedRate:设置定时任务执行的时间间隔,该值为当前任务启动时间与下次任务启动时间之差;
  • fixedDelay:设置定时任务执行的时间间隔,该值为当前任务结束时间与下次任务启动时间之差;
  • cron:通过cron表达式来设置定时任务启动时间,在Cron Generator网站可以直接生成cron表达式。

这样创建的定时任务存在一个问题:如存在多个定时任务,这些任务会同步执行,也就是说所有的定时任务都是在一个线程中执行。

再添几个定时任务来执行下看看:

    @Scheduled(fixedRate = 1000)
    public void job1() {
        System.out.println(Thread.currentThread().getId() + " ----- job1 ----- " + System.currentTimeMillis());
    }
 
    @Scheduled(fixedRate = 1000)
    public void job2() {
        System.out.println(Thread.currentThread().getId() + " ----- job2 ----- " + System.currentTimeMillis());
    }
 
    @Scheduled(fixedRate = 1000)
    public void job3() {
        System.out.println(Thread.currentThread().getId() + " ----- job3 ----- " + System.currentTimeMillis());
    }

代码中一共创建了三个定时任务,每个定时任务的执行间隔都是1000毫秒,在任务体中输出了执行任务的线程ID和执行时间。

看下执行结果:

20 ----- job3 ----- 1573120568263
20 ----- job1 ----- 1573120568263
20 ----- job2 ----- 1573120568263
20 ----- job3 ----- 1573120569264
20 ----- job1 ----- 1573120569264
20 ----- job2 ----- 1573120569264
20 ----- job3 ----- 1573120570263
20 ----- job1 ----- 1573120570263
20 ----- job2 ----- 1573120570263

可以看到这三个定时任务的执行有如下的特点:

  • 所有的定时任务每次都是在同一个线程上执行;
  • 虽然未必是job1第一个开始执行,但是每批任务的执行次序是固定的——这是由fixRate参数决定的

这样的定时任务已经能够覆盖绝大部分的使用场景了,但是它的缺点也很明显:前面的任务执行时间过长必然会影响之后的任务的执行。为了解决这个问题,我们需要异步执行定时任务。接下来的部分我们将主要着眼于如何实现异步执行定时任务。

通过@Async注解实现异步定时任务

最常用的方式是使用 @Async注解来实现异步执行定时任务。启用 @Async注解的步骤如下:

在启动类中添加 @EnableAsync注解:

@EnableAsync
@EnableScheduling
@SpringBootApplication
public class MyApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
    
}

在定时任务方法上添加 @Async注解

    @Async
    @Scheduled(fixedRate = 1000)
    public void job1() {
        System.out.println(Thread.currentThread().getId() + " ----- job1 ----- " + System.currentTimeMillis());
    }

我们为前面的三个定时任务都加上 @Async注解再运行看看:

25 ----- job1 ----- 1573121781415
24 ----- job3 ----- 1573121781415
26 ----- job2 ----- 1573121781415
30 ----- job3 ----- 1573121782298
31 ----- job1 ----- 1573121782299
32 ----- job2 ----- 1573121782299
25 ----- job2 ----- 1573121783304
35 ----- job3 ----- 1573121783306
36 ----- job1 ----- 1573121783306

通过输出信息可以看到每个定时任务都在不同的线程上执行,彼此的执行次序和执行时间也互不影响,说明配置为异步执行已经成功。

通过配置实现异步定时任务

现在我们有必要稍稍深入了解下springboot定时任务的执行机制了。

springboot的定时任务主要涉及到两个接口: TaskScheduler和 TaskExecutor。在springboot的默认定时任务实现中,这两个接口的实现类是 ThreadPoolTaskScheduler和 ThreadPoolTaskExecutor。

ThreadPoolTaskScheduler负责实现任务的定时执行机制,而 ThreadPoolTaskExecutor则负责实现任务的异步执行机制。二者中, ThreadPoolTaskScheduler执行栈更偏底层一些。

尽管在职责上有些区别,但是两者在底层上都是依赖java的线程池机制实现的: ThreadPoolTaskScheduler依赖的底层线程池是 ScheduledExecutorService,springboot默认为其提供的coreSize是1,所以默认的定时任务都是在一个线程中执行; ThreadPoolTaskExecutor依赖的底层线程池是 ThreadPoolExecutor,springboot默认为其提供的corePoolSize是8。

说到这里应该清楚了:我们可以不添加 @Async注解,仅通过调整 ThreadPoolTaskScheduler依赖的线程池的coreSize也能实现多线程异步执行;同样的,即使添加了 @Async注解,将 ThreadPoolTaskExecutor依赖的线程池的corePoolSize设置为1,那定时任务还是只能在一个线程上同步执行。看下springboot的相关配置项:

spring:
  task:
    scheduling:
      pool:
        size: 1
    execution:
      pool:
        core-size: 2

其中spring.task.scheduling是 ThreadPoolTaskScheduler的线程池配置项,spring.task.execution是 ThreadPoolExecutor的线程池配置项。

再稍稍扩展下: @Async注解的value属性就是用来指明使用的 TaskExecutor实例的。默认值是空字符串,表示使用的是springboot自启动的 TaskExecutor实例。如有需要,也可以使用自定义的 TaskExecutor实例,如下:

  /**
     * 配置线程池
     * @return
     */
    @Bean(name = "scheduledPoolTaskExecutor")
    public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(20);
        taskExecutor.setMaxPoolSize(200);
        taskExecutor.setQueueCapacity(25);
        taskExecutor.setKeepAliveSeconds(200);
        taskExecutor.setThreadNamePrefix("my-task-executor-");
        // 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //调度器shutdown被调用时等待当前被调度的任务完成
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        //等待时长
        taskExecutor.setAwaitTerminationSeconds(60);
        taskExecutor.initialize();
        return taskExecutor;
    }

此外,还有一种做法是通过提供自定义的 TaskScheduler Bean实例来实现异步执行。要提供提供自定义的 TaskScheduler 实例,可以直接通过 @Bean注解声明创建,也可以在 SchedulingConfigurer接口中配置。这些在后面我们会提到。

调用SpringBoot接口实现定时任务

有时候会需要将定时任务的定时元数据写在数据库或其他配置中心以便统一维护。这种情况就不是通过注解能够搞定的了,此时我们需要使用springboot定时任务一些组件来自行编程实现。常用的组件包括 TaskScheduler、 Triger接口和 SchedulingConfigurer接口。

注意:因为我们用到了springboot的定时任务组件,所以仍然需要在启动类上添加 @EnableScheduling注解。

Trigger接口

Trigger接口主要用来设置定时元数据。要通过程序实现定时任务就不能不用到这个接口。这个接口有两个实现类:

  • PeriodicTrigger用来配置固定时长的定时元数据
  • CronTrigger用来配置cron表达式定时元数据

使用TaskScheduler接口

TaskScheduler接口前面我们提过,这个接口需要配合 Trigger接口一起使用来实现定时任务,看个例子:

 @Autowired
    private TaskScheduler taskScheduler;
 
    public void job() {
        int fixRate = 10;
        taskScheduler.schedule(() -> System.out.println("  job4 ----- " + System.currentTimeMillis()),
                new PeriodicTrigger(fixRate, TimeUnit.SECONDS));
    }

在上面的代码里,我们使用 @Autowired注解获取了springbootr容器里默认的 TaskScheduler实例,然后通过 PeriodicTrigger设置了定时元数据,定时任务的任务体则是一个 Runable接口的实现(在这里只是输出一行信息)。

因为默认的 TaskScheduler实例的线程池coreSize是1,所以如有多个并发任务,这些任务的执行仍然是同步的。要调整为异步可以在配置文件中配置,也可以通过提供一个自定义的 TaskScheduler实例来设置:

 @Bean("taskScheduler")
    public TaskScheduler taskExecutor() {
        ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
        executor.setPoolSize(20);
        executor.setThreadNamePrefix("my-task-scheduler");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //调度器shutdown被调用时等待当前被调度的任务完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //等待时长
        executor.setAwaitTerminationSeconds(60);
        return executor;
    }

使用SchedulingConfigurer接口

SchedulingConfigurer接口的主要用处是注册基于 Trigger接口自定义实现的定时任务。

在实现 SchedulingConfigurer接口后,通常还需要使用 @Configuration注解(当然启动类上的 @EnableScheduling注解也不能少)来声明它实现类。

这个接口唯一的一个方法就是configureTasks,字面意思是配置定时任务。这个方法最重要的参数是一个 ScheduledTaskRegistrar定时任务注册类实例,该类有8个方法,允许我们以不同的方式注册定时任务。

简单做了个实现:

@Configuration
public class MyTaskConfigurer implements SchedulingConfigurer {
 
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        
        taskRegistrar
                .addCronTask(
                        () -> System.out.println(Thread.currentThread().getId() + " --- job5 ----- " + System.currentTimeMillis()),
                        "0/1 * * * * ?"
                );
 
        taskRegistrar
                .addFixedDelayTask(
                        () -> System.out.println(Thread.currentThread().getId() + " --- job6 ----- " + System.currentTimeMillis()),
                        1000
                );
 
        taskRegistrar
                .addFixedRateTask(
                        () -> System.out.println(Thread.currentThread().getId() + " --- job7 ----- " + System.currentTimeMillis()),
                        1000
                );
    }
}

这里我们只使用了三种注册任务的方法,分别尝试注册了fixDelay、fixRate以及cron触发的定时任务。

springboot会自动启动注册的定时任务。看下执行结果:

22 --- job7 ----- 1573613616349
22 --- job6 ----- 1573613616350
22 --- job5 ----- 1573613617001
22 --- job7 ----- 1573613617352
22 --- job6 ----- 1573613617353
22 --- job5 ----- 1573613618065
22 --- job7 ----- 1573613618350
22 --- job6 ----- 1573613618355
22 --- job5 ----- 1573613619002

在执行结果中可以看到这里的任务也是在单一线程同步执行的。要设置为异步执行也简单,因为 SchedulingConfigurer接口的另一个作用就是为定时任务提供自定义的 TaskScheduler实例。来看下:

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setThreadNamePrefix("my-task-scheduler");
        scheduler.setPoolSize(10);
        scheduler.initialize();
        taskRegistrar.setTaskScheduler(scheduler);
    }

在这里,我将之前注册的定时任务去掉了,目的是想验证下这里的配置是否对注解实现的定时任务有效。经检验是可行的。当然对在configureTasks方法中配置的定时任务肯定也是有效的。我就不一一贴结果了。

另外,需要注意:如 SchedulingConfigurer接口实例已经注入,将无法再获取到springboot默认提供的 TaskScheduler接口实例。

通过Quartz实现定时任务

Quartz是一个非常强大的定时任务管理框架。短短的一篇文章未必能介绍清楚Quartz的全部用法。所以这里只是简单地演示下如何在springboot中是如何使用Quartz的。更多的用法建议优先参考Quartz官方文档。

在spring-boot-web 2.0及之后的版本,已经自动集成了quartz,如果不使用spring-boot-web或使用较早的版本的话我们还需要加一些依赖:

 <!-- quartz -->
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
    </dependency>
    <!-- spring集成quartz -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
    </dependency>
    <!-- SchedulerFactoryBean依赖了tx包中的PlatformTransactionManager类,因为quartz的分布式功能是基于数据库完成的 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
    </dependency>

添加完成这些依赖后,springboot服务在启动时也会自启动内部的quartz。事实上springboot已经为我们准备好了几乎全部的quartz的配置。我们要做的只是把自定义的任务填进去。

首先我们需要创建一个Job实例,来实现Job的具体行为。

@Component
public class MyQuartzJob extends QuartzJobBean {
 
    @Override
    protected void executeInternal(JobExecutionContext context) {
        JobDataMap map = context.getMergedJobDataMap();
        // 从作业上下文中取出Key
        String key = map.getString("key");
        System.out.println(Thread.currentThread().getId() + " -- job8 ---------------------->>>>" + key);
    }
 
}

QuartzJobBean是Spring提供的Quartz Job抽象类。在实现这个类的时候我们可以获取注入到spring中的其他Bean。

配置Job

@Configuration
public class QuartzConfig implements InitializingBean {
 
    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;
 
 
    @Override
    public void afterPropertiesSet() throws Exception {
        config();
    }
 
 
    private void config() throws SchedulerException {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
 
        JobDetail jobDetail = buildJobDetail();
        Trigger trigger = buildJobTrigger(jobDetail);
        scheduler.scheduleJob(jobDetail, trigger);
    }
 
 
    private JobDetail buildJobDetail() {
        // 用来存储交互信息
        JobDataMap dataMap = new JobDataMap();
        dataMap.put("key", "zhyea.com");
 
        return JobBuilder.newJob(MyQuartzJob.class)
                .withIdentity(UUID.randomUUID().toString(), "chobit-job")
                .usingJobData(dataMap)
                .build();
    }
 
 
    private Trigger buildJobTrigger(JobDetail jobDetail) {
        return TriggerBuilder.newTrigger()
                .forJob(jobDetail)
                .withIdentity(jobDetail.getKey().getName(), "chobit-trigger")
                .withSchedule(CronScheduleBuilder.cronSchedule("0/1 * * * * ?"))
                .build();
    }
}

在创建 QuartzConfig类的时候实现了 InitializingBean接口,目的是在 QuartzConfig实例及依赖类都完成注入后可以立即执行配置组装操作。

这里面有几个关键接口需要说明下:

  • SchedulerFactoryBean,Quartz Scheduler工厂类,springboot自动化配置实现;
  • Scheduer,负责Quartz Job调度,可从工厂类实例获取;
  • JobDetail,执行Quartz Job封装;
  • Trigger,完成Quartz Job启动。

还可以在配置文件中添加Quartz的配置:

spring:
  quartz:
    startupDelay: 180000 #这里是毫秒值

这里配置了让Quartz默认延迟启动3分钟。

看下执行结果:

30 -- job8 ---------------------->>>>zhyea.com
31 -- job8 ---------------------->>>>zhyea.com
32 -- job8 ---------------------->>>>zhyea.com
33 -- job8 ---------------------->>>>zhyea.com
34 -- job8 ---------------------->>>>zhyea.com
...

好了,就这些内容了。前面用到的程序都上传到了GITHUB,有需要可以参考下。

参考文档

Spring Task Execution and Scheduling
Scheduling Tasks
SpringBoot Quartz Scheduler
Spring Boot Quartz Scheduler Example: Building an Email Scheduling app
Quartz Scheduler Tutorials

以上就是springboot如何配置定时任务的详细内容,更多关于springboot配置定时任务的资料请关注我们其它相关文章!

(0)

相关推荐

  • SpringBoot下RabbitMq实现定时任务

    本文实例为大家分享了SpringBoot下RabbitMq实现定时任务,供大家参考,具体内容如下 定时任务场景:订单下单15分钟未付款自动关闭 延迟任务实现原理图如下: 根据上图看出我们需要两个队列(一是死信队列,消息在里面度过TLL时间,二是处理队列,消息度过TLL时间后进入该队列),两个交换机和路由(一是用来将消息送入死信队列,二是将消息从死信队列送到处理队列),但是交换机其实可以用同一个,也就是一个交换机搭配两个路由的方式. 以下为代码实现过程: //首先rabbitAdmin的配置 @B

  • 一篇文章教你使用SpringBoot如何实现定时任务

    前言 在 Spring + SpringMVC 环境中,一般来说,要实现定时任务,我们有两中方案,一种是使用 Spring 自带的定时任务处理器 @Scheduled 注解,另一种就是使用第三方框架 Quartz ,Spring Boot 源自 Spring+SpringMVC ,因此天然具备这两个 Spring 中的定时任务实现策略,当然也支持 Quartz,本文我们就来看下 Spring Boot 中两种定时任务的实现方式. 一.第一种方式:@Scheduled 使用 @Scheduled

  • Springboot定时任务Scheduled重复执行操作

    今天用scheduled写定时任务的时候发现定时任务一秒重复执行一次,而我的cron表达式为 * 0/2 * * * * . 在源码调试的过程中,发现是我的定时任务执行过程太短导致的. 于是我另外写了个简单的定时任务 @Component public class TestJob { @Scheduled(cron = "* 0/2 * * * *") public void test() { System.out.println("测试开始"); System.o

  • springboot 定时任务@Scheduled实现解析

    这篇文章主要介绍了springboot 定时任务@Scheduled实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.pom.xml中导入必要的依赖: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version&g

  • springBoot 创建定时任务过程详解

    前言 好几天没写了,工作有点忙,最近工作刚好做一个定时任务统计的,所以就将springboot 如何创建定时任务整理了一下. 总的来说,springboot创建定时任务是非常简单的,不用像spring 或者springmvc 需要在xml 文件中配置,在项目启动的时候加载.spring boot 使用注解的方式就可以完全支持定时任务. 不过基础注解的话,可能有的需求定时任务的时间会经常变动,注解就不好修改,每次都得重新编译,所以想将定时时间存在数据库,然后项目读取数据库执行定时任务,所以就有了基

  • SpringBoot基于数据库的定时任务统一管理的实现

    定时任务1 import lombok.extern.slf4j.Slf4j; /** * @author Created by niugang on 2019/12/24/15:29 */ @Slf4j public class TaskTest { public void task1() { log.info("反射调用测试[一]类"); } } 定时任务2 import lombok.extern.slf4j.Slf4j; /** * @author Created by niu

  • 浅谈springboot项目中定时任务如何优雅退出

    在一个springboot项目中需要跑定时任务处理批数据时,突然有个Kill命令或者一个Ctrl+C的命令,此时我们需要当批数据处理完毕后才允许定时任务关闭,也就是当定时任务结束时才允许Kill命令生效. 启动类 启动类上我们获取到相应的上下文,捕捉相应命令.在这里插入代码片 @SpringBootApplication /**指定mapper对应包的路径*/ @MapperScan("com.youlanw.kz.dao") /**开启计划任务*/ @EnableScheduling

  • SpringBoot整合SpringTask实现定时任务

    半藏商城中会有一些用户提交了订单但是一直没有支付的情况,之前我是通过quartz定时任务每天的5点扫描未支付订单然后读取用户的邮箱地址发送邮件提醒用户尽快支付.这次我是采用Spring中自带的SpringTask来进行定时任务. Cron表达式 Cron表达式是一个字符串,包括6~7个时间元素,在SpringTask中可以用于指定任务的执行时间. Cron的语法格式 Seconds Minutes Hours DayofMonth Month DayofWeek Cron格式中每个时间元素的说明

  • SpringBoot中使用Quartz管理定时任务的方法

    定时任务在系统中用到的地方很多,例如每晚凌晨的数据备份,每小时获取第三方平台的 Token 信息等等,之前我们都是在项目中规定这个定时任务什么时候启动,到时间了便会自己启动,那么我们想要停止这个定时任务的时候,就需要去改动代码,还得启停服务器,这是非常不友好的事情 直至遇见 Quartz,利用图形界面可视化管理定时任务,使得我们对定时任务的管理更加方便,快捷 一.Quartz 简介 Quartz是一个开源的作业调度框架,它完全由Java写成,并设计用于J2SE和J2EE应用中.它提供了巨大的灵

  • SpringBoot中使用@Scheduled注解创建定时任务的实现

    在项目日常开发过程中,经常需要定时任务来帮我们做一些工作,如清理日志.定时任务的实现方法主要有 Timer.Quartz 以及 elastic-job Timer 实现定时任务 只执行一次的定时任务 Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("2000毫米后执行一次."); } }, 2000); timer.s

  • springboot schedule 解决定时任务不执行的问题

    @schedule 注解 是springboot 常用的定时任务注解,使用起来简单方便,但是如果定时任务非常多,或者有的任务很耗时,会影响到其他定时任务的执行,因为schedule 默认是单线程的,一个任务在执行时,其他任务是不能执行的.解决办法是重新配置schedule,改为多线程执行.只需要增加下面的配置类就可以了. import org.springframework.boot.autoconfigure.batch.BatchProperties; import org.springfr

随机推荐