使用 JSON 字符串作为正文创建 JAX-RS 客户端 Post 请求

Create JAX-RS Client Post Request with JSON String as Body

我正在为供应商 REST 服务之一编写 REST 客户端。我使用 jersey 2.x 和 JSON-P,下面是我添加的依赖项。

<dependencies>
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-processing</artifactId>
    <version>2.26</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-client</artifactId>
    <version>2.26</version>
</dependency>
</dependencies>

我成功地为 GET 请求编写了代码并收到了 JSON 输出。我将它保存到一个文件中并使用 JSON-P 来解释和执行我自己的逻辑,没有任何问题。

但是现在我需要写一个POST请求。当我如下使用 CURL 时,我可以做到。并希望使用 Jeresey 2.x 和 JSON-P.

实现相同的功能
curl -v -H "Accept:application/json" -H "Content-Type:application/json" -u user:password -X POST --databinary @./InputParameters.json https://<IP>:<PORT>/Configuration/server

InputParameters.json contents
{
"ip": "10.197.70.16",
"partNumber": 202067,
"model": "IBM P7"
}

当我尝试以 JSON 格式 ({"ip": "10.197.70.16", "partNumber": 202067, "model": "IBM P7"}) 将响应主体作为字符串传递时,但没有成功。所以尝试如下 JsonObject 仍然没有用。

JsonObject jsonObj = Json.createObjectBuilder()
            .add("ip", "10.197.70.16")
            .add("partNumber", 202067)
            .add("model", "IBM P7")
            .build();

response = invocationBuilder.post(Entity.json(jsonObj));

我知道核心 java,基于那个经验,我开始编写这个程序并通过 GET 而不是 POST 获得了成功。我怀疑我在使用 POST 做一些根本性的错误。

添加以下依赖项后问题已解决。在这一点上,我不确定它有什么作用。

感谢 Swamy (TCS) 支持解决此问题。

<dependency>
 <groupId>com.owlike</groupId>
 <artifactId>genson</artifactId>
 <version>1.4</version>
</dependency>

让我们解开你正在做的事情。首先是这部分:

JsonObject jsonObj = Json.createObjectBuilder()
    .add("ip", "10.197.70.16")
    .add("partNumber", 202067)
    .add("model", "IBM P7")
    .build();

这将创建一个 javax.json.JsonObject 实例。 JsonObject 是 JSON-P Java API 的一部分,几乎就是它所说的:一个 class 代表一个 JSON object。一个 JsonObject 实例包含 javax.json.JsonValue 个实例的层次结构,这些实例符合更具体的类型,如 JsonArray、JsonString、其他 JsonObjects 等。在这方面,它与 the classes of the DOM API for representing XML documents 没有什么不同(希望 Oracle 将 API 文档保持在 URL 一段时间)。但幸运的是 JSON 比 XML 和 DOM.

更直接

您的 jsonObj 实例将包含一个 JsonString 实例,其值“10.197.70.16”映射到名称“ip”,一个 JsonNumber 值 202067(可能表示为 BigDecimal)映射到名称“partNumber”等等。

接下来你的代码执行这个:

Entity.json(jsonObj)

javax.ws.rs.client.Entity.json(something) 基本上表明您想要创建一个实体,该实体将为 JAX-RS 客户端调用提供负载,如 Content-Type application/json。换句话说,当它被发送到 API 时,你为其创建它的 something 必须转换为 JSON 表示,它应该期望 JSON 有效负载并知道如何处理它。请注意 Entity.json(...) 具有泛型类型参数。方法签名是 static <T> Entity<t> json(T entity)。因此,您正在使用负载实体 jsonObj.

创建 Entity<JsonObject> 的实例

当这个交给javax.ws.rs.client.Invocation.Builder实例的post方法时(post方法其实是定义在它的parent接口SyncInvoker ) 客户端实现开始工作。

response = invocationBuilder.post(Entity.json(jsonObj));

