Spring AOP 实现通用 Token 自动刷新重试工具包

对接第三方系统时,Token 过期是高频痛点。传统方案到处写if‑else判断 401、手动刷新 Token、手写重试逻辑,业务代码被非业务逻辑污染。本文介绍一款注解驱动的通用组件 asset‑util‑tokenRetry,通过 AOP 切面实现 Token 过期自动刷新 + 自动重试,做到业务代码零侵入。

一、背景痛点

在对接各类第三方外部系统(U9、财务共享、IMS 等)时,经常遇到 Token 过期场景:

  1. 调用接口返回 401 / 抛出异常代表 Token 失效;
  2. 业务代码到处充斥 Token 过期判断、手动调用刷新 token、手动重试;
  3. 每个对接系统都要重复写一套相同的重试样板代码,代码冗余;
  4. 刷新 Token 逻辑散落在各个 service,维护成本高,漏写就会出现线上报错。

理想目标:

只需要加一个注解,当检测 Token 过期,切面自动完成刷新 Token、自动重试,业务代码只关心本身业务逻辑。

基于这个诉求,我们封装了asset‑util‑tokenRetry通用工具包,基于 Spring AOP + SpringBoot 自动装配实现。

二、组件核心能力

✨ 注解驱动,非侵入式 :@TokenRetry注解标记目标方法,业务代码无侵入。 ✨ 多第三方系统兼容 :不同外部系统分别实现管理器,通过 system 标识隔离。 ✨ 双重过期识别机制 :支持异常抛出检测 + 接口返回值检测 两种模式识别 token 过期。 ✨ 自动装配 :引入 Maven 依赖,编写对应管理器 Bean 即可直接使用。 ✨ 可控重试次数:自定义最大重试次数,防止无限死循环。

三、快速上手使用

1. 引入 Maven 依赖

XML 复制代码
<dependency>
    <groupId>com.meide.asset</groupId>
    <artifactId>asset-util-tokenRetry</artifactId>
    <version>1.0.0</version>
</dependency>

2. 实现每个第三方系统的 TokenRetryManager

为每一个对接的外部系统,实现TokenRetryManager接口,并注册为 Spring Bean。

java 复制代码
import org.springframework.stereotype.Component;

/**
 * 财务共享系统token刷新管理器
 */
@Component
public class FinancialShareTokenRetryManager implements TokenRetryManager {

    /**
     * 系统唯一标识,和注解 @TokenRetry(system="xxx")对应
     */
    @Override
    public String getSystem() {
        return "financialShare";
    }

    /**
     * 【核心】刷新token逻辑:调用第三方获取token接口,存入缓存(Redis/内存)
     */
    @Override
    public void refreshToken() {
        // 1.调用第三方token接口获取新token
        String newToken = httpClient.get("https://xxx.com/api/token");
        // 2.把新token存入redis缓存,业务方法直接从缓存读取
        redisTemplate.opsForValue().set("financial_share:token", newToken);
    }

    /**
     * 可选:从接口返回结果判断token是否过期
     * 例如返回code=401代表token失效
     */
    @Override
    public boolean isTokenExpired(Object response) {
        if (response instanceof ResponseDTO) {
            return ((ResponseDTO) response).getCode() == 401;
        }
        return false;
    }
}
  1. 业务 Service 添加注解使用
java 复制代码
@Service
public class FinancialShareService {

    /**
     * system:对应管理器的系统标识
     * maxRetries:最大重试次数(不包含首次调用,最多执行1+maxRetries次)
     */
    @TokenRetry(system = "financialShare", maxRetries = 2)
    public ResponseDTO queryData(Map<String, Object> params) {
        // 直接从缓存拿token,不需要关心过期刷新
        String token = getTokenFromCache();
        ResponseDTO result = httpClient.post(url, token, params);

        // 方式一:手动抛出TokenExpiredException触发重试
        if (result.getCode() == 401) {
            throw new TokenExpiredException("Token已过期");
        }
        return result;
    }
}

