JAX-RS 2.0 與 XML/JSON 資料轉換 - 神奇的 MOXy & Jackson
在前幾次的 JAX-RS 2.0 文章之中,介紹的都是 Java 基本型別的處理方式,不過我們一般比較常用的,反而是各種自訂 JavaBeans 型別與 XML/JSON 資料格式之間的轉換。資料轉換在 JAX-RS 2.0 裡頭,雖然不是預設就會啟用的功能,但是也不難。建立 JAX-RS 2.0 專案為了示範方便,請執行底下的指令,建立一個使用 Grizzly 的 Jersey 專案: mvn archetype:generate -DarchetypeGroupId=org.glassfish.jersey.archetypes -DarchetypeArtifactId=jersey-quickstart-grizzly2 -DarchetypeVersion=2.5.1 -DgroupId=tw.com.codedata -DartifactId=jaxbdemo -Dpackage=tw.com.codedata.jaxbdemo -DinteractiveMode=false 刪除自動產生的
package tw.com.codedata.jaxbdemo; import java.io.*; public class Region implements Serializable { static final long serialVersionUID = 20140124L; private int regionId; private String regionDescription; public Region() {} public Region(int regionId, String regionDescription) { this.regionId = regionId; this.regionDescription = regionDescription; } public int getRegionId() { return regionId; } public void setRegionId(int regionId) { this.regionId = regionId; } public String getRegionDescription() { return regionDescription; } public void setRegionDescription(String regionDescription) { this.regionDescription = regionDescription; } }
package tw.com.codedata.jaxbdemo; import java.util.*; import javax.ws.rs.*; import javax.ws.rs.core.*; @Path("/regions") public class RegionService { private static List<Region> regionList = null; private static Region errorRegion = null; static { regionList = new ArrayList<Region>(); regionList.add(new Region(1, "Eastern")); regionList.add(new Region(2, "Western")); regionList.add(new Region(3, "Northern")); regionList.add(new Region(4, "Southern")); errorRegion = new Region(0, "Error"); } public RegionService() { } @POST @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public int create(Region region) { regionList.add(region); return 1; } @GET @Path("/{regionId}") @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Region retrieve(@PathParam("regionId") int regionId) { for (Region region : regionList) { if (region.getRegionId() == regionId) return region; } return errorRegion; } @DELETE @Path("/{regionId}") public int delete(@PathParam("regionId") int regionId) { int found = 0; for (Region r : regionList) { if (r.getRegionId() == regionId) { regionList.remove(r); found = 1; } } return found; } @GET @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public List<Region> retrieveAll() { return regionList; } @PUT @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public int update(Region region) { int found = 0; int targetId = region.getRegionId(); for (Region r : regionList) { if (r.getRegionId() == targetId) { r.setRegionDescription(region.getRegionDescription());; found = 1; } } return found; } } 整個專案目錄架構如下: 執行之後,雖然可以透過 RESTClient 看到 WADL (Web Application Description Language) 檔案: 但是卻沒辦法正確觸發 原因就在於,Jersey 目前的設定只能處理簡單的 Java 型別,但是對於 POJO 與 XML/JSON 格式之間的轉換,就無能為力了。 XML 支援在 Java 與 REST 的邂逅(二)JAX-RS 核心 Annotation 裡頭提到,如果想要提供 XML 支援,也就是讓
JAX-RS 2.0 的規格要求 Implementation 必須支援上面提到的幾種作法。不過 JAXP 的 Application supplied JAXB classes 指的就是有加上 JAXB 相關 Annotation 的 Java 類別。這種方式比較簡單,適合用在初學或是沒有詭異需求的時候。 以我們的範例來說,只要修改一下前面的 package tw.com.codedata.jaxbdemo; import java.io.*; import javax.xml.bind.annotation.*; @XmlRootElement(name = "region") @XmlAccessorType(XmlAccessType.FIELD) public class Region implements Serializable { ... } Jersey 就可以順利進行 Marshalling 跟 Unmarshalling,把 JSON 支援Jersey 把 JSON 這部份的支援設計成 Extension Module,目前可以透過底下任意一個 3rd-Party Library 來達成:
只要 Jersey 在 CLASSPATH 發現這幾個 Library 的存在,就會自動註冊使用。 MOXy因為 EclipseLink 貢獻了 EclipseLink MOXy 這個 JSON-Binding Provider 給 JAX-RS 2.0,而 Jersey 又是 JAX-RS 2.0 的 Reference Implementation,所以 MOXy 順理成章就變成是 Jersey 建議使用的 JSON-Binding Library。 檢查剛剛由 Maven 自動產生的 <!-- uncomment this to get JSON support: <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-moxy</artifactId> </dependency> --> 打開這一段註解,重新再編譯一次,執行後應該就可以順利觸發 Jackson網路上有些人比較喜歡使用 Jackson 這個 JSON Processor,所以 Jersey 當然也支援。Jackson 有一個 Jackson JAX-RS Providers 子計畫,用來為 JAX-RS 2.0 提供 JSON、XML、與 Smile 三種資料格式的轉換。從 Jackson 2.2 版開始,這個子計畫會取代以前的 Extension Module,比方說 透過 Maven 使用 Jackson JAX-RS Providers 的時候,請在專案的 <dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-json-provider</artifactId> <version>2.3.0</version> </dependency> 然後一樣為 檢視 <dependencies> <!-- builds on shared base JAX-RS handling code... --> <dependency> <artifactId>jackson-jaxrs-base</artifactId> <groupId>${project.groupId}</groupId> <version>${project.version}</version> </dependency> <!-- Extends Jackson core, mapper, and also (sort of optionally) on JAXB annotation handler --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>${version.jackson.core}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${version.jackson.core}</version> </dependency> <!-- also need JAXB annotation support --> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-jaxb-annotations</artifactId> <version>${version.jackson.jaxb}</version> </dependency> <!-- test deps should come from parent, including jersey --> </dependencies> |