天天看點

Java RESTful Web Service實戰(第2版) 2.3 傳輸格式

<b>2.3 傳輸格式</b>

本節要考慮的就是如何設計表述,即傳輸過程中資料采用什麼樣的資料格式。通常,rest接口會以xml和json作為主要的傳輸格式,這兩種格式資料的處理是本節的重點。那麼jersey是否還支援其他的資料格式呢?答案是肯定的,讓我們逐一掌握各種類型的實作。

<b>2.3.1 基本類型</b>

java的基本類型又叫原生類型,包括4種整型(byte、short、int、long)、2種浮點類型(float、double)、unicode編碼的字元(char)和布爾類型(boolean)。

閱讀指南

本節的前4小節示例源代碼位址:https://github.com/feuyeux/jax-rs2-guide-ii/tree/master/2.simple-service-3。

相關包:com.example.response。

jersey支援全部的基本類型,還支援與之相關的引用類型。前述示例已經呈現了整型(int)等java的基本類型的參數,本例展示位元組數組類型作為請求實體類型、字元串作為響應實體類型的示例,示例代碼如下。

@post

@path("b")

public string postbytes(final byte[] bs)

{//關注點1:測試方法入參

for (final byte b : bs) {

logger.debug(b);

    }

return "byte[]:" + new string(bs);

}

@test