它获取提供的实体实例及其内容(我们的 jsonObj),检查实体的所需输出是什么(这是 application/json)并寻找可以提供 objects 的提供者给定类型的输出。换句话说,必须找到可以给定 javax.json.JsonObject 的某个组件,并将其作为 JSON 的表示形式写入 OutputStream。处理此问题的组件可能是一个 javax.ws.rs.ext.MessageBodyWriter,声称它可以执行此转换并已注册到 JAX-RS 运行时。各种库都提供此类提供程序,您也可以编写自己的提供程序。这使得 JAX-RS 可以扩展以处理各种场景,处理 non-standard 输入和输出或让您调整其行为。当多个提供者能够处理给定的实体类型并产生所需的输出时,有一些规则可以确定哪个提供者承担这项工作。请注意,这可能取决于您的 class 路径中的内容。有一些方法可以明确地强制执行此操作。

客户端通过其配置将调用放在一起,使用适当的 URL、查询参数、HTTP 方法、headers 等。通过将实体以所需格式写入 OutputStream 来创建有效负载。在您的示例中,这会导致 POST 到服务器。调用完成后,您会收到一个 javax.ws.rs.core.Response,您可以使用它来确定 HTTP 结果代码并检索响应负载(如果有)。 Response 的 readEntity(Class<T> entityType) 方法的工作方式类似于将实体转换为有效载荷的过程。它搜索可以根据从 response.getMediaType() 返回的值解释响应流的 MessageBodyReader,并可以从中创建 Class entityType 的实例。

所以在解释了所有这些之后,您的方法到底出了什么问题?好吧,问题是您的 JAX-RS 运行时可用的默认实现可能没有专门用于 JsonObject 类型输入和预期输出 application/json 的编写器。如果服务器期望 JSON,那么您应该能够提供 JsonObject 作为有效负载,这似乎非常合乎逻辑。但是,如果 JAX-RS 实现找不到处理 class 的东西,那么充其量只能使用一些默认方法。在那种情况下,它可能会尝试将 object 解释为 POJO 并以默认方式将其序列化为 JSON,这可能会导致像这样的怪异:

{
    "valueMap": {
        "ip": {
            "value": "10.197.70.16"
        },
        "partNumber": {
            "num": 202067,
            "integral": TRUE
        },
        ...
    }
}

这就是 JsonObject 实例的字面解释,具体取决于它使用的实现方式以及 JAX-RS 使用什么将其转换为 JSON 输出。当然有可能 object 根本无法序列化为 JSON,要么是因为找不到合适的 MessageBodyWriter,要么是在创建输出时遇到错误。

第一个解决方案非常简单。只需将 JsonObject 转换为 String 并将其作为实体提供即可:

StringWriter stringWriter = new StringWriter();
try (JsonWriter jsonWriter = Json.createWriter(stringWriter);) {
    jsonWriter.writeObject(jsonObject);
} // some error handling would be needed here
String jsonPayload = stringWriter.toString();
response = invocationBuilder.post(Entity.json(jsonPayload));

看来你已经试过了。一个可能的问题是,使用的 MessageBodyWriter 只需要以合适的编码(可能是 UTF-8)输出字符串的字节,当以字符串作为输出并以 application/json 作为所需的内容类型时。有些人可能不会那样做。您可以尝试 Entity.entity(jsonPayload, MediaType.TEXT_PLAIN_TYPE.withCharset("UTF-8")) 但服务器可能会拒绝该呼叫。

其他可能的解决方案是

  • 为带有 @javax.ws.rs.Produces(MediaType.APPLICATION_JSON) 注释的字符串 object 编写自己的 MessageBodyWriter。
  • 更好的是,编写这样一个接受 JsonObject 实例的 class。
  • 为您的 JSON 结构创建 POJO classes 并让它们用于从实例生成 JSON 或反序列化对实例的 JSON 响应。
  • 寻找包含合适的提供者的扩展库来处理 javax.json classes.

将 com.owlike:genson 依赖项添加到您的项目中正是对最后一个建议的应用。 Genson 库提供 Java object 和 JSON 之间的双向转换,以及数据绑定。它的部分代码库专用于 JAX-RS 提供程序,其中 class 适合接受 JsonObject 作为输入。