应用场景
当服务端程序提供一组RESTful接口给第三方调用的时候,往往需要给对方提供SDK,此时如果简单使用okhttp3客户端封装调用,当请求量较大时,往往会产生请求阻塞,jvm内存升高的情况,这是可以使用jdk8中的CompletableFuture达到异步调用提高并发量的目的。
构建接口定义
假设我们请求的是获取用户详情的接口,此时接口返回的CompletableFuture封装的异步对象,定义如下接口:
public interface AsyncRestV2Client {
CompletableFuture<UserDetails> getUser();
}
构建客户端
构建okhttp3,此时可以把api key和api secret等接口鉴权信息传入,也能指定接口反序列化的JsonMapper对象,用来处理返回值,代码如下:
public final class AsyncRestV2ClientImpl implements AsyncRestV2Client {
static final Logger LOGGER = LoggerFactory.getLogger(AsyncRestV2ClientImpl.class);
final String BASE_URL = "https://api.test.com";
final String key;
final String sec;
final OkHttpClient client;
final ObjectMapper jsonMapper;
public AsyncRestV2ClientImpl(
String key,
String sec,
ObjectMapper jsonMapper,
OkHttpClient httpClient
) {
this.key = key;
this.sec = sec;
this.jsonMapper = jsonMapper;
this.client = httpClient;
}
@Override
public CompletableFuture<UserDetails> getUser() {
return asyncRequest(
"GET",
BASE_URL + "/v2/user",
emptyMap(),
CustomJacksonTypes.USER_DETAILS
);
}
接口实现
okhttp3提供了callback的机制,这也是异步的关键。当http请求完成后,会触发callback方法,把请求结果的处理封装进CompletableFuture,即可实现请求的异步化,代码如下:
private <T> CompletableFuture<T> asyncRequest(String method, String endpoint, Map<String, String> params, TypeReference<T> responseType) {
CompletableFuture<T> future = new CompletableFuture<>();
Request request;
try {
request = checkNotNull(buildRequest(method, endpoint, params));
} catch (Exception e) {
if (e.getClass().equals(ApiException.class)) {
future.completeExceptionally(e);
} else {
future.completeExceptionally(new ApiException("failed to build request " + method + " | " + endpoint, e));
}
return future;
}
Callback callback = new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
String responseString = "";
int code = response.code();
ResponseBody body = response.body();
if (body != null) {
responseString = body.string();
}
if (!response.isSuccessful()) {
try {
Error error = jsonMapper.readValue(responseString, Error.class);
future.completeExceptionally(new ApiException("request failed (" + code + ") : " + responseString, error));
} catch (Exception e) {
future.completeExceptionally(new ApiException("request failed (" + code + "), failed to parse response from '" + responseString + "'", e));
}
} else {
try {
T result = jsonMapper.readValue(responseString, responseType);
future.complete(result);
} catch (Exception e) {
future.completeExceptionally(new ApiException("request successful (" + code + "), failed to parse response from '" + responseString + "'", e));
}
}
}
@Override
public void onFailure(Call call, IOException e) {
future.completeExceptionally(new ApiException(e));
}
};
LOGGER.debug("{}", request);
client.newCall(request).enqueue(callback);
return future;
}
请求构建
构建请求使用的是okhttp3提供的API支持,可以作为参考使用:
private Request buildRequest(String method, String urlString, Map<String, String> params) throws JsonProcessingException {
Request.Builder builder = new Request.Builder();
if (method.equals("GET")) {
HttpUrl url = checkNotNull(HttpUrl.parse(urlString));
if (!params.isEmpty()) {
HttpUrl.Builder ub = url.newBuilder();
params.forEach(ub::addQueryParameter);
url = ub.build();
}
String signature = sign(method, url.toString());
builder
.get()
.url(url)
.header("X-LA-APIKEY", key)
.header("X-LA-SIGNATURE", signature)
.header("User-Agent", getUserAgent());
} else if (method.equals("POST")) {
HttpUrl url = checkNotNull(HttpUrl.parse(urlString));
String signature;
RequestBody body;
if (params.isEmpty()) {
signature = sign(method, url.toString());
body = RequestBody.create(null, new byte[0]);
builder.header("Content-Length", "0");
} else {
HttpUrl.Builder ub = url.newBuilder();
params.forEach(ub::addQueryParameter);
HttpUrl urlWithParams = ub.build();
signature = sign(method, urlWithParams.toString());
LOGGER.debug("signature : {}", signature);
String json = jsonMapper.writeValueAsString(params);
LOGGER.debug("body : {}", json);
body = RequestBody.create(MediaType.parse("application/json"), json);
}
builder
.post(body)
.url(url)
.header("Content-Length", "0")
.header("Content-Type", "application/json")
.header("X-LA-APIKEY", key)
.header("X-LA-SIGNATURE", signature)
.header("User-Agent", getUserAgent());
} else {
throw new ApiException("not supported request method " + method);
}
return builder.build();
}
接口调用
此时get方法会等callback执行之后才能获取结果,可以防止所有请求排队阻塞,达到提高并发的目的。
@Test
public void getUser() {
try {
System.out.println(
asyncClientV2(key, sec, defaultObjectMapper(), defaultHttpClient()).getUser().get()
);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}