1. 程式人生 > ><轉載>在Jersey JAX-RS 處理泛型Collection

<轉載>在Jersey JAX-RS 處理泛型Collection

讓我 選擇 現在 com true container div 發布 params

在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對象。

<轉載>在Jersey JAX-RS 處理泛型Collection