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>

然后在@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();
}
}
}
}

浙公网安备 33010602011771号