這節介紹JAXB和JSON的使用。
爲了在XML和對象間進行映射,修改一下Customer類,添加JAXB相關的annotations。如下:
@XmlRootElement(name="customer")
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
@XmlAttribute
protected int id;
@XmlElement
protected String fullname;
public Customer() {}
public int getId() { return this.id; }
public void setId(int id) { this.id = id; }
public String getFullName() { return this.fullname; }
public void setFullName(String name} { this.fullname = name; }
}
這裏,對應的xml結構大致如下:
<customer id="42">
<fullname>Bill Burke</fullname>
</customer>
以上Customer類用JAXB處理,簡單的如下:
Customer customer = new Customer();
customer.setId(42);
customer.setName("Bill Burke");
JAXBContext ctx = JAXBContext.newInstance(Customer.class);
StringWriter writer = new StringWriter();
ctx.createMarshaller().marshal(customer, writer);
String custString = writer.toString();
customer = ctx.createUnmarshaller().unmarshal(new StringReader(custString));
JAX-RS規範規定:實現者需要自動支持marshalling和unmarshalling由@XmlRootElement或@XmlType約束的類對象,或者是包裝在javax.xml.bind.JAXBElement中的對象。例如:
@Path("/customers")
public class CustomerResource {
@GET
@Path("{id}")
@Produces("application/xml")
public Customer getCustomer(@PathParam("id") int id) {
Customer cust = findCustomer(id);
return cust;
}
@POST
@Consumes("application/xml")
public void createCustomer(Customer cust) {
...
}
}
這裏createCustomer(Customer)方法中的Customer參數即由內置的JAXB的轉換來的。
注:內置的JAXB處理器會處理交換類型是 application/xml、text/xml 或 application/*+xml 並且參數/返回值對象含有JAXB註釋約束的類;另外,它也負責管理JAXBContext實例的創建和初始化,因爲JAXBContext實例的創建是一個耗資源的操作,實現者通過會緩存他們以待下次使用。
二、使用ContextResolvers管理你自己的JAXBContext
你可以通過配置你的JAXBContext實例以得到你想要的輸出。JAX-RS內置的JAXB提供器允許你插入你自己的JAXBContext實例,要想這樣做,你需要實現一個類似於工廠類的操作javax.ws.rs.ext.ContextResoler,去覆蓋缺省的JAXBContext的創建:
public interface ContextResolver<T> {
T getContext(Class<?> type);
}
例如:
@Provider
@Produces("application/xml")
public class CustomerResolver
implements ContextResolver<JAXBContext> {
private JAXBContext ctx;
public CustomerResolver() {
this.ctx = ...; // initialize it the way you want
}
public JAXBContext getContext(Class<?> type) {
if (type.equals(Customer.class)) {
return ctx;
} else {
return null;
}
}
}
自定義的resolver類必須實現ContextResolver接口,並且這個類必須添加@javax.ws.rs.ext.Provider註釋去表明它是一個JAX-RS組件。
注:@Produces註釋是可選的,它允許你爲ContextResolver指定特定的交換數據類型,它允許你以其他格式輸入,而非侷限於XML。
定義完自己的Resolver類後,就是註冊它了。
三、JAXB和JSON
JAXB可以相當靈活以支持其他格式,而不僅限於xml格式。Jettison就是一個開源的JAXB適配器,可以用來輸入或輸出JSON格式。
JSON是一個基於文本的、可以直接被JavaScript解析的是格式, 它是Ajax應用首選的交換格式。儘管對於JAX-RS並不要求支持JSON,不過多數實現者都會使用Jettison去支持JAXB聲明的類對象與JSON之間的轉換。
JSON比XMl簡單的多。 數據由"{}"包着,包含key/value對,值可以是帶引號的字符串,boolean值(true/false),數據或者是這些值的數據類型,例如:
{
"id" : 42,
"name" : "Bill Burke",
"married" : true ,
"kids" : [ "Molly", "Abby" ]
}
key/value之間用分號分隔,並以逗號爲間隔符。
* 使用BadgerFish進行XML-JSON轉換
- xml元素名變成key,文本值變成一個內嵌的,key值爲"$"的子元素值,例如:
<customer>Bill Burke</customer> 變成 { "customer" : { "$" : "Bill Burke" }} - 子元素變成值,例如:
<customer> <first>Bill</first> <last>Burke</last> </customer>
變成:{ "customer" : { "first" : { "$" : "Bill"}, "last" : { "$" : "Burke" } } }
3. 多個同名元素值變成一個列表:
<customer> <phone>978-666-5555</phone> <phone>978-555-2233</phone> </customer
變成:{ "customer" : { "phone" : [ { "$": "978-666-5555"}, { "$":"978-555-2233"} ] } }
4. 屬性變成一個以@開始的值,例如:<customer id="42"> <name>Bill Burke</name> </customer>
變成:{ "customer" : { "@id" : 42, "name" : {"$": "Bill Burke"} } }
5. namespace會對應一個@xmlns屬性值,缺省的namespace對應"$", 所有子元素和屬性都使用namespace的前綴作爲他們名字的一部分,例如:<customer xmlns="urn:cust" xmlns:address="urn:address"> <name>Bill Burke</name> <address:zip>02115</address:zip> </customer>
對應:{ "customer" : { "@xmlns" : { "$" : "urn:cust", "address" : "urn:address" } , "name" : { "$" : "Bill Burke", "@xmlns" : { "$" : "urn:cust", "address" : "urn:address" } }, "address:zip" : { "$" : "02115", "@xmlns" : { "$" : "urn:cust", "address" : "urn:address" }} } }
* JSON和JSON Schema
BadgerFish對於Javascript程序員來說並不很直觀,不建議在XmlSchema和JSon之間進行映射。另一個更好的方式是定義一個JSON的schema來進行java對象和json之間的映射。一個好的框架是Jackson。
四、自定義輸出
除了xml和json外,還有很多很多其他的格式,JAX-RS只包含很少的一部分。下面要介紹怎麼實現自己的轉換器,這裏假設沒有JAXB,我們自己實現一個。
* MessageBodyWriter
首先實現JAXB-Marshalling支持。要實現java對象和xml之間的自動轉換,我們需要創建一個實現javax.ws.rs.ext.MessageBodyWriter的接口:
public interface MessageBodyWriter<T> { boolean isWriteable(Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType); long getSize(T t, Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType); void writeTo(T t, Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException; }
- isWriteable()方法決定當前對象是否支持
- getSize()方法決定Content-Length的值
- writeTo()做最終的寫出操作
下面看JAXBMarshaller的實現:
@Provider @Produces("application/xml") public class JAXBMarshaller implements MessageBodyWriter { public boolean isWriteable(Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType) { return type.isAnnotationPresent(XmlRootElement.class); }
首先使用@Provider,告訴JAX-RS,這是一個可配置的JAX-RS組件;另外,必須添加@Produces,以告訴JAX-RS,這個MessageBodyWriter支持哪些交換類型。
JAX-RS按照以後算法查找一個合適的MessageBodyWriter來輸出一個對象:
- 首先查看@Produces,以確定是否是支持的交換數據類型
- 其他查找最優的匹配,例如對於 application/xml,找到三種可用的(application/*,*/*, application/xml),則 application/xml爲最優
- 最後,得到可用列表以後,就會順序調用MessageBodyWriter.isWriteable()。如果成功,剛輸出,否則嘗試下一個。
對於getSize()方法,如果不能確定大小,則直接返回-1即可。
增加縮進
默認情況下,所有的空白和特殊字符都被去除。輸出內容都在一行上。如果希望以格式化輸出,則可以增加@Pretty可以保留縮進,例如:
@GET @Path("{id}") @Produces("application/xml") @Pretty public Customer getCustomer(@PathParam("id") int id) {...}
所以在自定義的Marsheller中需要處理這個annotation:
public void writeTo(Object target, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream outputStream) throws IOException { try { JAXBContext ctx = JAXBContext.newInstance(type); Marshaller m = ctx.createMarshaller(); boolean pretty = false; for (Annotation ann : annotations) { if (ann.annotationType().equals(Pretty.class)) { pretty = true; break; } } if (pretty) { marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); } m.marshal(target, outputStream); } catch (JAXBException ex) { throw new RuntimeException(ex); } }
插入JAXBContext
我們已經瞭解了怎麼插入一個JAXBContext,這裏我們需要知道怎麼把它加入到自定義的Marshaller.
我們需要找到一種方法,定位到一個ContextResoler類,這個類提供了JAXBContext對象,這個是通過javax.ws.rs.ext.Providers接口實現的:
public interface Providers { <T> ContextResolver<T> getContextResolver(Class<T> contextType, MediaType mediaType); <T> MessageBodyReader<T> getMessageBodyReader(Class<T> type, Type genericType, Annotation annotations[], MediaType mediaType); <T> MessageBodyWriter<T> getMessageBodyWriter(Class<T> type, Type genericType, Annotation annotations[], MediaType mediaType); <T extends Throwable> ExceptionMapper<T> getExceptionMapper(Class<T> type); }
使用Providers.getContextResolver()得到ContextResolver對象。所以我們需要在Marshaller裏注入一個Providers對象:@Context protected Providers providers; public void writeTo(Object target, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream outputStream) throws IOException { try { JAXBContext ctx = null; ContextResolver<JAXBContext> resolver = providers.getContextResolver(JAXBContext.class, mediaType); if (resolver != null) { ctx = resolver.getContext(type); } if (ctx == null) { // create one ourselves ctx = JAXBContext.newInstance(type); } ctx.createMarshaller().marshal(target, outputStream); } catch (JAXBException ex) { throw new RuntimeException(ex); } }
這樣就完成了Marshaller。
* MessageBodyReader
要自定義unmarshall,則需要用到javax.ws.rs.ext.MessageBodyReader接口了:
public interface MessageBodyReader<T> { boolean isReadable(Class<?> type, Type genericType, Annotation annotations[], MediaType mediaType); T readFrom(Class<T> type, Type genericType, Annotation annotations[], MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException; }
它和上面的Marshaller自定義過程非常類似。不細說:
Object readFrom(Class<Object>, Type genericType, Annotation annotations[], MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { try { JAXBContext ctx = JAXBContext.newInstance(type); return ctx.createUnmarshaller().unmarshal(outputStream); } catch (JAXBException ex) { throw new RuntimeException(ex); } }
五、生命週期與環境
默認情況下,每個應用只創建一個MessageBodyReader、MessageBodyWriter和ContextResolver。如果想實例化多個對象,剛需要提供一個public的構造方法,以便JAX-RSS運行時傳入所有需要的參數值,可能只需要包含一個以@Context註釋的參數即可,例如:
@Provider @Consumes("application/json") public class MyJsonReader implements MessageBodyReader { public MyJsonReader(@Context Providers providers) { this.providers = providers; } }