如何在@Scheduled方法中调用API初始化并更新Bean内的AccessToken?
解决方案:定时刷新AccessToken并注入使用
核心思路
不要试图在@Scheduled方法中调用@Bean方法(这会绕过Spring容器,生成不受管理的独立实例),而是创建一个可更新状态的单例Bean来持有AccessToken,调度器注入该Bean并定时更新令牌,其他业务类通过@Autowired注入这个Bean获取最新令牌。
具体实现步骤
1. 创建AccessToken持有者Bean
这个Bean负责存储令牌,并提供线程安全的读写方法:
import org.springframework.stereotype.Component; import java.util.concurrent.atomic.AtomicReference; @Component public class AccessTokenHolder { // 用AtomicReference保证多线程环境下的读写原子性,避免脏读 private final AtomicReference<String> token = new AtomicReference<>(); // 获取当前有效令牌 public String getToken() { return token.get(); } // 更新令牌 public void setToken(String newToken) { token.set(newToken); } }
2. 编写令牌刷新调度器
注入AccessTokenHolder,定时调用认证API更新令牌:
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import javax.annotation.PostConstruct; @Component public class TokenRefreshScheduler { private final AccessTokenHolder tokenHolder; private final RestTemplate restTemplate; // 构造注入(推荐写法,避免字段注入的耦合问题) public TokenRefreshScheduler(AccessTokenHolder tokenHolder, RestTemplate restTemplate) { this.tokenHolder = tokenHolder; this.restTemplate = restTemplate; } // 应用启动时立即初始化令牌,避免等待第一次调度触发 @PostConstruct public void initAccessToken() { refreshAccessToken(); } // 每15分钟执行一次刷新(15分钟=900000毫秒) @Scheduled(fixedRate = 900000) public void refreshAccessToken() { // 替换为实际的认证API调用逻辑,这里用RestTemplate示例 AuthResponse authResponse = restTemplate.postForObject( "https://your-auth-server/api/token", yourAuthRequestParams, // 替换为实际的请求参数 AuthResponse.class // 替换为API返回的响应实体类 ); tokenHolder.setToken(authResponse.getAccessToken()); } }
3. 业务类中使用令牌
直接注入AccessTokenHolder,获取最新令牌调用其他API:
import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpEntity; @Service public class BusinessApiService { private final AccessTokenHolder tokenHolder; private final RestTemplate restTemplate; public BusinessApiService(AccessTokenHolder tokenHolder, RestTemplate restTemplate) { this.tokenHolder = tokenHolder; this.restTemplate = restTemplate; } public void callTargetApi() { String currentToken = tokenHolder.getToken(); // 构造请求头携带令牌 HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(currentToken); HttpEntity<Void> entity = new HttpEntity<>(headers); // 调用目标API // YourApiResponse response = restTemplate.exchange( // "https://target-api/api/resource", // HttpMethod.GET, // entity, // YourApiResponse.class // ).getBody(); } }
关键注意事项
- 线程安全:使用
AtomicReference而非普通字符串字段,避免多线程读写时的脏读问题。 - 初始化令牌:通过
@PostConstruct在应用启动时立即获取令牌,无需等待第一次调度触发。 - 避免直接调用@Bean方法:Spring的
@Bean方法被代理,直接调用会生成新的实例,无法共享状态;必须注入容器管理的Bean实例进行状态更新。
内容的提问来源于stack exchange,提问作者jvx23
相关产品推荐
相关产品推荐

