<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<file> 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<inputstream> 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<reader> 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<book> 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 > content-type: application/xml
<?xml version="1.0"
encoding="utf-8" standalone="yes"?><book
bookid="100" bookname="test book"/>
2 < content-length: 79
2 < content-type: text/html
encoding="utf-8"?><book bookid="100"
bookname="test book"/>
从测试结果可以看到,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 & 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。
<dependency>
<groupid>org.glassfish.jersey.media</groupid>
<artifactid>jersey-media-moxy</artifactid>
</dependency>
(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<long, book> memorybase;
...
@get
//关注点2:实现类方法无需再定义@produces注解和@consumes注解
public books getbooks() {
final list<book> booklist = new arraylist<>();
final set<map.entry<long, book>> entries = bookresource.memorybase.entryset();
final iterator<entry<long, book>> iterator =
entries.iterator();
while (iterator.hasnext()) {
final entry<long, book> 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配置中声明如下依赖。
<artifactid>jersey-media-json-processing</artifactid>
使用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<map.entry<long, jsonobject>> entries =
bookresource.memorybase.entryset();
final iterator<entry<long, jsonobject>> 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配置中声明如下依赖。
<artifactid>jersey-media-json-jackson</artifactid>
使用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<objectmapper> {
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<\?> 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配置中声明如下依赖。
<artifactid>jersey-media-json-jettison</artifactid>
使用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<jaxbcontext> {
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<\?> 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服务更具可扩展性。