最近,随着JAVA EE 7 标准的最终落地,其中Oracle 还发布了GlassFish 4服务器,它可以说是JAVA EE 7标准的一种参考实现。 其中,Eclipse旗下的EclipseLink开源项目向JAVE EE 7中贡献了不少力量,其中包括JPA 2.1 (JSR-338)的实现,另外一个贡献是本文向大家介绍的EclipseLink MOXy项目,它是JAVE EE 7中JAX-RS(REST标准)的一个默认的JSON Provider。
首先简单介绍下Eclipse旗下的EclipseLink开源项目,它主要用来实现快速将JAVA中的对象转化为各种类型的XML,该项目主要有如下的特性:
-
支持JAXB中最多的注解
-
同时支持XML和JSON
-
支持最新的JPA 2.1
对JPA-RS的增强支持
MOXY有十分强大的将各类JAVA对象 序列化为XML以及XML反序列化为JAVA对象的能力。这在REST架构的应用中,MOXY可以用来实现JAX-RS标准中的各种转换,下面以一个REST的例子进行讲解。如果读者对REST和JAX-RS标准有不清楚的地方,请参考相关的资料。
我们设计一个最简单的Hello World的REST Webservice。其中Customer对象是一个简单的POJO对象,代码如下:
-
package org.example.service;
-
-
import javax.ejb.*;
-
import javax.ws.rs.*;
-
import javax.ws.rs.core.MediaType;
-
import org.example.model.*;
-
-
@Stateless
-
@LocalBean
-
@Path("/customers")
-
public class CustomerService {
-
-
@GET
-
@Produces({
-
MediaType.APPLICATION_XML,
-
MediaType.APPLICATION_JSON
-
})
-
@Path("{id}")
-
public Customer read(@PathParam("id") int id) {
-
Customer customer = new Customer();
-
customer.setId(id);
-
customer.setName("Jane Doe");
-
-
PhoneNumber pn = new PhoneNumber();
-
pn.setType("work");
-
pn.setValue("5551111");
-
customer.getPhoneNumbers().add(pn);
-
-
return customer;
-
}
-
-
}
可以看到,Customer对象中有一个PhoneNumber的表示电话号码的Pojo。代码中的注解都是遵守JAX-RS标准的REST相关的注解。其中,用注解
@ MediaType.APPLICATION_XML和@ MediaType.APPLICATION_JSON,分别指出该REST会同时以XML和JSON的形式对外发布。而在JAX-RS标准中,用如下的方式,就可以实现REST的对外发布部署:
-
package org.example.service;
-
-
import javax.ws.rs.ApplicationPath;
-
import javax.ws.rs.core.Application;
-
-
@ApplicationPath("rest/*")
-
public class CustomerApplication extends Application {
-
-
}
其中,@ApplicationPath 注解指定所有服务的相对基址,如果为空字符串,则直接使用上下文根路径。上面表示这个REST服务将以如http://localhost/rest/xxx的形式对外发布,应该的根为rest。
接下来我们具体看Customer这个POJO,代码如下:
-
package org.example.model;
-
-
import java.util.*;
-
import javax.xml.bind.annotation.*;
-
-
@XmlRootElement
-
public class Customer {
-
-
private int id;
-
private String name;
-
private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
-
-
public int getId() {
-
return id;
-
}
-
-
public void setId(int id) {
-
this.id = id;
-
}
-
-
public String getName() {
-
return name;
-
}
-
-
public void setName(String name) {
-
this.name = name;
-
}
-
-
@XmlElementWrapper
-
@XmlElement(name="phoneNumber")
-
public List<PhoneNumber> getPhoneNumbers() {
-
return phoneNumbers;
-
}
-
-
}
在这个Customer对象中,其中保持了对另外一个对象phoneNumber的引用。使用@XmlElementWrapper,其目的是为了在XML中通过单独设置名为phoneNumber的别名标签去
更清晰的输出。
接下来请看PhoneNumber类,代码如下:
-
package org.example.model;
-
-
import javax.xml.bind.annotation.*;
-
-
public class PhoneNumber {
-
-
private String type;
-
private String value;
-
-
@XmlAttribute
-
public String getType() {
-
return type;
-
}
-
-
public void setType(String type) {
-
this.type = type;
-
}
-
-
@XmlValue
-
public String getValue() {
-
return value;
-
}
-
-
public void setValue(String value) {
-
this.value = value;
-
}
-
-
}
注意,其中使用了注解@XmlAttribute,将type设置为转换后XML中的一个属性,而@XmlValue则将字段value直接序列为值,即如下的样子:
-
<?xml version="1.0" encoding="UTF-8"?>
-
<phone-number type="work">555-1234</phone-number>
如果不使用@XmlValue注解,则输出的XML为:
-
<phone-numbertypephone-numbertype="work">
-
<value>12345</value>
-
</phone-number>
接下来,我们尝试调用这个服务。下面是调用的urlhttp://localhost:8080/CustomerResource/rest/customers/1,则返回的XML为:
-
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-
<customer id="1">
-
<name>Jane Doe</name>
-
<phoneNumbers>
-
<phoneNumber type="work">5551111</phoneNumber>
-
</phoneNumbers>
-
</customer>
这个并不奇怪,因为对象是以JAXB的标准去注解的,可以通过REST返回XML。如果在GlassFish 3.1.2的时候,Moxy还不是默认的JSON Provider,有如下的几点值得注意:
-
POJO中的id属性如果是int类型的会以JSON text类型返回
-
customer对象中的phoneNumbers属性,其实持有的是List<PhoneNumber>,但在转变为JSON时变为JSON对象而不是一个JSON数组,如下:
-
{
-
-
"id": "1",
-
-
"name": "Jane Doe",
-
-
"phoneNumbers": {
-
-
"phoneNumber": {
-
-
"@type": "work",
-
"$": "5551111"
-
}
-
}
-
}
更奇怪的是,由于使用了@XmlAttribute注解和@XmlValue 注解,转变成JSON后,会分别变成“@type”,“$”显的不合理。而在最新的GlassFish 4中,上面的问题已经得到明显改善,输出的JSON如下:
-
"id": 1,
-
-
"name": "Jane Doe",
-
-
"phoneNumbers": {
-
-
"phoneNumber": [
-
-
"@type": "work",
-
-
"$": "5551111"
-
]
-
}
-
}
在GlassFish 4中,能够正确将POJO中的如int类型的正确序列化为JSON中的整形,即“id”:1,注意到PhoneNumber类中的value属性由于是String类型,因此在序列化为JSON后依然为String类型。但熟悉JSON的朋友应该清楚,phoneNumbers在这里依然没能转换为最标准的JSON格式,但我们可以使用JAX-RS中的ContextResolver机制,并使用MXOY中MOXyJsonConfig类去自定义JSON格式的显示,代码如下:
-
package org.example.service;
-
-
import javax.ws.rs.ext.*;
-
import org.eclipse.persistence.jaxb.JAXBContextProperties;
-
import org.glassfish.jersey.moxy.json.MoxyJsonConfig;
-
-
@Provider
-
public class MOXyJsonContextResolver implements ContextResolver<MoxyJsonConfig> {
-
-
private final MoxyJsonConfig config;
-
-
public MOXyJsonContextResolver() {
-
config = new MoxyJsonConfig()
-
.setAttributePrefix("")
-
.setValueWrapper("value")
-
.property(JAXBContextProperties.JSON_WRAPPER_AS_ARRAY_NAME, true);
-
}
-
-
@Override
-
public MoxyJsonConfig getContext(Class<?> objectType) {
-
return config;
-
}
-
-
}
其中,通过MoxyJsonConfig类中的setValueWrapper方法,重新设置了使用原来POJO中的字段名作为JSON的key,因此就不带任何多余的符号了,生成的XML如下:
-
{
-
"id": 1,
-
"name": "Jane Doe",
-
"phoneNumbers": [
-
{
-
"type": "work",
-
"value": "5551111"
-
}
-
]
-
}
可见,这是符合JSON格式标准的输出了。 读者可以进一步通过http://www.eclipse.org/eclipselink/moxy.php访问更多关于MOXY项目的情况,也可以关注http://blog.bdoughan.com/的博客以了解更多关于MXOY项目的用法。
原文链接:http://www.javacodegeeks.com/2013/06/moxy-is-the-new-default-json-binding-provider-in-glassfish-4.html