JAX-RS入門 七: 數據處理(2)

上節介紹了JAX-RS中怎麼與一些基礎的對象進行交互,這節介紹JAXB和JSON的使用。

 

一、JAXB

 

關於JAXB知識,可以去網上搜搜,或者要入門的話,可以看:http://liugang594.iteye.com/category/201713 。

 

爲了在XML和對象間進行映射,修改一下Customer類,添加JAXB相關的annotations。如下:

Java代碼  收藏代碼
  1. @XmlRootElement(name="customer")  
  2. @XmlAccessorType(XmlAccessType.FIELD)  
  3. public class Customer {  
  4.     @XmlAttribute  
  5.     protected int id;  
  6.     @XmlElement  
  7.     protected String fullname;  
  8.     public Customer() {}  
  9.     public int getId() { return this.id; }  
  10.     public void setId(int id) { this.id = id; }  
  11.     public String getFullName() { return this.fullname; }  
  12.     public void setFullName(String name} { this.fullname = name; }  
  13. }  

這裏,對應的xml結構大致如下:

Java代碼  收藏代碼
  1. <customer id="42">  
  2.     <fullname>Bill Burke</fullname>  
  3. </customer>  

 

以上Customer類用JAXB處理,簡單的如下:

Java代碼  收藏代碼
  1. Customer customer = new Customer();  
  2. customer.setId(42);  
  3. customer.setName("Bill Burke");  
  4.   
  5. JAXBContext ctx = JAXBContext.newInstance(Customer.class);  
  6. StringWriter writer = new StringWriter();  
  7.   
  8. ctx.createMarshaller().marshal(customer, writer);  
  9.   
  10. String custString = writer.toString();  
  11. customer = ctx.createUnmarshaller().unmarshal(new StringReader(custString));  

 

JAX-RS規範規定:實現者需要自動支持marshalling和unmarshalling由@XmlRootElement或@XmlType約束的類對象,或者是包裝在javax.xml.bind.JAXBElement中的對象。例如:

