写点什么

SpringBoot 中实现定时任务的几种方式

  • 2023-05-24
    湖南
  • 本文字数:3041 字

    阅读完需:约 10 分钟

定时任务在我们项目开发中也是很重要的,对于某些场景必须要用定时任务,如定时发送邮件啊,定时统计数据等,这篇文章主要讲讲项目中实现定时任务的几种方式。

一、基于注解

这种方式很简单,主要就是先@EnableScheduling开启定时任务功能,然后在相应的方法上添加@Scheduled()中间写上相应的cron表达式即可。示例如下:


schedule.ScheduleTask:

import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component@EnableScheduling //开启定时任务public class ScheduleTask { @Scheduled(cron = "0/5 * * * * ?") //定时任务注解+cron表达式 public void testScheduleTask() { System.out.println("执行定时任务" + LocalDateTime.now()); }}
复制代码

Cron 表达式参数参考:

  • 秒(0~59) 例如 0/5 表示每 5 秒

  • 分(0~59)

  • 时(0~23)

  • 日(0~31)的某天,需计算

  • 月(0~11)

  • 周几( 可填 1-7 或 SUN/MON/TUE/WED/THU/FRI/SAT)


建议:直接在线生成 Cron 表达式比较方便:www.matools.com/cron/

@Scheduled:除了支持灵活的参数表达式cron之外,还支持 fixedDelayfixedRateinitialDelay 这些延时性的操作。

启动测试就实现了基本的定时任务功能,但是如果我们修改了cron表达式,需要重启整个应用才能生效,不是很方便。想要实现修改cron表达式就生效就需要用到接口的方式来实现定时任务。

二、基于接口

接口的方式是我们把定时任务的信息放在数据库中,程序从数据库去拉取定时任务的信息如cron表达式来实现实时修改生效等功能。


1. 首先在数据库中创建一张用来记录定时任务的表

CREATE TABLE `scheduled` (  `id` bigint NOT NULL AUTO_INCREMENT,  `name` varchar(255) NULL,  `cron` varchar(255) NULL,  PRIMARY KEY (`id`))INSERT INTO `mydb`.`scheduled` (`id`, `name`, `cron`) VALUES (1, '定时任务1', '0/6 * * * * ?')
复制代码

2. 在项目中引入mabatis-plusmysql相应的依赖包

<dependency>  <groupId>com.baomidou</groupId>  <artifactId>mybatis-plus-boot-starter</artifactId>  <version>3.5.3.1</version></dependency><dependency>  <groupId>mysql</groupId>  <artifactId>mysql-connector-java</artifactId>  <version>8.0.32</version></dependency>
复制代码

3. 在application.yml中进行连接数据库相应配置

spring:  datasource:    url: jdbc:mysql://localhost:3306/mydb?characterEncoding=utf-8&serverTimeZone=UTC    username: root    password: 123456    driver-class-name: com.mysql.cj.jdbc.Driver
复制代码

4. 定义查询cron表达式的mapper


mapper.CronMapper:

import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Select;
@Mapperpublic interface CronMapper { @Select("select cron from scheduled where id=#{id}") String getCron(Long id);}
复制代码

5. 实现定时任务

import com.jk.mapper.CronMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.scheduling.annotation.SchedulingConfigurer;import org.springframework.scheduling.config.ScheduledTaskRegistrar;import org.springframework.scheduling.support.CronTrigger;import org.springframework.stereotype.Component;
@Component@EnableScheduling //开启定时任务public class ScheduleTask implements SchedulingConfigurer { @Autowired private CronMapper cronMapper;
@Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addTriggerTask( //添加任务内容 () -> process(), //设置执行的周期 triggerContext -> { //查询cron表达式 String cron = cronMapper.getCron(1L); if (cron.isEmpty()) { System.out.println("cron is null"); } return new CronTrigger(cron).nextExecutionTime(triggerContext); }); }
private void process() { System.out.println("基于接口的定时任务"); }}
复制代码

这种方式需要去实现SchedulingConfigurer接口并重写configureTasks方法,然后设置任务内容和执行周期等,启动测试就实现了基于接口的定时任务,此时我们改动数据库里的cron表达式也会实时生效

三、多线程定时任务

但上面的方法定义的定时任务会有个问题,就是如果我一个定时任务里面执行了复杂逻辑,导致本身执行花的时间就已经超过了定时任务间隔的时间怎么办呢?这时候定时任务的执行就会出现一定的问题,具体如下,我用线程睡眠的方式模拟处理复杂逻辑。

import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component@EnableScheduling //开启定时任务public class ScheduleTask { @Scheduled(cron = "0/5 * * * * ?") //定时任务注解+cron表达式 public void testScheduleTask1() throws InterruptedException { System.out.println("执行定时任务1 " + LocalDateTime.now()); Thread.sleep(10 * 1000); }
@Scheduled(cron = "0/5 * * * * ?") //定时任务注解+cron表达式 public void testScheduleTask2() { System.out.println("执行定时任务2 " + LocalDateTime.now()); }}
复制代码


可以看到两个任务的执行时间都被影响了,和我们设置的 5 秒不对应。此时就可以使用多线程定时任务,对多线程的使用不了解的可以看我的另一篇文章 SpringBoot 中异步任务实现及自定义线程池执行异步任务

import org.springframework.scheduling.annotation.Async;import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.scheduling.annotation.Scheduled;import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Component@EnableScheduling //开启定时任务@EnableAsync //开启多线程public class ScheduleTask { @Scheduled(cron = "0/5 * * * * ?") //定时任务注解+cron表达式 @Async public void testScheduleTask1() throws InterruptedException { System.out.println("执行定时任务1 " + LocalDateTime.now()); Thread.sleep(10 * 1000); }
@Scheduled(cron = "0/5 * * * * ?") //定时任务注解+cron表达式 @Async public void testScheduleTask2() { System.out.println("执行定时任务2 " + LocalDateTime.now()); }}
复制代码


这样多线程的定时任务就实现了,每个定时任务之间不会互相影响,定时任务执行时间太长也不会影响。


作者:JK 凯

链接:https://juejin.cn/post/7236217091966402618

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
SpringBoot 中实现定时任务的几种方式_Java_做梦都在改BUG_InfoQ写作社区