使用 Java 11 HttpClient 和带有 Jackson 的自定义 BodyHandler 反序列化 JSON 会停止,并且不会继续

2022-09-03 06:04:09

我有一个问题,直接使用Java 11将JSON反序列化为自定义对象与自定义。我在回答这个问题时遇到了这个问题HttpClient::sendHttpResponse.BodyHandler

我正在使用的版本:

  • 开放JDK 11
  • 杰克逊 2.9.9.3

我创建了一个简单的泛型类,它实现了:JsonBodyHandlerHttpResponse.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);
                    }
            });
}

因此,它返回一个自定义,该自定义将 body as 作为,然后将从 JSON 的映射应用于给定的代码来测试它:HttpResponse.BodySubscriberStringtargetType

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}

但是,当我将方法更改为读取而不是第一个时:asJSONInputStreamString

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);
                    }
            });
}

它在调用读取值后挂起,并且它没有继续(我已经检查过它是否成功从终结点获取响应,状态代码为200),但随后它只是挂起。有谁知道可能是什么问题?ObjectMapper


答案 1

我刚刚发现这个SO问题,它有同样的问题,但有.事实证明,这是有缺陷的,它并不像记录的那样工作。这是官方OpenJDK错误网站的链接。它在OpenJDK 13中得到了修复。因此,一种解决方法是使用而不是作为上游 - 在我的问题中显示了如何做到这一点。GZIPInputStreamHttpResponse.BodySubscribers.mappingHttpResponse.BodySubscribers::ofStringHttpResponse.BodySubscribers::ofInputStreamHttpResponse.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);
            }
        };
    }

还用于:JsonBodyHandlerSupplier

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文档中描述的推广方式:

映射函数是使用客户端的执行器执行的,因此可用于映射任何响应正文类型,包括阻塞 。但是,在映射器函数中执行任何阻塞操作都存在阻塞执行器线程未知时间(至少在阻塞操作完成之前)的风险,这可能最终使执行器缺少可用线程。因此,在映射到所需类型可能会阻塞的情况下(例如,通过在 InputStream 上读取),则应首选映射到所需类型的 a 并将阻塞操作推迟到调用方的线程调用。InputStreamSupplierSupplier::get


答案 2

使用Jackson,我能够单独使用泛型来完成它,而不需要冗余参数。TypeReferenceClass<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();
    }
}

推荐