Java代碼  收藏代碼
  1. @Path("/customers")  
  2. public class CustomerResource {  
  3.   
  4.     @GET  
  5.     @Path("{id}")  
  6.     @Produces("application/xml")  
  7.     public Customer getCustomer(@PathParam("id"int id) {  
  8.         Customer cust = findCustomer(id);  
  9.         return cust;  
  10.     }  
  11.   
  12.     @POST  
  13.     @Consumes("application/xml")  
  14.     public void createCustomer(Customer cust) {  
  15.         ...  
  16.     }  
  17. }  

這裏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的創建:

Java代碼  收藏代碼
  1. public interface ContextResolver<T> {  
  2.            T getContext(Class<?> type);  
  3. }  

 

例如:

Java代碼  收藏代碼
  1. @Provider  
  2. @Produces("application/xml")  
  3. public class CustomerResolver  
  4.         implements ContextResolver<JAXBContext> {  
  5.     private JAXBContext ctx;  
  6.   
  7.     public CustomerResolver() {  
  8.         this.ctx = ...; // initialize it the way you want  
  9.     }  
  10.   
  11.     public JAXBContext getContext(Class<?> type) {  
  12.         if (type.equals(Customer.class)) {  
  13.             return ctx;  
  14.         } else {  
  15.             return null;  
  16.         }  
  17.     }  
  18. }  

 

自定義的resolver類必須實現ContextResolver接口,並且這個類必須添加@javax.ws.rs.ext.Provider註釋去表明它是一個JAX-RS組件。

 

注:@Produces註釋是可選的,它允許你爲ContextResolver指定特定的交換數據類型,它允許你以其他格式輸入,而非侷限於XML。

 

定義完自己的Resolver類後,就是註冊它了。這裏需要用到節2 (http://liugang594.iteye.com/blog/1491649) 中提到的javax.ws.rs.core.Application類,JAXB處理器會順序查詢所有註冊的ContextResolver類,並調用它的getContext()方法,如果返回空,則繼續找下一個,否則返回對應的JAXBContext對象;如果沒有找着,則使用內置的對象。

 

三、JAXB和JSON

 

JAXB可以相當靈活以支持其他格式,而不僅限於xml格式。Jettison就是一個開源的JAXB適配器,可以用來輸入或輸出JSON格式。

 

JSON是一個基於文本的、可以直接被JavaScript解析的是格式, 它是Ajax應用首選的交換格式。儘管對於JAX-RS並不要求支持JSON,不過多數實現者都會使用Jettison去支持JAXB聲明的類對象與JSON之間的轉換。

 

JSON比XMl簡單的多。 數據由"{}"包着,包含key/value對,值可以是帶引號的字符串,boolean值(true/false),數據或者是這些值的數據類型,例如:

 

Java代碼  收藏代碼
  1. {  
  2.     "id" : 42,  
  3.     "name" : "Bill Burke",  
  4.     "married" : true ,  
  5.     "kids" : [ "Molly""Abby" ]  
  6. }  

key/value之間用分號分隔,並以逗號爲間隔符。

 

* 使用BadgerFish進行XML-JSON轉換

  1. xml元素名變成key,文本值變成一個內嵌的,key值爲"$"的子元素值,例如:
    <customer>Bill Burke</customer> 變成 { "customer" : { "$" : "Bill Burke" }}
  2. 子元素變成值,例如:
    Xml代碼  收藏代碼
    1. <customer>  
    2.           <first>Bill</first>  
    3.           <last>Burke</last>  
    4. </customer>  
     變成:
    Js代碼  收藏代碼
    1. "customer" :  
    2.            {  
    3.               "first" : { "$" : "Bill"},  
    4.               "last" : { "$" : "Burke" }  
    5.             }  
    6. }  
  3. 多個同名元素值變成一個列表:
    Xml代碼  收藏代碼
    1. <customer>  
    2.            <phone>978-666-5555</phone>  
    3.            <phone>978-555-2233</phone>  
    4. </customer  
     變成:
    Js代碼  收藏代碼
    1. "customer" :  
    2.           { "phone" : [ { "$""978-666-5555"}, { "$":"978-555-2233"} ] }  
    3. }  
     
  4. 屬性變成一個以@開始的值,例如:
    Java代碼  收藏代碼
    1. <customer id="42">  
    2.            <name>Bill Burke</name>  
    3. </customer>  
     變成:
    Xml代碼  收藏代碼
    1. { "customer" :  
    2.          {   
    3.             "@id" : 42,  
    4.             "name" : {"$": "Bill Burke"}  
    5.           }  
    6. }  
     
  5.  namespace會對應一個@xmlns屬性值,缺省的namespace對應"$", 所有子元素和屬性都使用namespace的前綴作爲他們名字的一部分,例如:
    Xml代碼  收藏代碼
    1. <customer xmlns="urn:cust" xmlns:address="urn:address">  
    2.        <name>Bill Burke</name>  
    3.        <address:zip>02115</address:zip>  
    4. </customer>  
     對應:
    Java代碼  收藏代碼
    1. "customer" :  
    2.     { "@xmlns" : { "$" : "urn:cust",  
    3.             "address" : "urn:address" } ,  
    4.         "name" : { "$" : "Bill Burke",  
    5.               "@xmlns" : { "$" : "urn:cust",  
    6.               "address" : "urn:address" } },  
    7.         "address:zip" : { "$" : "02115",  
    8.         "@xmlns" : { "$" : "urn:cust",  
    9.         "address" : "urn:address" }}  
    10.     }  
    11. }  

* 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的接口:

 

Java代碼  收藏代碼
  1. public interface MessageBodyWriter<T> {  
  2.       
  3.     boolean isWriteable(Class<?> type, Type genericType,  
  4.             Annotation annotations[], MediaType mediaType);  
  5.       
  6.     long getSize(T t, Class<?> type, Type genericType, Annotation annotations[],   
  7.             MediaType mediaType);  
  8.   
  9.     void writeTo(T t, Class<?> type, Type genericType, Annotation annotations[],   
  10.             MediaType mediaType,   
  11.             MultivaluedMap<String, Object> httpHeaders,  
  12.             OutputStream entityStream) throws IOException, WebApplicationException;      
  13. }  
  •  isWriteable()方法決定當前對象是否支持
  • getSize()方法決定Content-Length的值
  • writeTo()做最終的寫出操作

