不使用Retrofit时OkHttpClient Authenticator刷新Token的回调处理问题
问题与解决方案
问题描述
我在应用中刷新Access Token时遇到了回调处理的问题:需要在authenticate方法中返回携带新Token的请求,但getAuthRequest是异步的(void方法),没法在回调里把生成的Request传递出去,也没法在回调内部直接返回给authenticate方法。代码示例如下:
public Request authenticate(Route route, Response response) throws IOException { // GetAuthRequest是void方法,无法在回调中赋值String值。 getAuthRequest(new AuthResponse() { @Override public Request onSuccess(String token) { return response.request().newBuilder() .header("Authorization", "Bearer " + token) .build(); } }); }
解决方法
核心矛盾是OkHttp的authenticate方法为同步执行,而异步请求的回调无法直接为该方法提供返回值,解决思路是将刷新Token的操作改为同步调用,或通过阻塞等待的方式获取异步回调结果。
方案一:重构为同步请求(推荐)
将异步的getAuthRequest改造成同步方法,直接在authenticate内获取新Token并生成请求:
- 新增同步获取Token的方法:
public String getAuthRequestSync() throws IOException { // 构造刷新Token的同步请求 Request refreshRequest = new Request.Builder() .url("你的刷新Token接口地址") // 根据实际接口调整请求方式、参数 .post(RequestBody.create(MediaType.parse("application/json"), "{\"refresh_token\":\"你的刷新Token\"}")) .build(); // 同步执行请求并解析响应 try (Response refreshResponse = okHttpClient.newCall(refreshRequest).execute()) { if (!refreshResponse.isSuccessful()) { throw new IOException("刷新Token失败,响应码:" + refreshResponse.code()); } // 解析响应体获取新Token,此处示例用Gson解析,根据实际情况调整 String respBody = refreshResponse.body().string(); TokenResp tokenResp = new Gson().fromJson(respBody, TokenResp.class); return tokenResp.getAccessToken(); } }
- 在
authenticate中调用同步方法:
@Override public Request authenticate(Route route, Response response) throws IOException { // 同步获取新Token String newToken = getAuthRequestSync(); // 生成并返回携带新Token的请求 return response.request().newBuilder() .header("Authorization", "Bearer " + newToken) .build(); }
方案二:用CountDownLatch阻塞等待异步回调(临时方案)
如果无法修改getAuthRequest的异步逻辑,可以用CountDownLatch阻塞当前线程,等待回调完成后获取结果:
@Override public Request authenticate(Route route, Response response) throws IOException { CountDownLatch latch = new CountDownLatch(1); Request[] newRequestHolder = new Request[1]; // 用数组存储回调结果,规避匿名内部类无法修改外部变量的限制 getAuthRequest(new AuthResponse() { @Override public Request onSuccess(String token) { newRequestHolder[0] = response.request().newBuilder() .header("Authorization", "Bearer " + token) .build(); latch.countDown(); // 通知等待线程任务完成 return newRequestHolder[0]; } @Override public void onFailure() { latch.countDown(); // 此处可添加失败处理逻辑 } }); try { // 设置超时时间,避免无限阻塞 if (!latch.await(10, TimeUnit.SECONDS)) { throw new IOException("刷新Token超时"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IOException("等待刷新Token时被中断", e); } if (newRequestHolder[0] == null) { throw new IOException("刷新Token失败"); } return newRequestHolder[0]; }
注意事项
- 同步请求会阻塞当前线程,需合理设置超时时间,避免影响应用响应速度。
- 无论哪种方案,都要处理刷新Token失败的场景,及时抛出异常终止请求。
- 优先选择同步请求方案,代码更简洁易维护。
内容的提问来源于stack exchange,提问作者Daniel Hernández
相关产品推荐
相关产品推荐

