每一年都奔走在自己热爱里 没有人是一座孤岛,总有谁爱着你

岁
万
月
12

SpringBoot整合定时任务

项目地址:https://gitee.com/libeimaningg/beizhilib

基于注解的定时任务


1、前提配置

(1)在主启动类上添加@EnableScheduling注解,开启定时任务。无需引入任何jar,springboot自带定时。
(2)在需要定时执行的方法上添加@Scheduled注解,如@Scheduled(cron ="*/6 * * * * ?")

2、Cron 表达式详解

Cron 表达式是用于定义定时任务执行时间的字符串,广泛应用于 Spring 的 @Scheduled、Quartz 等定时任务框架。其核心是通过 ‌时间字段‌ 和 ‌通配符‌ 组合实现复杂的调度规则。
(1)表达式格式‌
Cron 表达式由 ‌6或7个字段‌ 组成,分别表示不同时间单位(Spring 中通常用 ‌6位‌ 格式)。格式如下:

字段 允许值 特殊字符 说明
秒(Seconds) 0-59 , - * / 可精确到秒级调度
分(Minutes) 0-59 , - * /
小时(Hours 0-23 , - * /
日(Day) 1-31 , - * / ? L W 月份中的某一天
月(Month) 1-12 或 JAN-DEC , - * /
周(Week) 0-7 或 SUN-SAT (0=周日) , - * / ? L # 周几(1=MON, 7=SUN)
年(Year) 1970-2099 (可选) , - * / Spring 中通常省略

(2)特殊字符解析

符号 作用 示例
* 匹配任意值 0 * * * * ? 每分钟的0秒执行
? 仅在 ‌日‌ 或 ‌周‌ 字段使用,表示“无意义” 0 0 0 * * ? 每天0点执行
- 范围区间 0 0 10-12 * * ? 10-12点每小时执行
, 多个值 0 0 2,14 * * ? 每天2点和14点执行
/ 步长(间隔时间) 0 0/5 * * * ? 每隔5分钟执行
L 最后一天(仅 ‌日‌ 或 ‌周‌ 字段) 0 0 L * * ? 每月最后一天0点执行
W 最近工作日(仅 ‌日‌ 字段) 0 0 0 15W * ? 每月15日最近的工作日执行
# 指定月份的周几(仅 ‌周‌ 字段) 0 0 0 ? * 6#3 每月第3个周五执行

(3)常用示例

表达式 说明
0 0 12 * * ? 每天中午12点执行
0 0/5 14 * * ? 每天下午2点开始,每隔5分钟执行一次
0 15 10 ? * MON-FRI 每周一至周五上午10:15执行
0 0 0 1 1 ? 2024 2024年1月1日0点执行(需7位表达式)
0 0 8-18/2 ? * MON 每周一上午8点到下午6点,每隔2小时执行一次
0 0 0 L * ? 每月最后一天的0点执行
0 0 0 15W * ? 每月15日最近的工作日执行
0 0 0 ? * 6#3 每月第3个周五0点执行

3、注意事项

(1)在多模块项目中,需要在启动类所在模块的pom.xml文件添加定时任务模块的依赖,如:

        <dependency>
            <groupId>org.yaoyao</groupId>
            <artifactId>yaoyao-quartz</artifactId>
        </dependency>

image
然后在@SpringBootApplication注解中扩大扫描范围,如:

@SpringBootApplication(scanBasePackages = {"org.yaoyao"})

4、代码示例

(1)启动类

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.yaoyao.yaoyaobiz.service.SysUserService;
import org.yaoyao.yaoyaocommon.core.domain.dto.UserDto;
import org.yaoyao.yaoyaocommon.utils.ContextUtil;

@ComponentScan("org.yaoyao")
@SpringBootApplication(scanBasePackages = {"org.yaoyao"})
@MapperScan("org.yaoyao.yaoyaobiz.mapper")
@EnableAspectJAutoProxy
@EnableAsync
@EnableRetry
@EnableScheduling
//@MapperScan(basePackages = { "org.yaoyao.yaoyaobiz.mapper" }, sqlSessionFactoryRef = "sqlSessionFactory")
public class YaoyaoWebApplication {

    public static void main(String[] args) {
        System.out.println("yaoyao启动成功YaoyaoWebApplication start");
        SpringApplication.run(YaoyaoWebApplication.class, args);
    }
}

(2)定时任务

package org.yaoyao.yaoyaoquartz.job;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.yaoyao.yaoyaobiz.service.SysUserService;
import org.yaoyao.yaoyaocommon.core.domain.dto.UserDto;
import org.yaoyao.yaoyaocommon.utils.ContextUtil;

/**
 * 定时任务测试类
 *
 * @author Bryson
 * @date 2025/3/13
 */
@Component
@Slf4j
public class QuartzJob {


    //通过在定时任务方法上添加@Async注解,可以使定时任务在新的线程中执行,避免任务之间的相互影响
    //每6s执行一次
    @Async
    @Scheduled(cron ="*/6 * * * * ?")
    public void execute() {
        log.info("定时任务启动QuartzJob execute");
        UserDto userDto = new UserDto();
        userDto.setPageIndex(1);
        userDto.setPageSize(5);
        ContextUtil.getBean(SysUserService.class).selectPage(userDto);
    }
}

异步执行:默认情况下,所有定时任务共享单一线程池。若任务执行时间较长,可能导致任务阻塞。
解决方案:使用@Async实现异步执行。

配置线程池:

# application.yml
spring:
  task:
    scheduling:
      pool:
        size: 10 # 定时任务线程池大小
    execution:
      pool:
        core-size: 10 # 异步任务线程池核心线程数

5、@Scheduled的核心配置

@Scheduled除过cron还有三种方式:fixedRate,fixedDelay,initialDelay
(1)fixedDelay:控制方法执行的间隔时间,是以上一次方法执行完开始算起,如上一次方法执行阻塞住了,那么直到上一次执行完,并间隔给定的时间后,执行下一次。

//每3秒执行一次
    @Scheduled(fixedDelay = 3000)
    private void myTasks() {
        System.out.println("I do myself per third seconds");
    }

(2)fixedRate:是按照一定的速率执行,是从上一次方法执行开始的时间算起,如果上一次方法阻塞住了,下一次也是不会执行,但是在阻塞这段时间内累计应该执行的次数,当不再阻塞时,一下子把这些全部执行掉,而后再按照固定速率继续执行。

//每10秒执行一次
    @Scheduled(fixedRate = 10000)
    private void myTasks2() {
        System.out.println("我是一个定时任务");
    }

(3)initialDelay:initialDelay = 10000 表示在容器启动后,延迟10秒后再执行一次定时器。

//容器启动后,延迟10秒后再执行一次定时器,以后每10秒再执行一次该定时器。
    @Scheduled(initialDelay = 10000, fixedRate = 10000)
    private void myTasks3() {
        System.out.println("我是一个定时任务3");
    }

6、结合Redisson的高级用法

在集群部署中,使用成熟的分布式锁框架 Redisson避免定时任务重复执行,它内置看门狗机制(自动续期)和可重入锁。
(1)添加依赖

            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.22.0</version>
            </dependency>

(2)配置 RedissonClient
RedissonClient 的配置通过 Config 类进行:

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class RedissonConfig {
    public static RedissonClient createClient() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379")
                .setPassword("123456");
        return Redisson.create(config);
    }
}

(3)定时任务

package org.yaoyao.yaoyaoquartz.job;

import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.yaoyao.yaoyaobiz.service.SysUserService;
import org.yaoyao.yaoyaocommon.config.RedissonConfig;
import org.yaoyao.yaoyaocommon.core.domain.dto.UserDto;
import org.yaoyao.yaoyaocommon.utils.ContextUtil;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 定时任务测试类
 *
 * @author Bryson
 * @date 2025/3/13
 */
@Component
@Slf4j
public class QuartzJob {

    RedissonClient redisson = RedissonConfig.createClient();

    //在集群部署中,使用成熟的分布式锁框架 Redisson避免定时任务重复执行,它内置看门狗机制(自动续期)和可重入锁。
    @Scheduled(cron = "*/6 * * * * ?")
    public void executeTask() {
        RLock lock = redisson.getLock("TASK_LOCK");
        try {
            // 尝试加锁,最多等待0秒,锁超时30秒(看门狗会自动续期)
            if (lock.tryLock(0, 30, TimeUnit.SECONDS)) {
                System.out.println("执行定时任务: " + new Date());
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

posted @ 2025-04-03 15:11  果咩那塞  阅读(5)  评论(0)    收藏  举报