Spring Boot 定时任务与异步处理
当你的Spring Boot项目核心功能(如接口开发、文件操作、Excel导入导出)已经完善后,自动化任务便成为提升项目实用性、降低人工成本的关键一步。日常开发中,定时清理冗余数据、凌晨自动生成报表、异步发送邮件/短信、定时同步数据等场景,都离不开定时任务与异步处理的支持。本文承接项目完善后的需求,手把手实战Spring Boot中定时任务(@Scheduled)与异步处理(@Async)的核心用法,结合定时清理、异步发送邮件等真实案例,代码可直接复制复用,帮你快速实现项目自动化,进一步提升项目专业度和实用性。
一、前置准备:环境搭建与核心配置
定时任务与异步处理均为Spring Boot原生支持的功能,无需引入额外依赖(仅异步发送邮件需添加邮件依赖),核心是通过注解开启功能、配置相关参数,适配项目自动化需求。
1.1 Maven 核心依赖
基础定时任务与异步处理无需额外依赖,仅当需要实现“异步发送邮件”案例时,添加邮件依赖即可,轻量无冗余,贴合项目完善后的扩展需求:
<!-- Spring Boot Web 核心依赖(确保项目基础运行) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 邮件依赖(仅用于“异步发送邮件”案例,可选) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- lombok 简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 可选:数据库依赖(用于定时清理数据库数据案例) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
1.2 核心配置(application.yml)
配置定时任务线程池、异步处理线程池,以及邮件相关配置(用于案例),集中管理参数,避免默认配置导致的性能问题,适配项目生产环境需求:
spring:
# 邮件配置(用于异步发送邮件案例,替换为自己的邮箱信息)
mail:
host: smtp.163.com # 邮箱SMTP服务器(163邮箱示例,QQ邮箱为smtp.qq.com)
username: your-email@163.com # 发送者邮箱
password: your-email-password # 邮箱授权码(不是登录密码,需在邮箱设置中开启)
port: 465 # 端口(163邮箱465,QQ邮箱587)
protocol: smtps # 协议,163邮箱用smtps,QQ邮箱用smtp
properties:
mail:
smtp:
ssl:
enable: true # 开启SSL加密
# 定时任务配置(自定义线程池,避免单线程阻塞)
spring:
task:
scheduling:
pool:
size: 5 # 定时任务线程池大小,根据定时任务数量调整
thread-name-prefix: scheduled-task- # 线程名前缀,便于日志排查
shutdown:
await-termination: true # 关闭时等待任务完成
await-termination-period: 3000 # 等待时间(毫秒)
# 异步处理配置(自定义线程池,提升异步任务效率)
execution:
pool:
core-size: 5 # 核心线程数
max-size: 10 # 最大线程数
queue-capacity: 20 # 队列容量
keep-alive: 60 # 空闲线程存活时间(秒)
thread-name-prefix: async-task- # 异步线程名前缀
# 数据库配置(用于定时清理案例,替换为自己的数据库信息)
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot_task?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
username: root
password: 123456
1.3 开启定时与异步功能(核心注解)
在Spring Boot启动类上添加两个核心注解,开启定时任务与异步处理功能,一行代码即可完成开启,简洁高效:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 启动类:开启定时任务(@EnableScheduling)和异步处理(@EnableAsync)
*/
@SpringBootApplication
@EnableScheduling // 开启定时任务功能
@EnableAsync // 开启异步处理功能
public class SpringBootTaskApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootTaskApplication.class, args);
}
}
关键注意点:1. 两个注解必须添加在启动类上(或配置类上),否则定时任务和异步处理无法生效;2. 自定义线程池不是必须的,但推荐配置,避免默认单线程导致任务阻塞(如多个定时任务同时执行,单线程会排队等待)。
二、核心实战一:@Scheduled 实现定时任务(自动化核心)
@Scheduled是Spring Boot原生定时任务注解,无需复杂配置,仅需在方法上添加注解,指定执行时间,即可实现定时执行。支持多种时间表达式(cron表达式、固定延迟、固定速率),适配不同定时场景。
2.1 @Scheduled 核心用法(3种常用方式)
创建定时任务类,通过不同注解参数,实现3种常用定时场景,覆盖大多数项目需求:
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 定时任务类(需添加@Component,交给Spring管理)
*/
@Slf4j
@Component
public class ScheduledTask {
/**
* 方式1:固定延迟(fixedDelay)
* 说明:以上一次任务执行完成的时间为基准,延迟指定时间后执行下一次
* 示例:延迟5秒执行,若上一次任务执行了3秒,那么两次任务间隔为5秒(3+5)
*/
@Scheduled(fixedDelay = 5000) // 单位:毫秒
public void fixedDelayTask() {
log.info("固定延迟定时任务执行:{}", System.currentTimeMillis());
// 模拟任务执行耗时(1秒)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 方式2:固定速率(fixedRate)
* 说明:以上一次任务开始执行的时间为基准,每隔指定时间执行一次
* 示例:每隔5秒执行,若上一次任务执行了3秒,那么下一次任务会在5秒时准时执行(无需等待上一次完成)
*/
@Scheduled(fixedRate = 5000)
public void fixedRateTask() {
log.info("固定速率定时任务执行:{}", System.currentTimeMillis());
}
/**
* 方式3:Cron表达式(最灵活,适配复杂定时场景)
* 说明:通过Cron表达式指定具体执行时间,支持按秒、分、时、日、月、周、年配置
* 示例:每天凌晨00:00:00执行(常用场景:定时清理、报表生成)
* Cron表达式语法:秒 分 时 日 月 周 年(年可选)
*/
@Scheduled(cron = "0 0 0 * * ?") // 每天0点执行
public void cronTask() {
log.info("Cron表达式定时任务执行:{}", System.currentTimeMillis());
}
}
2.2 常用Cron表达式(直接复制使用)
Cron表达式是定时任务的核心,整理项目中最常用的表达式,无需手动编写,直接复制适配场景:
| 定时场景 | Cron表达式 |
|---|---|
| 每天凌晨0点执行 | 0 0 0 * * ? |
| 每天凌晨2点执行 | 0 0 2 * * ? |
| 每小时执行一次 | 0 0 * * * ? |
| 每分钟执行一次 | 0 * * * * ? |
| 每周一凌晨0点执行 | 0 0 0 ? * MON |
| 每月1号凌晨0点执行 | 0 0 0 1 * ? |
2.3 真实案例:定时清理冗余数据
项目运行一段时间后,会产生大量冗余数据(如过期日志、废弃文件记录、无效用户),手动清理繁琐且易遗漏,通过定时任务自动清理,提升项目性能。结合MyBatis-Plus实现数据库冗余数据清理:
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.demo.entity.SysLog;
import com.example.demo.service.SysLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
/**
* 定时清理任务(清理30天前的系统日志)
*/
@Slf4j
@Component
public class DataCleanScheduledTask {
@Autowired
private SysLogService sysLogService;
/**
* 每天凌晨0点30分,清理30天前的系统日志
* Cron表达式:0 30 0 * * ?
*/
@Scheduled(cron = "0 30 0 * * ?")
public void cleanExpiredLog() {
log.info("开始执行定时清理任务:清理30天前的系统日志");
// 1. 计算30天前的时间
LocalDateTime thirtyDaysAgo = LocalDateTime.now().minus(30, ChronoUnit.DAYS);
// 2. 构建查询条件:创建时间小于30天前
QueryWrapper<SysLog> queryWrapper = new QueryWrapper<>();
queryWrapper.lt("create_time", thirtyDaysAgo);
// 3. 批量删除冗余日志
int deleteCount = sysLogService.remove(queryWrapper);
log.info("定时清理任务执行完成,共清理冗余日志{}条", deleteCount);
}
}
说明:SysLog为系统日志实体类,sys_log为数据库表,包含create_time(创建时间)字段,通过定时任务批量删除30天前的日志,可根据项目需求调整清理时间和清理规则(如清理无效用户、废弃文件记录等)。
三、核心实战二:@Async 实现异步处理(提升性能)
@Async是Spring Boot原生异步注解,用于实现“异步调用”——当执行某个耗时操作(如发送邮件、调用第三方接口、生成大文件)时,无需等待该操作完成,继续执行后续代码,提升接口响应速度和系统吞吐量,避免用户长时间等待。
核心原理:通过线程池异步执行标注@Async的方法,主线程无需阻塞等待,实现“并行处理”,尤其适合项目中耗时且非核心流程的操作。
3.1 @Async 核心用法(基础示例)
创建异步任务类,在需要异步执行的方法上添加@Async注解,即可实现异步处理,无需额外配置:
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 异步任务类(需添加@Component,交给Spring管理)
*/
@Slf4j
@Component
public class AsyncTask {
/**
* 异步方法:标注@Async,由Spring异步线程池执行
* 说明:异步方法不能与调用方法在同一个类中(否则异步失效)
*/
@Async
public void asyncMethod() {
log.info("异步方法开始执行,线程名:{}", Thread.currentThread().getName());
// 模拟耗时操作(如发送邮件、调用第三方接口,耗时3秒)
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("异步方法执行完成");
}
}
3.2 异步调用测试(Controller)
创建接口,调用异步方法,测试异步效果——主线程无需等待异步方法完成,直接返回响应,提升接口速度:
import com.example.demo.task.AsyncTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 异步处理测试控制器
*/
@Slf4j
@RestController
@RequestMapping("/async")
public class AsyncController {
@Autowired
private AsyncTask asyncTask;
/**
* 测试异步调用:主线程无需等待异步方法完成,直接返回
*/
@GetMapping("/test")
public String testAsync() {
log.info("主线程开始执行");
// 调用异步方法(无需等待,直接继续执行)
asyncTask.asyncMethod();
log.info("主线程执行完成,返回响应");
return "异步调用已触发,主线程已返回";
}
}
测试结果(日志输出):
主线程开始执行
主线程执行完成,返回响应
异步方法开始执行,线程名:async-task-1
异步方法执行完成
关键结论:主线程无需等待异步方法(耗时3秒)完成,直接返回响应,极大提升了接口响应速度,避免用户等待。
3.3 真实案例:异步发送邮件
项目中“发送邮件”是典型的耗时操作(需连接邮箱服务器、发送邮件),若同步发送,会导致接口阻塞,用户等待时间过长。通过@Async实现异步发送邮件,提升用户体验,结合Spring Mail实现完整案例:
3.3.1 邮件发送工具类(异步)
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* 异步邮件发送工具类
*/
@Slf4j
@Component
public class AsyncMailUtil {
@Autowired
private JavaMailSender javaMailSender;
// 从配置文件读取发送者邮箱
@Value("${spring.mail.username}")
private String fromEmail;
/**
* 异步发送简单邮件(文本邮件)
* @param toEmail 接收者邮箱
* @param subject 邮件主题
* @param content 邮件内容
*/
@Async
public void sendSimpleMail(String toEmail, String subject, String content) {
try {
log.info("开始异步发送邮件:接收者{},主题{}", toEmail, subject);
// 构建邮件信息
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(fromEmail); // 发送者邮箱
message.setTo(toEmail); // 接收者邮箱
message.setSubject(subject); // 邮件主题
message.setText(content); // 邮件内容
// 发送邮件(耗时操作,异步执行)
javaMailSender.send(message);
log.info("邮件发送成功:接收者{}", toEmail);
} catch (Exception e) {
log.error("邮件发送失败:接收者{},错误信息{}", toEmail, e.getMessage());
}
}
}
3.3.2 邮件发送接口(Controller)
import com.example.demo.util.AsyncMailUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 邮件发送控制器(异步发送)
*/
@RestController
@RequestMapping("/mail")
public class MailController {
@Autowired
private AsyncMailUtil asyncMailUtil;
/**
* 发送简单邮件(异步)
* @param toEmail 接收者邮箱
* @param subject 邮件主题
* @param content 邮件内容
*/
@GetMapping("/send")
public String sendMail(
@RequestParam String toEmail,
@RequestParam String subject,
@RequestParam String content) {
// 异步发送邮件,主线程直接返回
asyncMailUtil.sendSimpleMail(toEmail, subject, content);
return "邮件发送请求已提交,正在异步发送,请耐心等待";
}
}
测试:访问接口http://localhost:8080/mail/send?toEmail=xxx@qq.com&subject=测试邮件&content=异步发送邮件测试,接口会立即返回响应,邮件发送操作在后台异步执行,无需用户等待。
四、定时任务与异步处理结合(高级场景)
项目中很多场景需要“定时任务 + 异步处理”结合,比如:定时(每天凌晨)异步发送批量邮件、定时异步生成报表并推送,既实现自动化,又避免任务阻塞。
示例:每天凌晨1点,异步发送批量通知邮件(避免单封邮件发送耗时导致整体任务阻塞):
import com.example.demo.util.AsyncMailUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 定时+异步结合:每天凌晨1点,异步发送批量邮件
*/
@Slf4j
@Component
public class ScheduledAsyncTask {
@Autowired
private AsyncMailUtil asyncMailUtil;
// 模拟需要发送邮件的用户列表
private final List<String> emailList = Arrays.asList(
"user1@163.com", "user2@163.com", "user3@163.com"
);
/**
* 每天凌晨1点执行定时任务,异步发送批量邮件
*/
@Scheduled(cron = "0 0 1 * * ?")
public void scheduledAsyncSendMail() {
log.info("开始执行定时异步邮件发送任务");
// 循环发送邮件,每封邮件异步执行,避免阻塞
for (String email : emailList) {
asyncMailUtil.sendSimpleMail(
email,
"每日通知",
"您好,这是每日定时推送的通知邮件,无需回复~"
);
}
log.info("定时异步邮件发送任务提交完成,后续由异步线程执行");
}
}
核心优势:定时任务触发批量操作,每一个耗时的子操作(发送单封邮件)异步执行,既实现了自动化,又提升了任务执行效率,避免单线程阻塞导致的任务延迟。
五、常见问题与解决方案(项目落地避坑)
-
@Scheduled定时任务不执行?
解决方案:① 检查启动类是否添加@EnableScheduling注解;② 定时任务类是否添加@Component注解(交给Spring管理);③ 方法必须是无参、无返回值的public方法;④ 避免在定时方法上添加@Async(若需异步,可在方法内部调用异步方法)。 -
@Async异步方法失效?
解决方案:① 检查启动类是否添加@EnableAsync注解;② 异步方法类是否添加@Component注解;③ 异步方法不能与调用方法在同一个类中(Spring AOP代理机制限制);④ 方法必须是public方法,不能是private、static方法。 -
定时任务阻塞(多个任务排队执行)?
解决方案:配置定时任务线程池(spring.task.scheduling.pool.size),设置足够的线程数,避免单线程阻塞;同时避免定时任务执行时间过长。 -
异步发送邮件失败,提示“授权码错误”?
解决方案:① 确认邮箱授权码正确(不是登录密码,需在邮箱设置中开启SMTP服务,获取授权码);② 检查邮箱SMTP服务器、端口配置是否正确(如163邮箱host为smtp.163.com,端口465);③ 开启SSL加密(spring.mail.properties.mail.smtp.ssl.enable=true)。 -
项目部署后,定时任务执行时间偏差?
解决方案:① 确认服务器时间与本地时间一致(避免时区问题);② 若使用Docker部署,确保Docker容器时间与宿主机时间同步;③ 检查Cron表达式是否正确。
六、总结(项目自动化提升)
本文承接项目核心功能完善后的需求,完整实战了Spring Boot定时任务(@Scheduled)与异步处理(@Async)的核心用法,结合定时清理冗余数据、异步发送邮件等真实案例,帮你实现项目自动化,提升项目实用性和性能,核心要点总结:
-
定时任务(@Scheduled):通过3种方式(固定延迟、固定速率、Cron表达式)实现自动化任务,适配不同定时场景,核心是@EnableScheduling注解开启功能;
-
异步处理(@Async):通过注解实现耗时操作的异步执行,提升接口响应速度,核心是@EnableAsync注解开启功能,避免异步方法与调用方法同包同类;
-
场景结合:定时任务+异步处理,实现更复杂的自动化场景(如批量邮件推送、定时报表生成),既自动化又高效;
-
项目落地:配置自定义线程池,避免任务阻塞;排查常见问题,确保功能稳定运行,适配生产环境。
掌握本文内容后,你可以轻松将定时任务与异步处理集成到自己的项目中,实现自动化清理、异步通知、定时报表等功能,进一步完善项目,降低人工成本,提升项目专业度和用户体验,让项目更具竞争力。
6353

被折叠的 条评论
为什么被折叠?