public void testbytes() {

final string message = "test string";

final builder request = target(path).path("b").request();

final response response = request.post(

entity.entity(message,

mediatype.text_plain_type), response.class);

result = response.readentity(string.class);

//關注點2:測試斷言

assert.assertequals("byte[]:" + message, result);

在這段代碼中,資源方法postbytes()的輸入參數是byte[]類型,輸出參數是string類型,見關注點1;單元測試方法testbytes()的斷言是對字元串"test string"的驗證,見關注點2。

2.3.2 檔案類型

jersey支援傳輸file類型的資料,以友善用戶端直接傳遞file類執行個體給伺服器端。檔案類型的請求,預設使用的媒體類型是content-type: text/html,示例代碼如下。

@path("f")

//關注點1:測試方法入參

public file postfile(final file f) throws

filenotfoundexception, ioexception {

//關注點2:try-with-resources

try (bufferedreader br = new bufferedreader(new filereader(f))) {

string s;

do {

s = br.readline();

logger.debug(s);

        } while (s != null);

return f;

public void testfile() throws

//關注點3:擷取檔案全路徑

final url resource =

getclass().getclassloader().getresource("gua.txt");

//關注點4:建構file執行個體

final string file = resource.getfile();

final file f = new file(file);

final builder request = target(path).path("f").request();

//關注點5:送出post請求

entity&lt;file&gt; e = entity.entity(f, mediatype.text_plain_type);

final response response = request.post(e, response.class);

file result = response.readentity(file.class);

try (bufferedreader br = new bufferedreader(new filereader(result))) {

s = br.readline();//關注點6:逐行讀取檔案

} while (s != null);

在這段代碼中,資源方法postfile()的輸入參數類型和傳回值類型都是file類型,見關注點1;伺服器端對file執行個體進行解析,最後将該資源釋放,即try-with-resources,見關注點2;在測試方法testfile()中,建構了file類型的"gua.txt"檔案的執行個體,見關注點3;作為請求實體送出,見關注點4;并對響應實體進行逐行讀取的校驗,見關注點5;需要注意的是,由于我們使用的是maven建構的項目,測試檔案位于測試目錄的resources目錄,其相對路徑為/simple-service-3/src/test/resources/gua.txt,擷取該檔案的語句為getclass().getclassloader().getresource("gua.txt"),見關注點6。

另外,檔案的資源釋放使用了jdk7的try-with-resources文法,見關注點2。

2.3.3 inputstream類型

jersey支援java的兩大讀寫模式,即位元組流和字元流。本示例展示位元組流作為rest方法參數,示例如下。

@path("bio")

//關注點1:資源方法入參

public string poststream(final inputstream

is) throws filenotfoundexception, ioexception {

try (bufferedreader br = new bufferedreader(new inputstreamreader(is)))

{

stringbuilder result = new stringbuilder();

string s = br.readline();

while (s != null) {

result.append(s).append("\n");

return result.tostring();//關注點3:資源方法傳回值

public void teststream() {

//關注點4:擷取檔案全路徑

final inputstream resource =

getclass().getclassloader().getresourceasstream("gua.txt");

final builder request = target(path).path("bio").request();

entity&lt;inputstream&gt; e = entity.entity(resource,

mediatype.text_plain_type);

//關注點5:輸出傳回值内容

logger.debug(result);

在這段代碼中,資源方法poststream()的輸入參數類型是inputstream,見關注點1;伺服器端從中讀取位元組流,并最終釋放該資源,見關注點2;傳回值是string類型,内容是位元組流資訊,見關注點3;測試方法teststream()建構了"gua.txt"檔案内容的位元組流,作為請求實體送出,見關注點4;響應實體預期為string類型的"gua.txt"檔案内容資訊,見關注點5。

2.3.4 reader類型

本示例展示另一種java讀寫模式,以字元流作為rest方法參數,示例如下。

@path("cio")

public string postchars(final reader r)

throws filenotfoundexception, ioexception {

try (bufferedreader br = new bufferedreader(r)) {

if (s == null) {

throw new jaxrs2guidenotfoundexception("not found from

reader");

return "reader";

public void testreader() {

//關注點3:建構并送出reader執行個體

classloader classloader = getclass().getclassloader();

final reader resource =

new

inputstreamreader(classloader.getresourceasstream("gua.txt"));

final builder request = target(path).path("cio").request();

entity&lt;reader&gt; e = entity.entity(resource,

//關注點4:輸出傳回值内容

在這段代碼中,資源方法postchars()的輸入參數類型是reader,見關注點1;伺服器端從中讀取字元流,并最終釋放該資源,傳回值是string類型,見關注點2;測試方法testreader()建構了"gua.txt"檔案内容的reader執行個體,将字元流作為請求實體送出,見關注點3;響應實體預期為string類型的"gua.txt"檔案内容資訊,見關注點4。

2.3.5 xml類型

xml類型是使用最廣泛的資料類型。jersey對xml類型的資料處理,支援java領域的兩大标準,即jaxp(java api for

xml processing,jsr-206)和jaxb(java architecture for xml binding,jsr-222)。

本節示例源代碼位址:https://github.com/feuyeux/jax-rs2-guide-ii/tree/master/2.simple-service-3。

相關包:com.example.media.xml。

1. jaxp标準

jaxp包含了dom、sax和stax 3種解析xml的技術标準。

dom是面向文檔解析的技術,要求将xml資料全部加載到記憶體,映射為樹和結點模型以實作解析。

sax是事件驅動的流解析技術,通過監聽注冊事件,觸發回調方法以實作解析。

stax是拉式流解析技術,相對于sax的事件驅動推送技術,拉式解析使得讀取過程可以主動推進目前xml位置的指針而不是被動獲得解析中的xml資料。

對應的,jaxp定義了3種标準類型的輸入接口source(domsource,saxsource,streamsource)和輸出接口result(domresult,saxresult,streamresult)。jersey可以使用jaxp的輸入類型作為rest方法的參數,示例代碼如下。

@path("stream")

@consumes(mediatype.application_xml)

@produces(mediatype.application_xml)

public streamsource getstreamsource(

javax.xml.transform.stream.streamsource

streamsource) {

return streamsource;

@path("sax")

//關注點2:支援sax技術

public saxsource

getsaxsource(javax.xml.transform.sax.saxsource saxsource) {

return saxsource;

@path("dom")

//關注點3:支援dom技術

public domsource

getdomsource(javax.xml.transform.dom.domsource domsource) {

return domsource;

@path("doc")

//關注點4:支援dom技術

public document

getdocument(org.w3c.dom.document document) {

return document;

在這段代碼中,資源方法getstreamsource()使用stax拉式流解析技術支援輸入輸出類型為streamsource的請求,見關注點1;getsaxsource()方法使用sax是事件驅動的流解析技術支援輸入輸出類型為saxsource的請求,見關注點2;getdomsource()方法和getdocument()方法使用dom面向文檔解析的技術,支援輸入輸出類型分别為domsource和document的請求,見關注點3和關注點4。

2. jaxb标準

jaxp的缺點是需要編碼解析xml,這增加了開發成本,但對業務邏輯的實作并沒有實質的貢獻。jaxb隻需要在pojo中定義相關的注解(早期人們使用xml配置檔案來做這件事),使其和xml的schema對應,無須對xml進行程式式解析,彌補了jaxp的這一缺點,是以本書推薦使用jaxb作為xml解析的技術。

jaxb通過序列化和反序列化實作了xml資料和pojo對象的自動轉換過程。在運作時,jaxb通過編組(marshall)過程将pojo序列化成xml格式的資料,通過解編(unmarshall)過程将xml格式的資料反序列化為java對象。jaxb的注解位于javax.xml.bind.annotation包中,詳情可以通路jaxb的參考實作網址是https://jaxb.java.net/tutorial。

需要指出的是,從理論上講,jaxb解析xml的性能不如jaxp,但使用jaxb的開發效率很高。筆者所在的開發團隊使用jaxb解析xml,從實踐體會而言,筆者并不支援jaxb影響系統運作性能這樣的觀點。因為計算機執行的瓶頸在io,而無論使用哪種技術解析,xml資料本身是一樣的,差別僅在于解析手段。而rest風格以及靈活思想的宗旨就是簡單—開發過程簡單化、執行邏輯簡單化,是以如果連xml資料都趨于簡單,jaxp帶來的性能優勢就可以忽略不計了。綜合考量,實作起來更簡單的jaxb更适合做rest開發。

jersey支援使用jaxbelement作為rest方法參數的形式,也支援直接使用pojo作為rest方法參數的形式,後一種更為常用,示例代碼如下。

@path("jaxb")

public book

getentity(jaxbelement&lt;book&gt; bookelement) {

book book = bookelement.getvalue();

logger.debug(book.getbookname());

return book;

@consumes({ mediatype.application_xml,

mediatype.application_json })

public book getentity(book book) {

以上jaxp和jaxb的測試如下所示,其傳輸内容是相同的,不同在于伺服器端的rest方法定義的解析類型和傳回值類型。

1 &gt; content-type: application/xml

&lt;?xml version="1.0"

encoding="utf-8" standalone="yes"?&gt;&lt;book

bookid="100" bookname="test book"/&gt;

2 &lt; content-length: 79

2 &lt; content-type: text/html

encoding="utf-8"?&gt;&lt;book bookid="100"

bookname="test book"/&gt;

從測試結果可以看到,pojo類的字段是作為xml的屬性組織起來的,詳見如下的圖書實體類定義。

@xmlrootelement

public class book implements serializable {

//關注點1:jaxb屬性注解

@xmlattribute(name = "bookid")

public long getbookid() {

return bookid;

@xmlattribute(name = "bookname")

public string getbookname() {

return bookname;

@xmlattribute(name = "publisher")

public string getpublisher() {

return publisher;

(1)property和element

本例的pojo類book的字段都定義為xml的屬性(property)來組織,pojo的字段也可以作為元素(element)組織,見關注點1。如何定義通常取決于對接系統的設計。需要注意的是,如果rest請求的傳輸資料量很大,并且無須和外系統對接的場景,建議使用屬性來組織xml,這樣可以極大地減少xml格式的資料包的大小。

(2)xml_security_disable

jersey預設設定了xmlconstants.feature_secure_processing(http://javax.xml.xml

constants/feature/secure-processing)屬性,當屬性或者元素過多時,會報“well-formedness

error”這樣的警告資訊。如果業務邏輯确實需要設計一個繁瑣的pojo,可以通過設定messageproperties.xml_security_disable參數值為true來屏蔽。伺服器端和用戶端,示例代碼如下。

@applicationpath("/*")

public class xxxresourceconfig extends

resourceconfig {

public xxxresourceconfig() {

packages("xxx.yyy.zzz");

property(messageproperties.xml_security_disable, boolean.true);

clientconfig config = new clientconfig();

config.property(messageproperties.xml_security_disable,

boolean.true);

2.3.6 json類型

json類型已經成為ajax技術中資料傳輸的實際标準。jersey提供了4種處理json資料的媒體包。表2-6展示了4種技術對3種解析流派(基于pojo的json綁定、基于jaxb的json綁定以及低級的(逐字的)json解析和處理)的支援情況。moxy和jackon的處理方式相同,它們都不支援以json對象方式解析json資料,而是以綁定方式解析。jettison支援以json對象方式解析json資料,同時支援jaxb方式的綁定。json-p就隻支援json對象方式解析這種方式了。

表2-6 jersey對json的處理方式清單

解析方式\json支援包  moxy       json-p     jackson    jettison

pojo-based json binding        是     否     是     否

jaxb-based json binding        是     否     是     是

low-level json parsing &amp; processing      否     是     否     是

下面将介紹moxy、son-p、jackson和jettison這4種jersey支援的json處理技術在rest式的web服務開發中的使用。

1. 使用moxy處理json

moxy是eclipselink項目的一個子產品,其官方網站http://www.eclipse.org/eclipselink/moxy.php宣稱eclipselink的moxy元件是使用jaxb和sdo作為xml綁定的技術基礎。moxy實作了jsr 222标準(jaxb2.2)和jsr 235标準(sdo2.1.1),這使得使用moxy的java開發者能夠高效地完成java類和xml的綁定,所要花費的隻是使用注解來定義它們之間的對應關系。同時,moxy實作了jsr-353标準(java api for processing json1.0),以jaxb為基礎來實作對jsr353的支援。下面開始講述使用moxy實作在rest應用中解析json的完整過程。

2.3.6節的moxy示例源代碼位址:https://github.com/feuyeux/jax-rs2-guide-ii/tree/master/2.3.6-1.simple-service-moxy。

(1)定義依賴

moxy是jersey預設的json解析方式,可以在項目中添加moxy的依賴包來使用moxy。

&lt;dependency&gt;

&lt;groupid&gt;org.glassfish.jersey.media&lt;/groupid&gt;

&lt;artifactid&gt;jersey-media-moxy&lt;/artifactid&gt;

&lt;/dependency&gt;

(2)定義application

使用servlet3可以不定義web.xml配置,否則請參考1.6節的講述。

moxy的feature接口實作類是moxyjsonfeature,預設情況下,jersey對其自動探測,無須在applicaion類或其子類顯式注冊該類。如果不希望jersey這種預設行為,可以通過設定如下屬性來禁用自動探測:commonproperties.moxy_json_feature_disable兩端禁用,serverproperties.moxy_json_feature_disable伺服器端禁用,clientproperties.moxy_json_feature_disable用戶端禁用。

@applicationpath("/api/*")

public class jsonresourceconfig extends

public jsonresourceconfig() {

register(bookresource.class);

//property(org.glassfish.jersey.commonproperties.moxy_json_feature_disable,

true);

(3)定義資源類

接下來,我們定義一個圖書資源類bookresource,并在其中實作表述媒體類型為json的資源方法getbooks()。支援json格式的表述的資源類定義如下。

@path("books")

//關注點1:@produces注解和@consumes注解上移到接口

@consumes(mediatype.application_json)

@produces(mediatype.application_json)

public class bookresource {

private static final hashmap&lt;long, book&gt; memorybase;

...

@get

//關注點2:實作類方法無需再定義@produces注解和@consumes注解

public books getbooks() {

final list&lt;book&gt; booklist = new arraylist&lt;&gt;();

final set&lt;map.entry&lt;long, book&gt;&gt; entries = bookresource.memorybase.entryset();

final iterator&lt;entry&lt;long, book&gt;&gt; iterator =

entries.iterator();

while (iterator.hasnext()) {

final entry&lt;long, book&gt; cursor = iterator.next();

bookresource.logger.debug(cursor.getkey());

            booklist.add(cursor.getvalue());

final books books = new books(booklist);

bookresource.logger.debug(books);

return books;

在這段代碼中,資源類bookresource定義了@consumes(mediatype.application_json)和@produces(mediatype.application_json),表示該類的所有資源方法都使用mediatype.application_json類型作為請求和響應的資料類型,見關注點1;是以,getbooks()方法上無須再定義@consumes和@produces,見關注點2。

如果rest應用處于多語言環境中,不要忘記統一開放接口的字元編碼;如果統一開放接口同時供前端jsonp使用,不要忘記添加相關媒體類型,示例如下。

@produces({"application/x-javascript;charset=utf-8",

"application/json;charset=utf-8"})

在這段代碼中,rest api将支援jsonp、json,并且統一字元編碼為utf-8。

(4)單元測試

json處理的單元測試主要關注請求的響應中json資料的可用性、完整性和一緻性。在本章使用的單元測試中,驗證json處理無誤的标準是測試的傳回值是一個java類型的實體類執行個體,整個請求處理過程中沒有異常發生,測試代碼如下。

public class jsontest extends jerseytest {

private final static logger logger = logger.getlogger(jsontest.class);

@override

protected application configure() {

enable(testproperties.log_traffic);

enable(testproperties.dump_entity);

return new resourceconfig(bookresource.class);

public void testgettingbooks() {

//關注點1:在請求中定義媒體類型為json

books books =

target("books").request(mediatype.application_json_type).

get(books.class);

        for (book book : books.getbooklist()) {

在這段代碼中,測試方法testgettingbooks()定義了請求資源的資料類型為mediatype.application_json_type來比對伺服器端提供的rest api,其作用是定義請求的媒體類型為json格式的,見關注點1。

(5)內建測試

除了單元測試,我們使用curl來做內建測試。首先啟動本示例,然後輸入如下所示的指令。

curl

http://localhost:8080/simple-service-moxy/api/books

curl -h "content-type:

application/json" http://localhost:8080/simple-service-moxy/api/books

傳回json格式的資料如下。

{"booklist":{"book":[{"bookid":1,"bookname":"jsf2和richfaces4使用指南","publisher":"電子工業出版社","isbn":"9787121177378","publishtime":"2012-09-01"},{"bookid":2,"bookname":"java

restful web services實戰","publisher":"機械工業出版社","isbn":"9787111478881","publishtime":"2014-09-01"},{"bookid":3,"bookname":"java

ee 7 精髓","publisher":"人民郵電出版社","isbn":"9787115375483","publishtime":"2015-02-01"},{"bookid":4,"bookname":"java

restful web services實戰ii","publisher":"機械工業出版社"}]}}

2. 使用json-p處理json

json-p的全稱是 java api for

json processing(java的json處理api),而不是json with padding(jsonp),兩者隻是名稱相仿,用途大相徑庭。json-p是jsr 353标準規範,用于統一java處理json格式資料的api,其生産和消費的json資料以流的形式,類似stax處理xml,并為json資料建立java對象模型,類似dom。而jsonp是用于異步請求中傳遞腳本的回調函數來解決跨域問題。下面開始講述使用json-p實作在rest應用中解析json的完整過程。

2.3.6節的json-p示例源代碼位址:https://github.com/feuyeux/jax-rs2-guide-ii/tree/master/2.3.6-2.simple-service-jsonp。

使用json-p方式處理json類型的資料,需要在項目的maven配置中聲明如下依賴。

&lt;artifactid&gt;jersey-media-json-processing&lt;/artifactid&gt;

使用json-p的應用,預設不需要在其application中注冊jsonprocessingfeature,除非使用了如下設定。依次用于在伺服器和用戶端兩側去活json-p功能、在伺服器端去活json-p功能、在用戶端去活json-p功能。

commonproperties.json_processing_feature_disable

serverproperties.json_processing_feature_disable

clientproperties.json_processing_feature_disable

jsongenerator.pretty_printing屬性用于格式化json資料的輸出,當屬性值為true時,mesagebodyreader和messagebodywriter執行個體會對json資料進行額外處理,使得json資料可以格式化列印。該屬性的設定在application中,見關注點1,示例代碼如下。

//關注點1:配置json格式化輸出

property(jsongenerator.pretty_printing, true);

資源類bookresource同上例一樣定義了類級别的@consumes和@produces,媒體格式為mediatype.application_json,資源類bookresource的示例代碼如下。

static {

memorybase = com.google.common.collect.maps.newhashmap();

//關注點1:建構jsonobjectbuilder執行個體

jsonobjectbuilder jsonobjectbuilder = json.createobjectbuilder();

//關注點2:建構jsonobject執行個體

jsonobject newbook1 = jsonobjectbuilder.add("bookid", 1)

.add("bookname", "java restful web services實戰")

.add("publisher", "機械工業出版社")

            .add("isbn",

"9787111478881")

.add("publishtime", "2014-09-01")

.build();

public jsonarray getbooks() {

//關注點3:建構jsonarraybuilder執行個體

final jsonarraybuilder arraybuilder = json.createarraybuilder();

final set&lt;map.entry&lt;long, jsonobject&gt;&gt; entries =

bookresource.memorybase.entryset();

final iterator&lt;entry&lt;long, jsonobject&gt;&gt; iterator =

//關注點4:建構jsonarray執行個體

jsonarray result = arraybuilder.build();

return result;

在這段代碼中,jsonobjectbuilder用于構造json對象,見關注點1;jsonarraybuilder用于構造json數組對象,見關注點2;jsonobject是json-p定義的json對象類,見關注點3;jsonarray是json數組類,見關注點4。

json-p示例的單元測試需要關注json-p定義的json類型,測試驗收标準在前一小節moxy的單元測試中已經講述,示例代碼如下。

//關注點1:請求的響應類型為jsonarray

jsonarray books = target("books").request(mediatype.application_json_type).

get(jsonarray.class);

for (jsonvalue jsonvalue : books) {

//關注點2:強轉jsonvalue為jsonobject

jsonobject book = (jsonobject) jsonvalue;

logger.debug(book.getstring("bookname"));//關注點3:列印輸出測試結果

  }

在這段代碼片段中,jsonarray是getbooks()方法的傳回類型,get()請求發出後,伺服器端對應的方法是getbooks()方法,見關注點1;jsonvalue類型是一種抽象化的json資料類型,此處類型強制轉化為jsonobject,見關注點2;getstring()方法是将jsonobject對象的某個字段以字元串類型傳回,見關注點3。

使用curl對本示例進行內建測試的結果如下所示,json資料結果可以格式化列印輸出。

http://localhost:8080/simple-service-jsonp/api/books

[

    {

"bookid":1,

"bookname":"java restful web services實戰",

"publisher":"機械工業出版社",

"isbn":"9787111478881",

"publishtime":"2014-09-01"

},

 "bookid":2,

"bookname":"jsf2和richfaces4使用指南",

"publisher":"電子工業出版社",

"isbn":"9787121177378",

"publishtime":"2012-09-01"

"bookid":3,

"bookname":"java ee 7精髓",

"publisher":"人民郵電出版社",

        "isbn":"9787115375483",

"publishtime":"2015-02-01"

"bookid":4,

"bookname":"java restful web services實戰ii",

"publisher":"機械工業出版社"

]

http://localhost:8080/simple-service-jsonp/api/books/book?id=1

application/json" -x post \

-d

"{\"bookname\":\"abc\",\"publisher\":\"me\"}"

\

"bookid":23670621181527,

"bookname":"abc",

"publisher":"me"

3.使用jackson處理json

jackson是一種流行的json支援技術,其源代碼托管于github,位址是:https://github.com/fasterxml/jackson。jackson提供了3種json解析方式。

第一種是基于流式api的增量式解析/生成json的方式,讀寫json内容的過程是通過離散事件觸發的,其底層基于stax api讀取json使用org.codehaus.jackson.jsonparser,寫入json使用org.codehaus.jackson.jsongenerator。

第二種是基于樹型結構的記憶體模型,提供一種不變式的jsonnode記憶體樹模型,類似dom樹。

第三種是基于資料綁定的方式,org.codehaus.jackson.map.objectmapper解析,使用jaxb的注解。

下面開始講述使用jackson實作在rest應用中解析json的完整過程。

2.3.6節的jackson示例源代碼位址:https://github.com/feuyeux/jax-rs2-guide-ii/tree/master/2.3.6-3.simple-service-jackson。

使用jackson方式處理json類型的資料,需要在項目的maven配置中聲明如下依賴。

&lt;artifactid&gt;jersey-media-json-jackson&lt;/artifactid&gt;

使用jackson的應用,需要在其application中注冊jacksonfeature。同時,如果有必要根據不同的實體類做詳細的解析,可以注冊contextresolver的實作類,示例代碼如下。

register(jacksonfeature.class);

//關注點1:注冊contextresolver的實作類jsoncontextprovider

register(jsoncontextprovider.class);

在這段代碼中,注冊了contextresolver的實作類jsoncontextprovider,用于提供json資料的上下文,見關注點1。有關contextresolver詳細資訊參考3.2節。

(3)定義pojo

本例定義了3種不同方式的pojo,以示範jackson處理json的多種方式。分别是jsonbook、jsonhybridbook和jsonnojaxbbook。第一種方式是僅用jaxb注解的普通的pojo,示例類jsonbook如下。

@xmltype(proporder = {"bookid",

"bookname", "chapters"})

public class jsonbook {

private string[] chapters;

private string bookid;

private string bookname;

public jsonbook() {

bookid = "1";

bookname = "java restful web services實戰";

chapters = new string[0];

第二種方式是将jaxb的注解和jackson提供的注解混合使用的pojo,示例類jsonhybridbook如下。

//關注點1:使用jaxb注解

public class jsonhybridbook {

//關注點2:使用jackson注解

@jsonproperty("bookid")

@jsonproperty("bookname")

public jsonhybridbook() {

bookid = "2";

在這段代碼中,分别使用了jaxb的注解javax.xml.bind.annotation.xmlrootelement,見關注點1,和jackson的注解org.codehaus.jackson.annotate.jsonproperty,見關注點2,定義xml根元素和xml屬性。

第三種方式是不使用任何注解的pojo,示例類jsonnojaxbbook如下。

public class jsonnojaxbbook {

public jsonnojaxbbook() {

bookid = "3";

bookname = "java restful web services使用指南";

這樣的3種pojo如何使用jackson處理來處理呢?我們繼續往下看。

(4)定義資源類

資源類bookresource用于示範jackson對上述3種不同pojo的支援,示例代碼如下。

@path("/emptybook")

//關注點1:支援第一種方式的pojo類型

public jsonbook getemptyarraybook() {

return new jsonbook();

@path("/hybirdbook")

//關注點2:支援第二種方式的pojo類型

public jsonhybridbook gethybirdbook() {

return new jsonhybridbook();

@path("/nojaxbbook")

//關注點3:支援第三種方式的pojo類型

public jsonnojaxbbook getnojaxbbook() {

return new jsonnojaxbbook();

……

在這段代碼中,資源類bookresource定義了路徑不同的3個get方法,傳回類型分别對應上述的3種pojo,見關注點1到3。有了這樣的資源類,就可以向其發送get請求,并擷取不同類型的json資料,以研究jackson是如何支援這3種pojo的json轉換。

(5)上下文解析實作類

jsoncontextprovider是contextresolver(上下文解析器)的實作類,其作用是根據上下文提供的pojo類型,分别提供兩種解析方式。第一種是預設的方式,第二種是混合使用jackson和jaxb。兩種解析方式的示例代碼如下。

@provider

public class jsoncontextprovider implements

contextresolver&lt;objectmapper&gt; {

final objectmapper d;

final objectmapper c;

public jsoncontextprovider() {

//關注點1:執行個體化objectmapper

d = createdefaultmapper();

        c = createcombinedmapper();

private static objectmapper createcombinedmapper() {

pair ps = createintrospector();

objectmapper result = new objectmapper();

result.setdeserializationconfig(

result.getdeserializationconfig().withannotationintrospector(ps));

result.setserializationconfig(

result.getserializationconfig().withannotationintrospector(ps));

private static objectmapper createdefaultmapper() {

result.configure(feature.indent_output, true);

private static pair createintrospector() {

annotationintrospector p = new jacksonannotationintrospector();

annotationintrospector s = new jaxbannotationintrospector();

return new pair(p, s);

@override    public objectmapper

getcontext(class&lt;\?&gt; type) {

//關注點2:判斷pojo類型傳回相應的objectmapper執行個體

if (type == jsonhybridbook.class) {

            return c;

} else {

return d;

在這段代碼中,jsoncontextprovider定義并執行個體化了兩種類型objectmapper,見關注點1;在實作接口方法getcontext()中,通過判斷目前pojo的類型,傳回兩種objectmapper執行個體之一,見關注點2。通過這樣的實作,當流程擷取json上下文時,既可使用jackson依賴包完成對相關pojo的處理。

(6)單元測試

單元測試類bookresourcetest的目的是對支援上述3種pojo的資源位址發起請求并測試結果,示例如下。

public class bookresourcetest extends

jerseytest {

private static final logger logger =

logger.getlogger(bookresourcetest.class);

webtarget bookstarget = target("books");

protected resourceconfig configure() {

//關注點1:伺服器端配置

resourceconfig resourceconfig = new resourceconfig(bookresource.class);

//關注點2:注冊jacksonfeature

resourceconfig.register(jacksonfeature.class);

return resourceconfig;

protected void configureclient(clientconfig config) {

//關注點3:注冊jacksonfeature

config.register(new jacksonfeature());

config.register(jsoncontextprovider.class);

//關注點4:測試出參為jsonbook類型的資源方法

public void testemptyarray() {

jsonbook book =

bookstarget.path("emptybook").request(mediatype.application_json).get(jsonbook.class);

logger.debug(book);

//關注點5:測試出參為jsonhybridbook類型的資源方法

public void testhybrid() {

jsonhybridbook book =

bookstarget.path("hybirdbook").request(mediatype

.application_json).get(jsonhybridbook.class);

//關注點6:測試出參為jsonnojaxbbook類型的資源方法

public void testnojaxb() {

jsonnojaxbbook book =

bookstarget.path("nojaxbbook").request(mediatype.

application_json).get(jsonnojaxbbook.class);

在這段代碼中,首先要在伺服器端注冊支援jackson功能,見關注點2;同時在用戶端也要注冊支援jackson功能并注冊jsoncontextprovider,見關注點3;該測試類包含了用于測試3種類型pojo的測試用例,見關注點4到6;注意,configure()方法是覆寫測試伺服器執行個體行為,configureclient()方法是覆寫測試用戶端執行個體行為,見關注點1。

(7)內建測試

使用curl對本例進行內建測試,結果如下所示。

curl http://localhost:8080/simple-service-jackson/api/books

"booklist" : [ {

"bookid" : 1,

"bookname" : "jsf2和richfaces4使用指南",

"isbn" : "9787121177378",

"publisher" : "電子工業出版社",

"publishtime" : "2012-09-01"

  },

"bookid" : 2,

"bookname" : "java restful web services實戰",

"isbn" : "9787111478881",

"publisher" : "機械工業出版社",

"publishtime" : "2014-09-01"

"bookid" : 3,

"bookname" : "java ee 7 精髓",

"isbn" : "9787115375483",

"publisher" : "人民郵電出版社",

"publishtime" : "2015-02-01"

"bookid" : 4,

"bookname" : "java restful web services實戰ii",

"isbn" : null,

"publishtime" : null

  } ]

http://localhost:8080/simple-service-jackson/api/books/emptybook

"chapters" : [ ],

"bookid" : "1",

"bookname" : "java restful web services實戰"

http://localhost:8080/simple-service-jackson/api/books/hybirdbook

{"jsonhybridbook":{"bookid":"2","bookname":"java

restful web services實戰"}}

curl http://localhost:8080/simple-service-jackson/api/books/nojaxbbook

"bookid" : "3",

4. 使用jettison處理json

jettison是一種使用stax來解析json的實作。項目位址是:http://jettison.codehaus.org。jettison項目起初用于為cxf提供基于json的web服務,在xstream的java對象的序列化中也使用了jettison。jettison支援兩種json映射到xml的方式。jersey預設使用mapped方式,另一種叫做badgerfish方式。

下面開始講述使用jettison實作在rest應用中解析json的完整過程。

2.3.6節的jettison示例源代碼位址:https://github.com/feuyeux/jax-rs2-guide-ii/tree/master/2.3.6-4.simple-service-jettison。

使用jettison方式處理json類型的資料,需要在項目的maven配置中聲明如下依賴。

&lt;artifactid&gt;jersey-media-json-jettison&lt;/artifactid&gt;

使用jettison的應用,需要在其application中注冊jettisonfeature。同時,如果有必要根據不同的實體類做詳細的解析,可以注冊contextresolver的實作類,示例代碼如下。

//關注點1:注冊jettisonfeature和contextresolver的實作類jsoncontextresolver

register(jettisonfeature.class);

register(jsoncontextresolver.class);

在這段代碼中,注冊了jettison功能jettisonfeature和contextresolver的實作類jsoncontextresolver,以便使用jettison處理json,見關注點1。

本例定義了兩個類名不同、内容相同的pojo(jsonbook和jsonbook2),用以示範jettison對json資料以jettison_mapped(default notation)和badgerfish兩種不同方式的處理情況。

資源類bookresource為兩種json方式提供了資源位址,示例如下。

@path("/jsonbook")

//關注點1:傳回類型為jsonbook的get方法

public jsonbook getbook() {

final jsonbook book = new jsonbook();

bookresource.logger.debug(book);

@path("/jsonbook2")

//關注點2:傳回類型為jsonbook2的get方法

public jsonbook2 getbook2() {

final jsonbook2 book = new jsonbook2();

 }

在這段代碼中,資源類bookresource定義了路徑不同的兩個get方法,傳回類型分别是jsonbook和jsonbook2,見關注點1和2。有了這樣的資源類,就可以向其發送get請求,并擷取不同類型的json資料,以研究jettison是如何處理jettison_mapped和badgerfish兩種不同格式的json資料的。

jsoncontextresolver實作了contextresolver接口,示例如下。

public class jsoncontextresolver implements

contextresolver&lt;jaxbcontext&gt; {

private final jaxbcontext context1;

private final jaxbcontext context2;

@suppresswarnings("rawtypes")

public jsoncontextresolver() throws exception {

class[] clz = new class[]{jsonbook.class, jsonbook2.class, books.class,

book.class};

//關注點1:執行個體化jettisonjaxbcontext

this.context1 = new jettisonjaxbcontext(jettisonconfig.default, clz);

this.context2 = new

jettisonjaxbcontext(jettisonconfig.badgerfish().build(), clz);

public jaxbcontext getcontext(class&lt;\?&gt; objecttype) {

//關注點2:判斷pojo類型傳回相應的jaxbcontext執行個體

if (objecttype == jsonbook2.class) {

return context2;

return context1;

在這段代碼中,jsoncontextresolver定義了兩種jaxbcontext分别使用mapped方式或者badgerfish方式,見關注點1。這兩種方式的參數資訊來自jettision依賴包的jettisonconfig類。在實作接口方法getcontext()中,根據不同的pojo類型,傳回兩種jaxbcontext執行個體之一,見關注點2。通過這樣的實作,當流程擷取json上下文時,既可使用jettision依賴包完成對相關pojo的處理。

單元測試類bookresourcetest的目的是對支援上述兩種json方式的資源位址發起請求并測試結果,示例如下。

//關注點1:注冊jettisonfeature和jsoncontextresolver

resourceconfig.register(jettisonfeature.class);

resourceconfig.register(jsoncontextresolver.class);

//關注點2:注冊jettisonfeature和jsoncontextresolver

config.register(new

jettisonfeature()).register(jsoncontextresolver.class);

public void testjsonbook() {

//關注點3:測試傳回類型為jsonbook的get方法

jsonbook book = target("books").path("jsonbook")

.request(mediatype.application_json).get(jsonbook.class);

//{"jsonbook":{"bookid":1,"bookname":"abc"}}

public void testjsonbook2() {

//關注點4:測試傳回類型為jsonbook2的get方法

jsonbook2 book = target("books").path("jsonbook2")

.request(mediatype.application_json).get(jsonbook2.class);

//{"jsonbook2":{"bookid":{"$":"1"},"bookname":{"$":"abc"}}}

在這段代碼中,首先要在伺服器和用戶端兩側注冊jettison功能和jsoncontextresolver,見關注點1和2。該測試類包含了用于測試兩種格式json資料的測試用例,見關注點3和4。

使用curl對本例進行內建測試,結果如下所示。可以看到mapped和badgerfish兩種方式的json資料内容不同。

http://localhost:8080/simple-service-jettison/api/books

{"books":{"booklist":{"book":[{"@bookid":"1","@bookname":"jsf2和richfaces4使用指南","@publisher":"電子工業出版社","isbn":9787121177378,"publishtime":"2012-09-01"},{"@bookid":"2","@bookname":"java

restful web services實戰","@publisher":"機械工業出版社","isbn":9787111478881,"publishtime":"2014-09-01"},{"@bookid":"3","@bookname":"java

ee 7 精髓","@publisher":"人民郵電出版社","isbn":9787115375483,"publishtime":"2015-02-01"},{"@bookid":"4","@bookname":"java

restful web services實戰ii","@publisher":"機械工業出版社"}]}}}

jettison mapped notation

http://localhost:8080/simple-service-jettison/api/books/jsonbook

{"jsonbook":{"bookid":1,"bookname":"java

badgerfish notation

http://localhost:8080/simple-service-jettison/api/books/jsonbook2

{"jsonbook2":{"bookid":{"$":"1"},"bookname":{"$":"java

restful web services實戰"}}}

最後簡要介紹一下atom類型。

atom是一種基于xml的文檔格式,該格式的标準定義在ietf rfc 4287(atom syndication format,atom聯合格式),其推出的目的是用來替換rss。atompub是基于atom的釋出協定,定義在ietf rfc 5023(atom publishing protocol)。

jersey2沒有直接引入支援atom格式的媒體包,但jersey1.x中包含jersey-atom包。這說明jersey的基本架構可以支援基于xml類型的資料,這種可插拔的媒體包支援對于jersey本身更具靈活性,對使用jersey的rest服務更具可擴充性。