天天看点

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服务更具可扩展性。