使用 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 作为输入。
我正在为供应商 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 作为输入。