<转载>在Jersey JAX-RS 处理泛型Collection

在Java中,从1.5开始,我们就可以使用泛型了(generic),这看上去很像C++ Template,但是实际上它们是不同的。在这里我不想过多的描述细节,你可以从Google上搜索一下。 但是,泛型已经变得如此复杂,以至于已经有500多页的 FAQ。

我们长话短说:泛型提供了编译时类型安全,所以也消除了类型转换的(cast)的需要。它是通过被称为类型消除(type erasure)的编译时技术来实现的。 泛型FAQ解释了所有的细节,对我来说它就是Java泛型的圣经。
在有些情况下,我们需要从JAXRS的Response类的资源方法中饭后参数化的类型。 因为类型消除,使得此种情况下Jersey运行环境需要特殊的处理来决定为泛型类型选择相应的MessageBodyWriter。
对于使用Jersey的JAX-RS的开发者而言有很多可供选择的技术,下面我将详细的讨论其中的几个。
让我们看一个简单的域模型:Employee。

 1 package net.javasight;
 2
 3 import javax.xml.bind.annotation.XmlRootElement;
 4
 5 @XmlRootElement(name = "employee")
 6 public class EmployeeBean {
 7 private Long id;
 8 private String firstName;
 9 private String lastName;
10
11 public EmployeeBean() {
12 // required for JAXB
13 }
14
15 public EmployeeBean(Long id, String firstName, String lastName) {
16 this.id = id;
17 this.firstName = firstName;
18 this.lastName = lastName;
19 }
20
21 public Long getId() {
22 return id;
23 }
24
25 public void setId(Long id) {
26 this.id = id;
27 }
28
29 public String getFirstName() {
30 return firstName;
31 }
32
33 public void setFirstName(String firstName) {
34 this.firstName = firstName;
35 }
36
37 public String getLastName() {
38 return lastName;
39 }
40
41 public void setLastName(String lastName) {
42 this.lastName = lastName;
43 }
44 }

employee资源的一个实现如下:

1 @Path("/employees")
2 public class EmployeeResource {
3 @GET
4 public Collection<EmployeeBean> getEmployees() {
5 EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
6 return Collections.singletonList(emp);
7 }
8 }

