在 Java 服务端开发中,Access Token 管理器必须是单例(Singleton) ,以确保整个应用只维护一个 Token 副本,并使用强大的可重入锁(ReentrantLock) 来保证在高并发下的数据一致性。
1. Token 管理器结构:单例模式
我们使用最常用的懒汉式或饿汉式单例模式来创建 AccessTokenManager 类。这里以饿汉式为例,确保类加载时即完成初始化。
java
public class AccessTokenManager {
// 状态变量
private String accessToken = null;
private long expiresAt = 0L; // 精确到毫秒的过期时间戳
// 线程安全的核心:可重入锁
private final ReentrantLock lock = new ReentrantLock();
// 单例实例(饿汉式)
private static final AccessTokenManager INSTANCE = new AccessTokenManager();
// 构造方法私有化
private AccessTokenManager() {
// 初始化配置
}
// 对外提供获取实例的静态方法
public static AccessTokenManager getInstance() {
return INSTANCE;
}
// ... getAccessToken 方法实现
}
2. 核心方法:getAccessToken 的实现逻辑
getAccessToken 方法负责检查当前 Token 的状态。其核心在于使用锁来保护 Token 刷新这一关键操作,同时允许大量并发的读操作在 Token 有效时无需等待锁。
逻辑流程:
-
快速检查: 检查当前 Token 是否在安全阈值(例如 10 分钟)内有效。如果有效,直接返回,避免锁竞争。
-
获取锁: 如果 Token 即将过期或已过期,线程尝试获取
ReentrantLock。此时,其他线程将被阻塞。 -
二次校验(Double Check): 在获取锁后,必须再次检查 Token 状态。因为可能在线程 A 释放锁的瞬间,线程 B 立即获得了锁,但线程 A 已经完成了刷新。
-
执行刷新: 如果二次校验仍显示 Token 无效,则线程 B 调用企业微信 API 执行刷新操作。
-
更新和释放: 更新
accessToken和expiresAt,并在finally块中释放锁。
Java 代码关键部分:
java
private void refreshAccessToken() {
// 实际调用企业微信 API 的代码
// 假设 API 返回 { "access_token": "new_token", "expires_in": 7200 }
// 1. 调用 API 获取新 Token 和有效期
// APIResponse response = callWeComApi();
// 2. 更新状态
this.accessToken = "新获取的 Token"; // response.getAccessToken();
// 设定过期时间为当前时间 + 7200秒 - 600秒安全边际
this.expiresAt = System.currentTimeMillis() + (7200 - 600) * 1000L;
}
public String getAccessToken() {
long currentTime = System.currentTimeMillis();
// 1. 快速检查 (无需锁)
if (accessToken != null && currentTime < expiresAt) {
return accessToken;
}
// 2. 尝试获取锁进行刷新
lock.lock();
try {
// 3. 二次校验 (在锁保护下)
if (accessToken != null && currentTime < expiresAt) {
return accessToken; // 已被其他线程刷新,直接返回
}
// 4. 执行刷新
refreshAccessToken();
return accessToken;
} catch (Exception e) {
// 记录日志或抛出异常
throw new RuntimeException("Access Token 刷新失败", e);
} finally {
// 5. 确保锁释放
lock.unlock();
}
}
3. ReentrantLock 的优势
相比于使用 synchronized 关键字,ReentrantLock 在这里的优势在于:
-
明确的锁定和释放: 必须手动调用
lock()和unlock(),强制要求开发者将释放锁的逻辑放置在finally块中,防止异常导致死锁。 -
非阻塞尝试:
ReentrantLock支持tryLock(),可以尝试获取锁,如果获取失败,线程可以选择做其他事情,而不是无限等待。 -
条件变量: 支持
Condition,可以更精细地控制线程的等待和唤醒,虽然在这个简单的 Token 场景中用不到,但在更复杂的并发任务中非常有用。
通过这种设计,AccessTokenManager 成为了一个高可用、线程安全的 Token 抽象层,为上层的所有 API 调用提供了稳定的保障。
QiWe开放平台提供了后台直登功能,登录成功后获取相关参数,快速Apifox在线测试,所有登录功能都是基于QiWe平台API自定义开发。