在直接使用带有自定义HttpClient::send的Java11 HttpResponse.BodyHandler将JSON反序列化为定制对象时,我遇到了问题。我在回答这个问题时遇到了这个问题。
我正在使用的版本:
我创建了一个简单的泛型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);
}
}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);
}
});
}因此,它返回一个自定义HttpResponse.BodySubscriber,它将主体作为String,然后应用从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);
}Model类:
public class Model {
private String userId;
private String id;
private String title;
private boolean completed;
//getters setters constructors toString
}产出与预期相符:
Model{userId='1', id='1', title='delectus aut autem', completed=false}但是,当我首先将asJSON方法更改为读取InputStream而不是String时:
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);
}
});
}它在使用ObjectMapper调用该值后挂起,并且不继续(我已经检查它是否成功地从端点获得响应,状态代码为200),但是它只是挂起。有人知道有什么问题吗?
发布于 2019-08-23 16:37:49
我刚刚发现了这个问题,它有同样的问题,但与GZIPInputStream。事实证明,HttpResponse.BodySubscribers.mapping是有缺陷的,而且不像文档所描述的那样工作。以下是官方的链接 OpenJDK bug站点。它是为OpenJDK 13修正的。因此,一个解决办法是使用HttpResponse.BodySubscribers::ofString而不是HttpResponse.BodySubscribers::ofInputStream作为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);
}
};
}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);
}
}然后我们可以这样称呼它:
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);
}这甚至被推广到在OpenJDK 13 docs中描述的方法中。
映射函数使用客户端的执行器执行,因此可以用于映射任何响应体类型,包括阻塞
InputStream。但是,在mapper函数中执行任何阻塞操作都有可能阻塞执行器线程的未知时间(至少在阻塞操作结束之前),这最终可能会使执行者缺乏可用的线程。因此,在映射到所需类型可能会阻塞的情况下(例如,通过在InputStream上读取),然后映射到所需类型的Supplier并将阻塞操作推迟到调用者的线程调用Supplier::get时应该是首选的。
发布于 2021-09-16 15:23:49
通过使用杰克逊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();
}
}并在使用中
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();
}
}发布于 2021-05-28 12:45:24
在科特林:
val objectMapper = ObjectMapper()
fun jsonBodyHandler(): HttpResponse.BodyHandler<JsonNode> {
val jsonNodeSubscriber = BodySubscribers.mapping(BodySubscribers.ofByteArray()) {
objectMapper.readTree(it)
}
return HttpResponse.BodyHandler { jsonNodeSubscriber }
}https://stackoverflow.com/questions/57629401
复制相似问题