mic*_*alk 12 java jackson java-http-client java-11
我在使用HttpClient::send带有 custom 的Java 11 直接将 JSON 反序列化为自定义对象时遇到问题HttpResponse.BodyHandler。我在回答这个 SO question时遇到了这个问题。
我使用的版本:
我创建了一个简单的泛型JsonBodyHandler类,它实现HttpResponse.BodyHandler:
public class JsonBodyHandler<W> implements HttpResponse.BodyHandler<W> {
private final Class<W> wClass;
public JsonBodyHandler(Class<W> wClass) {
this.wClass = wClass;
}
@Override
public HttpResponse.BodySubscriber<W> apply(HttpResponse.ResponseInfo responseInfo) {
return asJSON(wClass);
}
}
Run Code Online (Sandbox Code Playgroud)
该asJSON方法定义为:
public static <W> HttpResponse.BodySubscriber<W> asJSON(Class<W> targetType) {
HttpResponse.BodySubscriber<String> upstream = HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8);
return HttpResponse.BodySubscribers.mapping(
upstream,
(String body) -> {
try {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(body, targetType);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
Run Code Online (Sandbox Code Playgroud)
所以它返回一个自定义HttpResponse.BodySubscriber,它获取 body asString然后应用从 JSON 映射到给定targetType
的代码来测试它:
public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder(new URI("https://jsonplaceholder.typicode.com/todos/1"))
.header("Accept", "application/json")
.build();
Model model = HttpClient.newHttpClient()
.send(request, new JsonBodyHandler<>(Model.class))
.body();
System.out.println(model);
}
Run Code Online (Sandbox Code Playgroud)
和Model班级:
public class Model {
private String userId;
private String id;
private String title;
private boolean completed;
//getters setters constructors toString
}
Run Code Online (Sandbox Code Playgroud)
输出符合预期:
Model{userId='1', id='1', title='delectus aut autem', completed=false}
Run Code Online (Sandbox Code Playgroud)
但是,当我将asJSON方法更改为 readInputStream而不是Stringfirst 时:
public static <W> HttpResponse.BodySubscriber<W> asJSON(Class<W> targetType) {
HttpResponse.BodySubscriber<InputStream> upstream = HttpResponse.BodySubscribers.ofInputStream();
return HttpResponse.BodySubscribers.mapping(
upstream,
(InputStream is) -> {
try (InputStream stream = is) {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(stream, targetType);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
Run Code Online (Sandbox Code Playgroud)
它在调用读取值后挂起ObjectMapper并且它不会继续(我已经检查它是否成功地从端点获得响应,状态代码是 200)但它只是挂起。有谁知道可能是什么问题?
mic*_*alk 12
我刚刚发现了这个 SO 问题,它有同样的问题,但GZIPInputStream. 事实证明这HttpResponse.BodySubscribers.mapping是有问题的,它不能按文档工作。这是官方 OpenJDK 错误站点的链接。它已针对 OpenJDK 13 进行了修复。因此,一种解决方法是使用HttpResponse.BodySubscribers::ofString 而不是HttpResponse.BodySubscribers::ofInputStream作为上游的 for HttpResponse.BodySubscribers::mapping- 在我的问题中展示了如何执行此操作。
或者,正如@daniel 在评论中指出的那样,更好的解决方案是返回一个Supplier而不是模型类:
public static <W> HttpResponse.BodySubscriber<Supplier<W>> asJSON(Class<W> targetType) {
HttpResponse.BodySubscriber<InputStream> upstream = HttpResponse.BodySubscribers.ofInputStream();
return HttpResponse.BodySubscribers.mapping(
upstream,
inputStream -> toSupplierOfType(inputStream, targetType));
}
public static <W> Supplier<W> toSupplierOfType(InputStream inputStream, Class<W> targetType) {
return () -> {
try (InputStream stream = inputStream) {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(stream, targetType);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
}
Run Code Online (Sandbox Code Playgroud)
在JsonBodyHandler还使用Supplier:
public class JsonBodyHandler<W> implements HttpResponse.BodyHandler<Supplier<W>> {
private final Class<W> wClass;
public JsonBodyHandler(Class<W> wClass) {
this.wClass = wClass;
}
@Override
public HttpResponse.BodySubscriber<Supplier<W>> apply(HttpResponse.ResponseInfo responseInfo) {
return asJSON(wClass);
}
}
Run Code Online (Sandbox Code Playgroud)
然后我们可以这样称呼它:
public static void main(String[] args) throws URISyntaxException, IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder(new URI("https://jsonplaceholder.typicode.com/todos/1"))
.header("Accept", "application/json")
.build();
Model model = HttpClient.newHttpClient()
.send(request, new JsonBodyHandler<>(Model.class))
.body()
.get();
System.out.println(model);
}
Run Code Online (Sandbox Code Playgroud)
这甚至是OpenJDK 13 docs) 中描述的推广方式:
映射函数使用客户端的执行器执行,因此可用于映射任何响应主体类型,包括阻塞
InputStream。但是,在映射器函数中执行任何阻塞操作都有可能导致执行器线程阻塞一段未知时间(至少在阻塞操作完成之前),这可能最终导致执行器缺乏可用线程。因此,在映射到所需类型可能会阻塞的情况下(例如,通过读取 InputStream),则应首选映射到Supplier所需类型的 a 并将阻塞操作推迟到Supplier::get调用者的线程调用之前。
使用 Jackson,TypeReference我可以单独使用泛型来完成此操作,而不需要冗余Class<T>参数。
package com.company;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.http.HttpResponse;
import java.util.function.Supplier;
public class JsonBodyHandler<W> implements HttpResponse.BodyHandler<Supplier<W>> {
public JsonBodyHandler() {
}
private static <W> Supplier<W> toSupplierOfType(InputStream inputStream) {
return () -> {
try (InputStream stream = inputStream) {
return new ObjectMapper().readValue(stream, new TypeReference<W>() {
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
}
private static <W> HttpResponse.BodySubscriber<Supplier<W>> asJSON() {
return HttpResponse.BodySubscribers.mapping(
HttpResponse.BodySubscribers.ofInputStream(),
JsonBodyHandler::toSupplierOfType);
}
@Override
public HttpResponse.BodySubscriber<Supplier<W>> apply(HttpResponse.ResponseInfo responseInfo) {
return JsonBodyHandler.asJSON();
}
}
Run Code Online (Sandbox Code Playgroud)
并且在使用中
package com.company;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.time.Duration;
import java.util.List;
public class Main {
private static final HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://myApi"))
.timeout(Duration.ofMinutes(1))
.header("Content-Type", "application/json")
.build();
private static final HttpClient client = HttpClient.newBuilder().build();
public static void main(String[] args) throws InterruptedException {
client.sendAsync(Main.request, new JsonBodyHandler<List<MyDto>>())
.thenAccept(response -> {
List<MyDto> myDtos = response.body().get();
System.out.println(myDtos);
}).join();
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
8964 次 |
| 最近记录: |