在这种情况下,我们从资源方法中返回了EmployeeBean的Collection集合。如果你从如下地址(http://localhost:9998/employees)访问该资源,将会产生如下的XML输出:

1 <? xml version = "1.0" encoding = "UTF-8" standalone = "yes" ?>
2 <employeeBeans>
3 <employee>
4 <firstName>John</firstName>
5 <id>1</id>
6 <lastName>Doe</lastName>
7 </employee>
8 </employeeBeans>

我希望看到employee在<employees>标签中,而不是在<employeeBeans>中。这里需要做一些额外的配置来产生该格式。那么让我们修改下Employee的POJO来包含该集合。

 1 package net.javasight;
 2
 3 import java.util.List;
 4
 5 import javax.xml.bind.annotation.XmlRootElement;
 6
 7 @XmlRootElement
 8 public class Employees {
 9 private List<EmployeeBean> employee;
10
11 public Employees(List<EmployeeBean> employee) {
12 this.employee = employee;
13 }
14
15 public Employees() {
16 // required for JAXB
17 }
18
19 public List<EmployeeBean> getEmployee() {
20 return employee;
21 }
22
23 public void setEmployee(List<EmployeeBean> employee) {
24 this.employee = employee;
25 }
26 }

让我们完成EmployeeResource的方法来使用自定义的POJO来产生相关的XML。

 1 package net.javasight;
 2
 3 @Path("/employees")
 4 public class EmployeeResource {
 5 @GET
 6 @Path("test1")
 7 public Employees getEmployees1() {
 8 EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
 9 Employees employees = new Employees(Collections.singletonList(emp));
10 return employees;
11 }
12
13 @GET
14 @Path("test2")
15 public Response getEmployees2() {
16 EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
17 Employees employees = new Employees(Collections.singletonList(emp));
18 return Response.ok(employees).build();
19 }
20 }

现在访问http://localhost:9988/employees/test1或http://localhost:9998/employees/test2应该会产生如下的XML:

1 <? xml version = "1.0" encoding = "UTF-8" standalone = "yes" ?>
2 <employees>
3 <employee>
4 <firstName>John</firstName>
5 <id>1</id>
6 <lastName>Doe</lastName>
7 </employee>
8 </employees>

但是,难道我们需要这样糟糕的方法来产生该输出吗?已经不需要了,在Jersey1.2发布后有了一些改善。可以启用资源配置的FEATURE_XMLROOTELEMENT_PROCESSING 特性后,应当可以自动的产生该输出。因此,访问http://localhost:9998/employees/test1应该产生这种的格式XML。该属性默认是禁用的。
现在让我们看看在JAX-RS Response中返回参数化类型会遇到的实际的问题。我在EmployeeResource中添加了另一个方法。

@GET
@Path("test3")
@Produces(MediaType.APPLICATION_XML)
public Response getEmployees3() {
EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
List<EmployeeBean> list = new ArrayList<EmployeeBean>();
list.add(emp);
return Response.ok(list).build();
}

现在通过http://localhost:9998/employees/test3访问该方法应该会产生如下异常。我相信该异常在Jersey/JAX-RS用户中应该很常见。

 1 SEVERE: A message body writer for Java class java.util.ArrayList, and Java type class java.util.ArrayList, and MIME media type application/xml was not found
 2 Jul 24 , 2010 11 : 58 : 55 PM com.sun.jersey.spi.container.ContainerResponse write
 3 SEVERE: The registered message body writers compatible with the MIME media type are:
 4 application/xml ->
 5 com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$App
 6 com.sun.jersey.core.impl.provider.entity.DocumentProvider
 7 com.sun.jersey.core.impl.provider.entity.SourceProvider$SourceWriter
 8 com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$App
 9 com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$App
10 */* ->
11 com.sun.jersey.core.impl.provider.entity.FormProvider
12 com.sun.jersey.server.impl.template.ViewableMessageBodyWriter
13 com.sun.jersey.core.impl.provider.entity.StringProvider
14 com.sun.jersey.core.impl.provider.entity.ByteArrayProvider
15 com.sun.jersey.core.impl.provider.entity.FileProvider
16 com.sun.jersey.core.impl.provider.entity.InputStreamProvider
17 com.sun.jersey.core.impl.provider.entity.DataSourceProvider
18 com.sun.jersey.core.impl.provider.entity.XMLJAXBElementProvider$General
19 com.sun.jersey.core.impl.provider.entity.ReaderProvider
20 com.sun.jersey.core.impl.provider.entity.DocumentProvider
21 com.sun.jersey.core.impl.provider.entity.StreamingOutputProvider
22 com.sun.jersey.core.impl.provider.entity.SourceProvider$SourceWriter
23 com.sun.jersey.core.impl.provider.entity.XMLRootElementProvider$General
24 com.sun.jersey.core.impl.provider.entity.XMLListElementProvider$General
25 Jul 24 , 2010 11 : 58 : 55 PM com.sun.jersey.spi.container.ContainerResponse traceException
26 SEVERE: Mapped exception to response: 500 (Internal Server Error)
27 javax.ws.rs.WebApplicationException

要修复该问题,我们需要告诉JAX-RS运行时响应实体的类型,在我们的情况下是Employee的集合。JAX-RS APIGenericEntity正是用于该目的。GenericEntity可以用于反映响应实体中的泛型类型。 EmployeeResource方法被更新在返回集合类型时使用GenericEntity。

@GET
@Path("test4")
@Produces(MediaType.APPLICATION_XML)
public Response getEmployees4() {
EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
List<EmployeeBean> list = new ArrayList<EmployeeBean>();
list.add(emp);
GenericEntity entity = new GenericEntity<List<EmployeeBean>>(list) {
};
return Response.ok(entity).build();
}

访问http://localhost:9998/employees/test4应该会产生我们所需要的输出。
除了这种方式,Jersey1.2引入了新的APIJResponse来支持这个,并且更优。JResponse是类型安全的Response,它保留了响应实体的类型信息,因此不需要额外的使用GenericEntity。
使用JResponse更新后的方法如下:

1 @GET
2 @Path("test5")
3 @Produces(MediaType.APPLICATION_XML)
4 public JResponse<List<EmployeeBean>> getEmployees5() {
5 EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
6 List<EmployeeBean> list = new ArrayList<EmployeeBean>();
7 list.add(emp);
8 return JResponse.ok(list).build();
9 }

访问http://localhost:9998/employees/test5应该会产生我们所需要的输出.
这两种方式实现起来都很容易。主要的不同是GenericEntity是JAX-RS API然而JResponse是Jersey API,它可能不能在其他的JAX-RS实现中使用,因此不具备移植性。 如果你仅仅使用Jersey,那么JResponse是更好的选择,因为它是类型安全的并与Response兼容。
下面是完整的服务端代码:

 1 package net.javasight;
 2
 3 import com.sun.grizzly.http.SelectorThread;
 4 import com.sun.jersey.api.container.grizzly.GrizzlyWebContainerFactory;
 5 import com.sun.jersey.core.util.FeaturesAndProperties;
 6 import javax.ws.rs.core.UriBuilder;
 7 import java.io.IOException;
 8 import java.net.URI;
 9 import java.util.HashMap;
10 import java.util.Map;
11
12 public class Main {
13 private static int getPort(int defaultPort) {
14 String port = System.getenv("JERSEY_HTTP_PORT");
15 if (null != port) {
16 try {
17 return Integer.parseInt(port);
18 } catch (NumberFormatException e) {
19 }
20 }
21 return defaultPort;
22 }
23
24 private static URI getBaseURI() {
25 return UriBuilder.fromUri("http://localhost/").port(getPort(9998))
26 .build();
27 }
28
29 public static final URI BASE_URI = getBaseURI();
30
31 protected static SelectorThread startServer() throws IOException {
32 final Map<String, String> initParams = new HashMap<String, String>();
33 initParams.put("com.sun.jersey.config.property.packages",
34 "com.employee.resources");
35 initParams.put(FeaturesAndProperties.FEATURE_XMLROOTELEMENT_PROCESSING,
36 "true");
37 System.out.println("Starting grizzly...");
38 SelectorThread threadSelector = GrizzlyWebContainerFactory.create(
39 BASE_URI, initParams);
40 return threadSelector;
41 }
42
43 public static void main(String[] args) throws IOException {
44 SelectorThread threadSelector = startServer();
45 System.out
46 .println(String
47 .format(
48 "Jersey app started with WADL available at "
49 + "%sapplication.wadl\nTry out %shelloworld\nHit enter to stop it...",
50 BASE_URI, BASE_URI));
51 System.in.read();
52 threadSelector.stopEndpoint();
53 }
54 }

以上内容转自http://javasight.net/2011/05/generified-collections-in-jersey/,感谢作者,但是作者只提供了服务端的泛型处理,并没有提供客户端如何使用泛型。
个人解决客户端接受泛型方法:

服务端代码:

@GET
@Path("test4")
@Produces(MediaType.APPLICATION_JSON)
public Response getEmployees4() {
EmployeeBean emp = new EmployeeBean(1L, "John", "Doe");
EmployeeBean emp1 = new EmployeeBean(2L, "zhangsan", "lisi");
List<EmployeeBean> list = new ArrayList<EmployeeBean>();
list.add(emp);
list.add(emp1);
GenericEntity<List<EmployeeBean>> entity = new GenericEntity<List<EmployeeBean>>(list) {
};
return Response.ok(entity).build();
}

客户端代码:

1 GenericType<List<EmployeeBean>> gt = new GenericType<List<EmployeeBean>>(){};
2 Object obj = wr.path("/message/test4").get(gt);
3 System.out.println(obj);

加红字体是rest服务地址 wr是webResource对象。

时间: 2024-10-01 04:34:26

<转载>在Jersey JAX-RS 处理泛型Collection的相关文章

认识Collection 与 泛型

主要内容 Collection集合 迭代器 增强for 泛型 Collection集合 集合到底是什么呢? 集合:集合是java中提供的一种容器,可以用来存储多个数据. 集合和数组既然都是容器,它们有啥区别呢? 数组的长度是固定的.集合的长度是可变的. 数组中存储的是同一类型的元素,可以存储基本数据类型值.集合存储的都是对象.而且对象的类型可以不 一致.在开发中一般当对象多的时候,使用集合进行存储. 1.2 集合框架 JAVASE提供了满足各种需求的API,在使用这些API前,先了解其继承与接口

Collection、泛型

Collection.泛型 Collection.泛型 Collection.泛型 Collection.泛型 Collection.泛型 Collection.泛型 Collection.泛型 原文地址:https://www.cnblogs.com/juna3066/p/10617864.html

Java——Collection集合、迭代器、泛型

集合 ——集合就是java提供的一种容器,可以用来存储多个数据. 集合和数组的区别 数组的长度是固定的.集合的长度是可变的. 数组中存储的是同一类型的元素,可以存储基本数据类型值. 集合存储的都是对象.而且对象的类型可以不一致.在开发中一般当对象多的时候,使用集合进行存储. 接口继承关系与实现 集合类存放于 Java.util 包中,主要有 3 种:set(集).list(列表包含 Queue)和 map(映射). 1. **Collection:**Collection 是集合 List.Se

我的学习之路_第六章_迭代器,泛型

[Collection] 类 接口类 所属包:java.util.Collection Collection类是集合的最顶层的接口,定义了集合共性的方法. 接口无法创建对象,使用多态的方式创建对象 Collection<集合中的数据类型(泛型)> 变量名 = new ArrayList<集合中的数据类型(泛型)>(); Collection接口常用子类接口有: List接口 set接口 List接口常用子类有: ArrayList类 LinkedList类 set接口常用的子类有:

Collection的toArray()使用上需要注意的地方

转载:http://llade.iteye.com/blog/199818 Collection在很多情况下需要转换为数组来处理(很多接口方法都使用array作为参数). Collection的toArray()方法返回的Object[],是不能被强制转换为子元素类型的 例如: Java代码   List l=new ArrayList(); l.add("a"); l.add("b"); String[] strs=(String[])l.toArray();//

初识集合和泛型

package com.oracle.Test; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; /* * 问题: 你们怎么理解集合这个词. * 答: 通常指容器.用来装载n个数据. * * 问题: 集合中能放的元素类型 * 答: Java设计人员.设计Java中的集合时.是希望这个集合可以放下所有类型的数据. * Java中有一个类是所有类的父类.Object.按照继承和多态

NET/ASP.NET Routing路由(深入解析路由系统架构原理)(转载)

NET/ASP.NET Routing路由(深入解析路由系统架构原理) 阅读目录: 1.开篇介绍 2.ASP.NET Routing 路由对象模型的位置 3.ASP.NET Routing 路由对象模型的入口 4.ASP.NET Routing 路由对象模型的内部结构 4.1UrlRoutingModule 对象内部结构 4.2RouteBase.Route.RouteCollection.RouteTable 路由核心对象模型 4.3RouteValueDictionary.RouteData

Jackson如何实现json字符串和泛型List集合之间的转换

前言: Jackson常用方法:   Jackson是处理一般的java对象和json之间的转换常用的两个方法是ObjectMapper类的writeValueAsString()和readValue()两个方法就可以实现. 但是,如果是要转成指定泛型的List集合,如List<User>.就需要先反序列化复杂类型为泛型 Collection Type; 举例说明: 如果是List<User>集合,那么使用ObjectMapper的getTypeFactory().construc

【转】.NET/ASP.NET Routing路由(深入解析路由系统架构原理)

阅读目录: 1.开篇介绍 2.ASP.NET Routing 路由对象模型的位置 3.ASP.NET Routing 路由对象模型的入口 4.ASP.NET Routing 路由对象模型的内部结构 4.1]UrlRoutingModule 对象内部结构 4.2]RouteBase.Route.RouteCollection.RouteTable 路由核心对象模型 4.3]RouteValueDictionary.RouteData.RequestContext 路由数据对象模型 4.4]IRou