Spring Boot 定时任务与异步处理

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("定时异步邮件发送任务提交完成,后续由异步线程执行");
    }
}

核心优势:定时任务触发批量操作,每一个耗时的子操作(发送单封邮件)异步执行,既实现了自动化,又提升了任务执行效率,避免单线程阻塞导致的任务延迟。

五、常见问题与解决方案(项目落地避坑)

  1. @Scheduled定时任务不执行?

    解决方案:① 检查启动类是否添加@EnableScheduling注解;② 定时任务类是否添加@Component注解(交给Spring管理);③ 方法必须是无参、无返回值的public方法;④ 避免在定时方法上添加@Async(若需异步,可在方法内部调用异步方法)。
    
  2. @Async异步方法失效?

    解决方案:① 检查启动类是否添加@EnableAsync注解;② 异步方法类是否添加@Component注解;③ 异步方法不能与调用方法在同一个类中(Spring AOP代理机制限制);④ 方法必须是public方法,不能是private、static方法。
    
  3. 定时任务阻塞(多个任务排队执行)?

    解决方案:配置定时任务线程池(spring.task.scheduling.pool.size),设置足够的线程数,避免单线程阻塞;同时避免定时任务执行时间过长。
    
  4. 异步发送邮件失败,提示“授权码错误”?

    解决方案:① 确认邮箱授权码正确(不是登录密码,需在邮箱设置中开启SMTP服务,获取授权码);② 检查邮箱SMTP服务器、端口配置是否正确(如163邮箱host为smtp.163.com,端口465);③ 开启SSL加密(spring.mail.properties.mail.smtp.ssl.enable=true)。
    
  5. 项目部署后,定时任务执行时间偏差?

    解决方案:① 确认服务器时间与本地时间一致(避免时区问题);② 若使用Docker部署,确保Docker容器时间与宿主机时间同步;③ 检查Cron表达式是否正确。
    

六、总结(项目自动化提升)

本文承接项目核心功能完善后的需求,完整实战了Spring Boot定时任务(@Scheduled)与异步处理(@Async)的核心用法,结合定时清理冗余数据、异步发送邮件等真实案例,帮你实现项目自动化,提升项目实用性和性能,核心要点总结:

  • 定时任务(@Scheduled):通过3种方式(固定延迟、固定速率、Cron表达式)实现自动化任务,适配不同定时场景,核心是@EnableScheduling注解开启功能;

  • 异步处理(@Async):通过注解实现耗时操作的异步执行,提升接口响应速度,核心是@EnableAsync注解开启功能,避免异步方法与调用方法同包同类;

  • 场景结合:定时任务+异步处理,实现更复杂的自动化场景(如批量邮件推送、定时报表生成),既自动化又高效;

  • 项目落地:配置自定义线程池,避免任务阻塞;排查常见问题,确保功能稳定运行,适配生产环境。

掌握本文内容后,你可以轻松将定时任务与异步处理集成到自己的项目中,实现自动化清理、异步通知、定时报表等功能,进一步完善项目,降低人工成本,提升项目专业度和用户体验,让项目更具竞争力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码客日记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值