SpringBoot中的定时任务

一、基于注解(@Scheduled)

  • 基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。

1. 创建定时器

  • 使用SpringBoot基于注解来创建定时任务非常简单,只需几行代码便可完成。 代码如下:
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.EnableScheduling;
    import org.springframework.scheduling.annotation.Scheduled;
    
    import java.time.LocalDateTime;
    
    /**
     * @author zhaokuii11@163.com
     * @create 2022-01-11 14:53
     * @Description
     */
    @Configuration    // 1. 代表当前类是一个配置类
    @EnableScheduling // 2.开启定时任务
    public class StaticScheduleTask {
        //3.添加定时任务
        @Scheduled(cron = "0/5 * * * * ?")
        //或直接指定时间间隔,例如:5秒
        //@Scheduled(fixedRate=5000)
        private void configureTask() {
            System.out.println("执行静态定时任务时间" + LocalDateTime.now());
        }
    }
    
    image

1.2 Cron表达式参数分别表示:

  1. 秒(0~59) 例如0/5表示每5秒
  2. 分(0~59)
  3. 时(0~23)
  4. 日(0~31)的某天,需计算
  5. 月(0~11)
  6. 周几( 可填1-7 或 SUN/MON/TUE/WED/THU/FRI/SAT)
  • @Scheduled:除了支持灵活的参数表达式cron之外,还支持简单的延时操作,例如 fixedDelay ,fixedRate 填写相应的毫秒数即可。
    # Cron表达式范例:
    每隔5秒执行一次:*/5 * * * * ?
    每隔1分钟执行一次:0 */1 * * * ?
    每天23点执行一次:0 0 23 * * ?
    每天凌晨1点执行一次:0 0 1 * * ?
    每月1号凌晨1点执行一次:0 0 1 1 * ?
    每月最后一天23点执行一次:0 0 23 L * ?
    每周星期天凌晨1点实行一次:0 0 1 ? * L
    在26分、29分、33分执行一次:0 26,29,33 * * * ?
    每天的0点、13点、18点、21点都执行一次:0 0 0,13,18,21 * * ?
    
  • 更详细的 cron参数
    https://blog.csdn.net/Qiwan2/article/details/89848298
    https://blog.csdn.net/weixin_30720461/article/details/91388486

二、基于接口(SchedulingConfigurer)

  • 从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就派上用场了。
  • 基于接口(SchedulingConfigurer)

2.1 依赖

    <!--web启动器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--添加MySql依赖 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.45</version>
        <scope>runtime</scope>
    </dependency>
    <!--jdbc-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <!--添加Mybatis依赖 配置mybatis的一些初始化的东西-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.0</version>
    </dependency>

2.2 创建数据库

  • 开启本地数据库mysql,随便打开查询窗口,然后执行脚本内容,如下:
DROP DATABASE IF EXISTS `socks`;
CREATE DATABASE `socks`;
USE `SOCKS`;
DROP TABLE IF EXISTS `cron`;
CREATE TABLE `cron`  (
  `cron_id` varchar(30) NOT NULL PRIMARY KEY,
  `cron` varchar(30) NOT NULL  
);
INSERT INTO `cron` VALUES ('1', '0/5 * * * * ?');
# 连接参数配置
spring.datasource.url=jdbc:mysql://localhost:3306/socks
spring.datasource.password=root
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

2.3 创建定时器

  • 数据库准备好数据之后,我们编写定时任务,注意这里添加的是TriggerTask,目的是循环读取我们在数据库设置好的执行周期,以及执行相关定时任务的内容。
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.context.annotation.Configuration;
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.util.StringUtils;

import javax.annotation.Resource;
import java.time.LocalDateTime;

/**
 * @author zhaokuii11@163.com
 * @create 2022-01-11 15:10
 * @Description
 */
@Configuration    //1. 主要用于标记配置类,兼备 Component的效果
@EnableScheduling //2. 开启定时任务
public class DynamicScheduleTask implements SchedulingConfigurer {
    @Mapper
    public interface CronMapper {
        @Select("select cron from cron limit 1")
        public String getCron();
    }

    @Resource
    CronMapper cronMapper;

    /**
     * 执行定时任务
     *
     * @param scheduledTaskRegistrar
     */
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.addTriggerTask(
                //1. 添加任务内容(Runnable)
                () -> System.out.println("动态定时任务:" + LocalDateTime.now().toLocalTime()),
                //2. 设置执行周期(Trigger)
                triggerContext -> {
                    //2.1 从数据库获取执行周期
                    String cron = cronMapper.getCron();
                    //2.2 合法性校验
                    if (StringUtils.isEmpty(cron)) {
                        // Omitted Code ...
                    }
                    //2.3 返回执行周期(Date)
                    return new CronTrigger(cron).nextExecutionTime(triggerContext);
                });
    }
}

2.4 启动测试

  • 启动应用后,查看控制台,打印时间是我们预期的每5秒一次:
  • 注意: 如果在数据库修改时格式出现错误,则定时任务会停止,即重新修改正确;此时只能重新启动项目才能恢复。
    image

三、基于注解设定多线程定时任务

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;

/**
 * @author zhaokuii11@163.com
 * @create 2022-01-11 18:55
 * @Description
 */
@Component //@Component注解用于对那些比较中立的类进行注释
@EnableScheduling //1. 开启定时任务
@EnableAsync //2. 开启多线程
public class MultithreadScheduleTask {

    @Async//3. @Async就可以定义一个线程任务
    @Scheduled(fixedDelay = 1000)
    public void first() throws InterruptedException {
        System.out.println("第一个定时任务开始" + LocalDateTime.now().toLocalTime());
        Thread.sleep(1000 * 10);
    }

    @Async
    @Scheduled(fixedDelay = 1000)
    public void second() throws InterruptedException {
        System.out.println("第二个定时任务开始" + LocalDateTime.now().toLocalTime());
        System.out.println();
    }

}

image

  • 从控制台可以看出,第一个定时任务和第二个定时任务互不影响;
    并且,由于开启了多线程,第一个任务的执行时间也不受其本身执行时间的限制,所以需要注意可能会出现重复操作导致数据异常。

参考

原文地址:https://www.cnblogs.com/zk2020/p/15790212.html