下面看JAXBMarshaller的實現:

Java代碼  收藏代碼
  1. @Provider  
  2. @Produces("application/xml")  
  3. public class JAXBMarshaller implements MessageBodyWriter {  
  4.   
  5.     public boolean isWriteable(Class<?> type, Type genericType,  
  6.             Annotation annotations[], MediaType mediaType) {  
  7.         return type.isAnnotationPresent(XmlRootElement.class);  
  8.     }  

 

首先使用@Provider,告訴JAX-RS,這是一個可配置的JAX-RS組件;另外,必須添加@Produces,以告訴JAX-RS,這個MessageBodyWriter支持哪些交換類型。

 

JAX-RS按照以後算法查找一個合適的MessageBodyWriter來輸出一個對象:

  1. 首先查看@Produces,以確定是否是支持的交換數據類型
  2. 其他查找最優的匹配,例如對於 application/xml,找到三種可用的(application/*,*/*, application/xml),則 application/xml爲最優
  3. 最後,得到可用列表以後,就會順序調用MessageBodyWriter.isWriteable()。如果成功,剛輸出,否則嘗試下一個。

對於getSize()方法,如果不能確定大小,則直接返回-1即可。

 

增加縮進

 

默認情況下,所有的空白和特殊字符都被去除。輸出內容都在一行上。如果希望以格式化輸出,則可以增加@Pretty可以保留縮進,例如:

Java代碼  收藏代碼
  1. @GET  
  2. @Path("{id}")  
  3. @Produces("application/xml")  
  4. @Pretty  
  5. public Customer getCustomer(@PathParam("id"int id) {...}  

 

所以在自定義的Marsheller中需要處理這個annotation:

Java代碼  收藏代碼
  1. public void writeTo(Object target,  
  2.     Class<?> type,  
  3.     Type genericType,  
  4.     Annotation[] annotations,  
  5.     MediaType mediaType,  
  6.     MultivaluedMap<String, Object> httpHeaders,  
  7.     OutputStream outputStream) throws IOException  
  8.     {  
  9.   
  10.         try {  
  11.             JAXBContext ctx = JAXBContext.newInstance(type);  
  12.             Marshaller m = ctx.createMarshaller();  
  13.             boolean pretty = false;  
  14.             for (Annotation ann : annotations) {  
  15.                 if (ann.annotationType().equals(Pretty.class)) {  
  16.                     pretty = true;  
  17.                     break;  
  18.                 }  
  19.             }  
  20.   
  21.             if (pretty) {  
  22.                 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);  
  23.             }  
  24.   
  25.             m.marshal(target, outputStream);  
  26.         } catch (JAXBException ex) {  
  27.             throw new RuntimeException(ex);  
  28.         }  
  29. }  

 

插入JAXBContext

 

我們已經瞭解了怎麼插入一個JAXBContext,這裏我們需要知道怎麼把它加入到自定義的Marshaller.

 

我們需要找到一種方法,定位到一個ContextResoler類,這個類提供了JAXBContext對象,這個是通過javax.ws.rs.ext.Providers接口實現的:

Java代碼  收藏代碼
  1. public interface Providers {  
  2.     <T> ContextResolver<T> getContextResolver(Class<T> contextType,  
  3.         MediaType mediaType);  
  4.   
  5.     <T> MessageBodyReader<T>  
  6.         getMessageBodyReader(Class<T> type, Type genericType,  
  7.             Annotation annotations[], MediaType mediaType);  
  8.   
  9.     <T> MessageBodyWriter<T>  
  10.         getMessageBodyWriter(Class<T> type, Type genericType,  
  11.             Annotation annotations[], MediaType mediaType);  
  12.   
  13.     <T extends Throwable> ExceptionMapper<T>  
  14.         getExceptionMapper(Class<T> type);  
  15.   
  16. }  

 使用Providers.getContextResolver()得到ContextResolver對象。所以我們需要在Marshaller裏注入一個Providers對象:

 

Java代碼  收藏代碼
  1. @Context  
  2. protected Providers providers;  
  3. public void writeTo(Object target,  
  4.     Class<?> type,  
  5.     Type genericType,  
  6.     Annotation[] annotations,  
  7.     MediaType mediaType,  
  8.     MultivaluedMap<String, Object> httpHeaders,  
  9.     OutputStream outputStream) throws IOException  
  10.     {  
  11.         try {  
  12.             JAXBContext ctx = null;  
  13.             ContextResolver<JAXBContext> resolver =  
  14.                 providers.getContextResolver(JAXBContext.class, mediaType);  
  15.             if (resolver != null) {  
  16.                 ctx = resolver.getContext(type);  
  17.             }  
  18.             if (ctx == null) {  
  19.                 // create one ourselves  
  20.                 ctx = JAXBContext.newInstance(type);  
  21.             }  
  22.             ctx.createMarshaller().marshal(target, outputStream);  
  23.         } catch (JAXBException ex) {  
  24.             throw new RuntimeException(ex);  
  25.         }  
  26. }  

這樣就完成了Marshaller。

 

* MessageBodyReader

 

要自定義unmarshall,則需要用到javax.ws.rs.ext.MessageBodyReader接口了:

Java代碼  收藏代碼
  1. public interface MessageBodyReader<T> {  
  2.   
  3. boolean isReadable(Class<?> type, Type genericType,  
  4.         Annotation annotations[], MediaType mediaType);  
  5.   
  6. T readFrom(Class<T> type, Type genericType,  
  7.         Annotation annotations[], MediaType mediaType,  
  8.         MultivaluedMap<String, String> httpHeaders,  
  9.         InputStream entityStream)  
  10.             throws IOException, WebApplicationException;  
  11. }  

它和上面的Marshaller自定義過程非常類似。不細說:

Java代碼  收藏代碼
  1. @Provider  
  2. @Consumes("application/xml")  
  3. public class JAXBUnmarshaller implements MessageBodyReader {  
  4.   
  5.     public boolean isReadable(Class<?> type, Type genericType,  
  6.         Annotation annotations[], MediaType mediaType) {  
  7.         return type.isAnnotationPresent(XmlRootElement.class);  
  8.     }  
  9.     。。。  
  10. }  

注:這裏使用了@Provider和@Consumes annotation。@Provider同上;@Consumes用於指定支持的交換格式。  

 

讀如下:

Java代碼  收藏代碼
  1. Object readFrom(Class<Object>, Type genericType,  
  2.     Annotation annotations[], MediaType mediaType,  
  3.     MultivaluedMap<String, String> httpHeaders,  
  4.     InputStream entityStream)  
  5.         throws IOException, WebApplicationException {  
  6.   
  7.     try {  
  8.         JAXBContext ctx = JAXBContext.newInstance(type);  
  9.         return ctx.createUnmarshaller().unmarshal(outputStream);  
  10.     } catch (JAXBException ex) {  
  11.         throw new RuntimeException(ex);  
  12.   
  13.     }  
  14.   
  15. }  

 

五、生命週期與環境  

 

默認情況下,每個應用只創建一個MessageBodyReader、MessageBodyWriter和ContextResolver。如果想實例化多個對象,剛需要提供一個public的構造方法,以便JAX-RSS運行時傳入所有需要的參數值,可能只需要包含一個以@Context註釋的參數即可,例如:

 

Java代碼  收藏代碼
  1. @Provider  
  2. @Consumes("application/json")  
  3. public class MyJsonReader implements MessageBodyReader {  
  4.   
  5.     public MyJsonReader(@Context Providers providers) {  
  6.         this.providers = providers;  
  7.     }  
  8.   
  9. }  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章