💡两种触发重试方式二选一即可

  1. 异常触发 :业务代码抛出 TokenExpiredException;
  2. 返回值触发 :在TokenRetryManager#isTokenExpired识别返回对象,不需要业务抛异常。

五、整体执行流程

整体分为两阶段检测:异常检测 、返回值检测

  1. 执行被@TokenRetry标记的业务方法;
  2. AOP 切面根据 system 找到对应的TokenRetryManager;
  3. 执行业务方法:
    • 如果抛出TokenExpiredException:判断是否还有重试次数;有则调用refreshToken()刷新 token,重新执行业务方法;无则向外抛出异常;
    • 方法正常返回:调用manager.isTokenExpired()判断返回结果是否 token 过期;true 则刷新 token 并重试;false 直接返回结果;
  4. 其他非 TokenExpiredException 异常直接向上抛出,不会触发重试逻辑。

注意:void返回的方法,不会执行返回值检测,只能通过抛出 TokenExpiredException 来触发重试

六、关键注意事项(生产踩坑点)

1. 事务 @Transactional 的坑

如果业务方法加了@Transactional,重试依然在同一个事务内部执行。

refreshToken()只是刷新缓存 token,本身不操作数据库一般不受影响; 如果 refreshToken 内部包含数据库操作,同一个事务回滚会把刷新动作也回滚。 👉 解决方案:refreshToken 内 DB 操作使用独立新事务(REQUIRES_NEW)。

2. 并发场景

内部注册中心TokenRetryManagerRegistry使用ConcurrentHashMap,支持并发注册与查询,线程安全。

3. 重复注册 Manager

同一个system注册多个 Bean,后注册的 Bean 会覆盖前面的,同时输出 WARN 日志提醒。

4. 重试次数配置

maxRetries不要设置过大,建议 1‑2 次即可,避免死循环。

5. void 返回方法限制

void 方法不会执行返回值检测,只能靠抛TokenExpiredException触发重试。

6. 非 token 异常不会重试

只有两种情况触发重试:

  1. 抛出TokenExpiredException;
  2. isTokenExpired(response)返回 true。

网络超时、业务报错等其他异常直接抛出,不重

九、总结

这套组件把 token 过期的识别、刷新、重试全部交给 AOP 切面处理,业务代码只需要添加注解,消除样板代码。 对于系统对接 U9、EBS、IMS 等多外部系统场景,非常适合接入。

相关推荐
会编程的吕洞宾几秒前
Spring AI 2.0 自校正结构化输出实战:让大模型稳定吐出合格 JSON
java·人工智能·后端·spring
bmxy小明同学7 分钟前
2026-10-09-token与成本
java·前端·数据库
深蓝AI22 分钟前
Future.result 超时了,Python 线程却没停:线程池取消任务的三个陷阱
后端
suhan35 分钟前
【数据结构】三、栈和队列:6.链队列、双端队列、队列的应用(树的层次遍历、广度优先BFS、先来先服务FCFS)
数据结构·后端·算法
鸿蒙开发36 分钟前
我把自己的应用上线了:从买服务器、域名和 DNS,到备案与 HTTPS
后端·github
neoooo37 分钟前
🚀Claude Code 小技巧(二):找回会话,撤销修改
前端·人工智能·后端
数据库小学妹40 分钟前
向量检索最小可运行实现:HNSW索引参数与召回率/延迟权衡
数据库·后端
姜鱼问生40 分钟前
Docker Volume 备份与恢复实战:不停机迁移数据
后端·算法·架构
suhan40 分钟前
【数据结构】三、栈和队列:5.顺序队列(循环队列)(初始化,判空判满,入队,出队,实例)
数据结构·后端·算法
看浪的路人41 分钟前
第7讲:大模型后端服务化——Go 实现高并发 LLM Proxy / Router / Fallback
开发语言·后端·golang