第9章 对常用媒体类型的支持 - 9.1 JSON
Jersey JSON 支持之际,一组扩展模块,每个模块包含一个功能的实现,需要注册到您的配置实例(客户机/服务器)。有多个框架提供支持 JSON 处理和/或 JSON-to-Java 绑定。下面列出的模块提供支持 JSON 表示通过整合个人 JSON 框架 Jersey。目前,Jersey 集成了以下模块提供 JSON 支持:
- MOXy-JSON 默认通过 MOXy来绑定,并且在 Jersey 2.0 以来的应用程序是支持 JSON 绑定首选方法 。当JSON MOXy 模块在 类路径,Jersey 将自动发现模块和无缝地支持 JSON 绑定支持通过 MOXy 在应用程序中。(见4.3节,“自动发现功能”。
- Java API为JSON处理(JSON-P)
- Jackson
- Jettison
9.1.1. Approaches to JSON Support 支持JSON方法
每个上述扩展模块使用一个或多个可用的三种基本方法在处理JSON表示:
- 基于 POJO 的 JSON 绑定
- 基于 JAXB 的 JSON 绑定
- 低级的JSON解析和处理支持
第一个方法是非常通用的,允许您将任何 Java 对象映射到 JSON,反之亦然。其他两种方法限制你在 Java 类型资源方法可以生产和/或使用。基于JAXB 方法是有用的,如果你打算使用 JAXB 的某些特性和支持 XML 和JSON 表示。最后,低级方法给你最好的细粒度控制输出的 JSON 数据格式。
9.1.1.1. POJO support 基于 POJO
POJO的支持是最简单的方法将 Java 对象转换为 JSON 和转回去。
媒体模块,支持这种方法是 MOXy 和 Jackson
9.1.1.2. JAXB based JSON support 基于 JAXB
采取这种方法可以节省大量的时间,如果你想轻松地生成/使用 JSON 和 XML 数据格式。与 JAXB bean 你将能够使用相同的 Java 模型生成JSON和 XML 表示。与这样一个合作的另一个优点是简单模型和 API 在 Java SE 平台的可用性。 JAXB 使用注解的 POJO,这些可以处理简单的 Java bean。
基于 JAXB 方法的一个缺点可能是如果你需要使用一个非常具体的 JSON 格式。然后可能很难找到一个合适的方法来得到这样一个格式生产和消费。这是一个原因提供了许多配置选项,这样你就可以控制如何 JAXB bean 序列化和反序列化。额外的配置选项但是需要你更详细的了解您所使用的框架。
下面是一个非常简单的例子,来说明JAXB bean可能看起来像。
Example 9.1. Simple JAXB bean implementation
@XmlRootElement
public class MyJaxbBean {
public String name;
public int age;
public MyJaxbBean() {} // JAXB needs this
public MyJaxbBean(String name, int age) {
this.name = name;
this.age = age;
}
}
使用上面的 JAXB bean 生成 JSON 数据格式资源方法,然后一样简单:
Example 9.2. JAXB bean used to generate JSON representation
@GET
@Produces("application/json")
public MyJaxbBean getMyBean() {
return new MyJaxbBean("Agamemnon", 32);
}
注意,JSON @Produces 注释中指定特定的mime类型,MyJaxbBean 的方法返回一个实例,JAXB 能够处理。生成的 JSON 在这种情况下会看起来像:
{"name":"Agamemnon", "age":"32"}
正确使用 JAXB 注解本身可以控制一定 JSON 格式输出。具体来说,直接通过使用 JAXB 注释很容易做到重命名和删除属性。例如,下面的例子描述了上述 MyJaxbBean 变化将导致 {“king”:”Agamemnon”} JSON输出。
Example 9.3. Tweaking JSON format using JAXB
@XmlRootElement
public class MyJaxbBean {
@XmlElement(name="king")
public String name;
@XmlTransient
public int age;
// several lines removed
}
媒体模块,支持这种方法是 MOXy, Jackson,Jettison
9.1.1.3. Low-level based JSON support 低级的JSON解析和处理支持
JSON 处理 API 是一个新的标准 API 进行解析和处理 JSON 结构以类似的方式,SAX 和 StAX 解析器提供对 XML 。这个 API 是Java EE 7 和后来的一部分。另一个 JSON 解析/处理抛弃框架提供的 API。这两种 api 提供一个低级访问生产和消费 JSON 数据结构。采用这种低级的方法你会使用JsonObject(或JsonObject) 和/或 JsonArray (或分别 JsonArray )类在处理JSON数据表示。
这些低级 api 的最大优势是,你会得到完全控制和消费产生的 JSON 格式。你也能够生产和消费非常大的 JSON 结构使用流 JSON 解析器/生成器api。另一方面,处理您的数据模型对象可能会更复杂,相对于 POJO 或基于JAXB 绑定方法。差异是描述在以下代码片段。
基于JAXB 绑定方法
Example 9.4. JAXB bean creation
MyJaxbBean myBean = new MyJaxbBean("Agamemnon", 32);
当你构建一个 JAXB bean 时,JSON 写成 {“name”:”Agamemnon”, “age”:32}
现在构建一个等价的 JsonObject / JsonObject(生成的JSON的表达式),您需要几行代码。下面的例子说明了如何构造相同的 JSON 数据使用标准的Java EE 7 JSON处理API。
JsonObject myObject = Json.createObjectBuilder()
.add("name", "Agamemnon")
.add("age", 32)
.build();
最后看下使用 Jettison 来做同样的事,
Example 9.6. Constructing a JSONObject (Jettison)
JSONObject myObject = new JSONObject();
try {
myObject.put("name", "Agamemnon");
myObject.put("age", 32);
} catch (JSONException ex) {
LOGGER.log(Level.SEVERE, "Error ...", ex);
}
媒体模块,支持低级 JSON 解析和生成方法是 Java API for JSON Processing (JSON-P)和Jettison。除非你有强烈的理由使用非标准抛Jettison API,我们推荐您使用新标准Java API for JSON Processing (JSON-P) API。
9.1.2. MOXy
9.1.2.1. Dependency
需要添加 jersey-media-moxy 依赖库在你的 pom.xml 来使用 MOXy
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.16</version>
</dependency>
不用 maven 的话,要确保所有需要的库在类路径下,建jersey-media-moxy
9.1.2.2. Configure and register 配置和注册
如上所述在见4.3节,“自动发现功能”以及在本章早些时候,MOXy 模块是您不需要显式地注册它的特性(MoxyJsonFeature)在您的客户端/服务器配置的模块之一,这个特性是自动发现和注册时将 jersey-media-moxy 模块添加到您的类路径。
自动发现的 jersey-media-moxy 模块定义了几个属性,可用于控制自动登记 MoxyJsonFeature(除了通用CommonProperties.FEATURE_AUTO_DISCOVERY_DISABLE一个客户机/服务器变量):
- CommonProperties.MOXY_JSON_FEATURE_DISABLE
- ServerProperties.MOXY_JSON_FEATURE_DISABLE
- ClientProperties.MOXY_JSON_FEATURE_DISABLE
注意
手动注册其他Jersey JSON提供者功能(除了Java API for JSON Processing (JSON-P)) 禁用MoxyJsonFeature的自动启用和配置。
配置 MOXy 所提供的MessageBodyReader / MessageBodyWriter 您可以简单地创建一个 MoxyJsonConfig 实例,并设置必要的属性的值。最常见的属性可以使用一个特定的方法来设置属性的值也可以使用更通用的方法来设置属性:
- MoxyJsonConfig#property(java.lang.String, java.lang.Object)) ——设置 Marshaller 和 Unmarshaller属性值
- MoxyJsonConfig#marshallerProperty(java.lang.String, java.lang.Object)) ——设置 Marshaller 属性值
- MoxyJsonConfig#unmarshallerProperty(java.lang.String, java.lang.Object)) ——设置 Unmarshaller属性值
Example 9.7. MoxyJsonConfig - Setting properties.
final Map<String, String> namespacePrefixMapper = new HashMap<String, String>();
namespacePrefixMapper.put("http://www.w3.org/2001/XMLSchema-instance", "xsi");
final MoxyJsonConfig configuration = new MoxyJsonConfig()
.setNamespacePrefixMapper(namespacePrefixMapper)
.setNamespaceSeparator(':');
为了使 MoxyJsonConfig 对 MOXy 可见,您需要创建并注册ContextResolver 在您的客户端/服务器的代码。
Example 9.8. Creating ContextResolver
final Map<String, String> namespacePrefixMapper = new HashMap<String, String>();
namespacePrefixMapper.put("http://www.w3.org/2001/XMLSchema-instance", "xsi");
final MoxyJsonConfig moxyJsonConfig = MoxyJsonConfig()
.setNamespacePrefixMapper(namespacePrefixMapper)
.setNamespaceSeparator(':');
final ContextResolver<MoxyJsonConfig> jsonConfigResolver = moxyJsonConfig.resolver();
配置属性传递给底层 MOXyJsonProvider 的另一种方法是设置直接到您的配置实例(参见下面的一个例子)。这些都是被属性设置覆盖到 MoxyJsonConfig 。
Example 9.9. Setting properties for MOXy providers into Configurable
new ResourceConfig()
.property(MarshallerProperties.JSON_NAMESPACE_SEPARATOR, ".")
// further configuration
当 MOXy的 MessageBodyReader/ MessageBodyWriter 被使用,有一些 Jersey 的属性被设置默认值
Table 9.1. Default property values for MOXy MessageBodyReader/ MessageBodyWriter
javax.xml.bind.Marshaller#JAXB_FORMATTED_OUTPUT org.eclipse.persistence.jaxb.JAXBContextProperties#JSON_INCLUDE_ROOT
false org.eclipse.persistence.jaxb.MarshallerProperties#JSON_MARSHAL_EMPTY_COLLECTIONStrue org.eclipse.persistence.jaxb.JAXBContextProperties#JSON_NAMESPACE_SEPARATORorg.eclipse.persistence.oxm.XMLConstants#DOT
Example 9.10. Building client with MOXy JSON feature enabled.
final Client client = ClientBuilder.newBuilder()
// The line below that registers MOXy feature can be
// omitted if FEATURE_AUTO_DISCOVERY_DISABLE is
// not disabled.
.register(MoxyJsonFeature.class)
.register(jsonConfigResolver)
.build();
Example 9.11. Creating JAX-RS application with MOXy JSON feature enabled.
// Create JAX-RS application.
final Application application = new ResourceConfig()
.packages("org.glassfish.jersey.examples.jsonmoxy")
// The line below that registers MOXy feature can be
// omitted if FEATURE_AUTO_DISCOVERY_DISABLE is
// not disabled.
.register(MoxyJsonFeature.class)
.register(jsonConfigResolver);
9.1.2.3. Examples
Jersey 提供一个 JSON MOXy example如何使用 MOXy 来消费/生成JSON。
8.1.3. Java API for JSON Processing (JSON-P)
8.1.3.1. Dependency 依赖
使用 JSON-P 作为 JSON 的提供者需要添加 jersey-media-json-processing 模块到 pom.xml 文件:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-processing</artifactId>
<version>2.16</version>
</dependency>
如果你不使用 Maven,要确保所有需要的依赖关系(见jersey-media-json-processing)到类的路径。
9.1.3.2. Configure and register 配置和注册
正如见4.3节,“自动发现功能”中提到的,JSON-Processing 模块,您不需要显式地注册它的特性(JsonProcessingFeature)在您的客户端/服务器配置,这个特性是将jersey-media-json-processing 模块添加到您的类路径中时自动发现和注册时。
至于其他模块,jersey-media-json-processing还几个属性,会影响JsonProcessingFeature 的注册 (除了CommonProperties.FEATURE_AUTO_DISCOVERY_DISABLE等):
- CommonProperties.JSON_PROCESSING_FEATURE_DISABLE
- ServerProperties.JSON_PROCESSING_FEATURE_DISABLE
- ClientProperties.JSON_PROCESSING_FEATURE_DISABLE
JSON-P 提供配置 MessageBodyReader/MessageBodyWriter ,只需提供支持的属性值添加到配置实例(客户机/服务器)。目前支持这些属性:
- JsonGenerator.PRETTY_PRINTING (“javax.json.stream.JsonGenerator.prettyPrinting”)
Example 9.12. Building client with JSON-Processing JSON feature enabled.
ClientBuilder.newClient(new ClientConfig()
// The line below that registers JSON-Processing feature can be
// omitted if FEATURE_AUTO_DISCOVERY_DISABLE is not disabled.
.register(JsonProcessingFeature.class)
.property(JsonGenerator.PRETTY_PRINTING, true)
);
Example 9.13. Creating JAX-RS application with JSON-Processing JSON feature enabled.
// Create JAX-RS application.
final Application application = new ResourceConfig()
// The line below that registers JSON-Processing feature can be
// omitted if FEATURE_AUTO_DISCOVERY_DISABLE is not disabled.
.register(JsonProcessingFeature.class)
.packages("org.glassfish.jersey.examples.jsonp")
.property(JsonGenerator.PRETTY_PRINTING, true);
9.1.3.3. Examples
Jersey 提供了一个JSON Processing实例如何使用 JSON-Processing 处理消费/生成JSON。
9.1.4. Jackson (1.x and 2.x)
9.1.4.1. Dependency 依赖
使用 Jackson 2.x 需添加 jersey-media-json-jackson 模块到 pom.xml:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.16</version>
</dependency>
使用 Jackson 1.x 用法如下:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson1</artifactId>
<version>2.16</version>
</dependency>
如果你不使用 Maven,要确保所有需要的依赖关系(见 jersey-media-json-jackson 或 jersey-media-json-jackson) )到类的路径。
9.1.4.2. Configure and register 配置和注册
注意
注意,不同的名称空间,Jackson 1.x (org.codehaus.jackson) 和 Jackson 2.x (com.fasterxml.jackson)
Jackson JSON 处理器可以通过提供一个自定义Jackson 2 的ObjectMapper (或者 Jackson 1的 ObjectMapper ) 实例来控制。这可能是方便的,如果你需要重新定义默认 Jackson 行为和调整你的 JSON数据结构。Jackson 的所有特性的详细描述了本指南的范围。下面的例子给你一个提示如何写 ObjectMapper (ObjectMapper)实例 到你的 Jersey 的应用程序。
如果需要,在你的
配置(客户机/服务器)中,为了使用 Jackson 作为JSON(JAXB/POJO)提供者需要给 ObjectMapper注册JacksonFeature(Jackson1Feature)和 ContextResolver。
Example 9.14. ContextResolver
@Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public MyObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
@Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
}
private static ObjectMapper createDefaultMapper() {
final ObjectMapper result = new ObjectMapper();
result.configure(Feature.INDENT_OUTPUT, true);
return result;
}
// ...
}
完整示例,见 来自 JSON-Jackson 例子中的 MyObjectMapperProvider 类.
Example 9.15. Building client with Jackson JSON feature enabled.
final Client client = ClientBuilder.newBuilder()
.register(MyObjectMapperProvider.class) //无特殊要求无需注册这个
.register(JacksonFeature.class)
.build();
Example 9.16. Creating JAX-RS application with Jackson JSON feature enabled.
// Create JAX-RS application.
final Application application = new ResourceConfig()
.packages(“org.glassfish.jersey.examples.jackson”)
.register(MyObjectMapperProvider.class) //无特殊要求无需注册这个
.register(JacksonFeature.class);
9.1.4.3. Examples
Jersey 提供 JSON Jackson (2.x) 的例子和 JSON Jackson (1.x) 例子 展示如何使用 Jackson 消费/生成JSON。
9.1.5. Jettison
Jettison 模块提供 (反)序列化 JSON 的 JAXB 方法,除了使用纯 JAXB,配置选项可以设置在一个JettisonConfig 实例。然后实例可以进一步用于创建 JettisonJaxbContext,作为主要的配置点。通过你的专业 JettisonJaxbContext to Jersey,你将最终需要实现一个JAXBContext ContextResolver ((见下文)。
9.1.5.1. Dependency 依赖
如果使用 Jettison 需要添加 jersey-media-json-jettison 模块到 pom.xml :
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jettison</artifactId>
<version>2.16</version>
</dependency>
如果没有使用 Maven ,确保所有依赖库 (详见 jersey-media-json-jettison) 在 classpath 中.
9.1.5.2. JSON Notations 符号
JettisonConfig 允许你使用两种 JSON 符号,每种序列化 JSON 的方式是不同的。下面是支持符号的列表:
- JETTISON_MAPPED (默认符号)
- BADGERFISH
在处理更复杂的XML文档,你可能想要使用这些符号。即当你在JAXB bean处理多个XML名称空间。
独立的符号及其进一步的配置选项如下所述。而不是解释规则映射 XML 结构转换为JSON,描述的符号将使用一个简单的例子。以下是JAXB bean,它将被使用。
Example 9.17. JAXB beans for JSON supported notations description, simple address bean
@XmlRootElement
public class Address {
public String street;
public String town;
public Address(){}
public Address(String street, String town) {
this.street = street;
this.town = town;
}
}
Example 9.18. JAXB beans for JSON supported notations description, contact bean
@XmlRootElement
public class Contact {
public int id;
public String name;
public List<Address> addresses;
public Contact() {};
public Contact(int id, String name, List<Address> addresses) {
this.name = name;
this.id = id;
this.addresses =
(addresses != null) ? new LinkedList<Address>(addresses) : null;
}
}
以下文本主要工作是 contact bean 初始化:
Example 9.19. JAXB beans for JSON supported notations description, initialization
Address[] addresses = {new Address("Long Street 1", "Short Village")};
Contact contact = new Contact(2, "Bob", Arrays.asList(addresses));
例子中 contact bean 的 id=2, name=”Bob” 包含一个 address (street=”Long Street 1”, town=”Short Village”).
下面所有的配置选项描述的记录也在 JettisonConfig api文档
9.1.5.2.1. Jettison mapped notation 隐射符号
如果你需要处理各种 XML 名称空间,你会发现 Jettison 映射符号非常有用。允许定义一个特定名称空间 id 项:
...
@XmlElement(namespace="http://example.com")
public int id;
...
然后你只需配置从 XML 名称空间映射到 JSON 前缀如下:
Example 9.20. XML namespace to JSON mapping configuration for Jettison based mapped notation
Map<String,String> ns2json = new HashMap<String, String>();
ns2json.put("http://example.com", "example");
context = new JettisonJaxbContext(
JettisonConfig.mappedJettison().xml2JsonNs(ns2json).build(),
types);
JSON 的结果就像下面的例子.
Example 9.21. JSON expression with XML namespaces mapped into JSON
{
"contact":{
"example.id":2,
"name":"Bob",
"addresses":{
"street":"Long Street 1",
"town":"Short Village"
}
}
}
请注意,该 id 项变成了 example.id 基于XML名称空间映射的id。如果你有更多的 XML 名称空间的 XML ,您需要为所有这些配置合适的映射。
Jersey 版本 2.2 中引入另一个可配置的选项与序列化 JSON 数组与Jettison的映射的符号。当序列化元素代表单项列表/数组时,您可能想要使用以下 Jersey 配置方法来显式地名称元素将其视为数组不管实际内容是什么。
Example 9.22. JSON Array configuration for Jettison based mapped notation
context = new JettisonJaxbContext(
JettisonConfig.mappedJettison().serializeAsArray("name").build(),
types);
JSON 结果想下面例子,不重要的行已经删除
Example 9.23. JSON expression with JSON arrays explicitly configured via Jersey
{
"contact":{
...
"name":["Bob"],
...
}
}
9.1.5.2.2. Badgerfish notation
从 JSON 和 JavaScript 的角度来看,这种表示法绝对是最可读的。您可能不希望使用它,除非你需要确保你的 JAXB bean 可以完美地读写和JSON ,无需顾及任何格式配置中,名称空间等。
JettisonConfig 使用 badgerfish 符号可以通过下面语句创建
JettisonConfig.badgerFish().build()
JSON 输出如下:
Example 9.24. JSON expression produced using badgerfish notation
{
"contact":{
"id":{
"$":"2"
},
"name":{
"$":"Bob"
},
"addresses":{
"street":{
"$":"Long Street 1"
},
"town":{
"$":"Short Village"
}
}
}
}
9.1.5.3. Configure and register 配置和注册
若使用 Jettison 为你的 JSON (JAXB/POJO) 提供者,需给 JAXBContext(如果需要) 注册 JettisonFeature 和 ContextResolver 到 在你的配置 (client/server).
Example 9.25. ContextResolver
@Provider
public class JaxbContextResolver implements ContextResolver<JAXBContext> {
private final JAXBContext context;
private final Set<Class<?>> types;
private final Class<?>[] cTypes = {Flights.class, FlightType.class, AircraftType.class};
public JaxbContextResolver() throws Exception {
this.types = new HashSet<Class<?>>(Arrays.asList(cTypes));
this.context = new JettisonJaxbContext(JettisonConfig.DEFAULT, cTypes);
}
@Override
public JAXBContext getContext(Class<?> objectType) {
return (types.contains(objectType)) ? context : null;
}
}
Example 9.26. Building client with Jettison JSON feature enabled.
final Client client = ClientBuilder.newBuilder()
.register(JaxbContextResolver.class) // No need to register this provider if no special configuration is required.
.register(JettisonFeature.class)
.build();
Example 9.27. Creating JAX-RS application with Jettison JSON feature enabled.
// Create JAX-RS application.
final Application application = new ResourceConfig()
.packages("org.glassfish.jersey.examples.jettison")
.register(JaxbContextResolver.class) // No need to register this provider if no special configuration is required.
.register(JettisonFeature.class);
9.1.5.4. Examples 例子
Jersey 提供 JSON Jettison 的例子.
8.1.6. @JSONP - JSON with Padding Support
Jersey 提供 开箱即用的支持 JSONP - JSON with Padding。以下条件必须满足利用此功能:
- 资源的方法,它应该返回 JSON,包装需要由 @JSONP 注释的。
- MessageBodyWriter application/json 媒体类型,也接受资源方法的返回类型,需要注册(见本章JSON部分)。
- 用户的请求必须包含 Accept 标头的 JavaScript 定义媒体类型(见下文)。
可接受的媒体类型兼容 @JSONP 是:pplication/javascript, application/x-javascript, application/ecmascript, text/javascript, text/x-javascript, text/ecmascript, text/jscript.
Example 9.28. Simplest case of using @JSONP
@GET
@JSONP
@Produces({"application/json", "application/javascript"})
public JaxbBean getSimpleJSONP() {
return new JaxbBean("jsonp");
}
假设我们有注册一个 JSON 提供者和 JaxbBean 看起来像:
Example 9.29. JaxbBean for @JSONP example
@XmlRootElement
public class JaxbBean {
private String value;
public JaxbBean() {}
public JaxbBean(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(final String value) {
this.value = value;
}
}
当你发送一个 GET 请求接受标题设置为 application/javascript 你会得到一个结果实体看起来像:
callback({
"value" : "jsonp",
})
当然,方法配置包装方法返回的实体默认回调可以看到在前面的例子。@JSONP 有两个参数,可以配置:回调,queryParam。回调的名称代表JavaScript 应用程序定义的回调函数。queryParam,第二个参数定义的名称查询参数的回调函数的名称使用在请求(如果存在)。queryParam 值默认为__callback,所以即使你不自己设置查询参数的名称,客户总是可以影响结果包装 JavaScript 回调方法的名称。
注意
queryParam 值(如果设置)总是优先于回调函数值。
稍微改下代码
Example 9.30. Example of @JSONP with configured parameters.
@GET
@Produces({"application/json", "application/javascript"})
@JSONP(callback = "eval", queryParam = "jsonpCallback")
public JaxbBean getSimpleJSONP() {
return new JaxbBean("jsonp");
}
两次提交:
curl -X GET http://localhost:8080/jsonp
将返回
eval({
"value" : "jsonp",
})
以及
curl -X GET http://localhost:8080/jsonp?jsonpCallback=alert
将返回
alert({
"value" : "jsonp",
})
Example. 这里提供示例.