14

我在使用HttpClient::send带有 custom的 Java 11 直接将 JSON 反序列化为自定义对象时遇到问题HttpResponse.BodyHandler。我在回答这个 SO question时遇到了这个问题。

我正在使用的版本:

  • 开放JDK 11
  • 杰克逊 2.9.9.3

我创建了一个简单的泛型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方法更改为 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);
                    }
            });
}

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

4

3 回答 3

16

我刚刚发现这个 SO question有同样的问题,但是GZIPInputStream. 事实证明这HttpResponse.BodySubscribers.mapping是错误的,并且它不像记录的那样工作。这是官方 OpenJDK 错误站点的链接。它已针对 OpenJDK 13 进行了修复。因此,一种解决方法是使用HttpResponse.BodySubscribers::ofString 而不是HttpResponse.BodySubscribers::ofInputStream作为上游使用HttpResponse.BodySubscribers::mapping- 在我的问题中显示了如何做到这一点。

或者,正如@daniel 在评论中指出的那样,一个更好的解决方案是返回 aSupplier而不是模型类:

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 函数中执行任何阻塞操作都会冒着阻塞 executor 线程未知时间的风险(至少直到阻塞操作完成),这可能最终导致 executor 的可用线程不足。因此,在映射到所需类型可能会阻塞的情况下(例如,通过读取 InputStream),则应优先映射到Supplier所需类型的 a 并将阻塞操作推迟到调用者的线程调用。Supplier::get

于 2019-08-23T16:37:49.587 回答
4

使用 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();
    }

}


并且在使用中

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-09-16T15:23:49.630 回答
1

在科特林:

    val objectMapper = ObjectMapper()

    fun jsonBodyHandler(): HttpResponse.BodyHandler<JsonNode> {
      val jsonNodeSubscriber = BodySubscribers.mapping(BodySubscribers.ofByteArray()) {
        objectMapper.readTree(it)
      }
      return HttpResponse.BodyHandler { jsonNodeSubscriber }
    }
于 2021-05-28T12:45:24.440 回答