CXF默認使用JAXB 來實現對象和XML之間的映射。在前面的例子 中,使用CXF發佈的Webservice,其方法的參數和返回值都是簡單類型。 本文討論對象複雜性的分級,驗證對於各種複雜度JAXB的支持情況,以及使用JAXB時對於Map,循環引用,繼承等情況的處理辦法。 文中的例子沒有直接調用JAXB的API,而是用CXF發佈webservice的形式驗證對象到xml的marshal和unmarshal, 所以本文也可以作爲使用CXF的參考資料。
Table of Contents
1 數據複雜性的分類
大體來說,Java中的數據/數據對象按照其複雜度可以分爲以下幾類:
1.1 簡單數據類型
包括基本類型和Java對基本類型的封裝,主要有:
基本類型 | 封裝類 |
---|---|
float | Float |
double | Double |
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
boolean | Boolean |
char[] | String |
1.2 自定義類型
在C裏面叫做struct,在Java裏面叫做JavaBean,包含自定義屬性和getter/setter方法。
1.3 集合類型
Java的集合類(Collection)主要分爲List,Set,Map三個系列。List實現了元素的序列(順序),Set實現不重複的集合,Map實現了key-value的映射。
1.4 複雜類型
更復雜的情況是對於上述三種類型的組合運用,比如在自定義類型中使用集合,或者集合的嵌套等。 複雜類型還會涉及到循環引用和繼承關係等問題。
2 JAXB對數據複雜性的支持
- 簡單類型
對於簡單的數據類型,JAXB不需要任何處理就完全能夠支持
- 自定義類型
JAXB對於一般的JavaBean也能夠支持,比如下面的例子:
User.java
public class User {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
不需要JavaBean實現Serializable接口,也不需要增加@XmlRootElement聲明。
- 集合類型
JAXB能夠內置支持List和Set集合,但是對於Map的支持需要自己處理。
- 複雜類型
JAXB支持簡單類型、自定義類型、集合類型等的嵌套,但是對於循環引用、繼承等情況需要增加額外的處理。
3 常用技巧
3.1 使用自定義的XmlAdapter支持Map
JAXB可以在變量上添加@XmlJavaTypeAdapter標註,指定對該變量專門的適配器進行處理。 適配器繼承XmlAdapter類,並覆蓋了marshal和unmarshal方法,分別用於對象到XML的映射和XML到對象的映射。
使用XmlAdapter可以實現對Map類型的映射。
比如對於要通過CXF發佈的WebService接口方法上,可以增加標註:
@XmlJavaTypeAdapter(MapAdapter.class)
Map<String,User> getUserMap();
Integer setUserMap(@XmlJavaTypeAdapter(MapAdapter.class)Map<String,User> users);
其中的MapAdapter就是自己實現的Map適配器,代碼如下:
MapEntity是自己定義的一個簡單結構,用於保持Map中的key-value關係:
public class MapEntity{ public Object key; public Object value; }
經過這樣的處理,就能夠實現Map與XML之間的映射。
3.2 斷開循環引用的迴路
對象之間的引用很有可能出現迴路。最簡單的情況是兩個對象之間互相引用。這在ORM中很常見。如果我們在前面的User類中增加父子關係,如下:
當同時在兩個方向設置引用關係時,就發生了循環引用:
child.parent = parent;
parent.children.put(child.getName(), child);
發生循環引用時,JAXB就會拋出異常。而處理的辦法就是斷開其中一個方向的引用。具體做法就是使用@XmlTransient標註,表明該屬性在marshal是不作處理。 如上面的User中,我們可以只處理parent到child的引用,而不處理child到parent的引用:
@XmlTransient
public User parent;
這樣雖然解決了循環引用的問題,但是會導致得到User對象的parent屬性爲null。爲使用帶來不變。 解決的辦法是在JavaBean中增加afterUnmarshal()方法,當JAXB從xml恢復出對象後,會自動調用這個方法。我們可以在方法中將丟失的信息補全:
public void afterUnmarshal(Unmarshaller u,Object parent) { for(Iterator itor = this.children.values().iterator();itor.hasNext();){ User user = (User)itor.next(); user.parent = this; } }
3.3 使用@XmlSeeAlso標註處理繼承關係
繼承關係在ORM中已經處理得非常完善了,JAXB處理繼承關係更加簡單,只需要在繼承樹的根類上增加@XmlSeeAlso標註,聲明所有的子類即可。 比如我們定義了一個User的子類:
public class MyUser extends User {...}
則只需要在User類上面增加標註:
@XmlSeeAlso({ MyUser.class }) public class User {...}
4 代碼
本文相關的所有代碼如下:
4.1 maven工程文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hysec</groupId> <artifactId>cxfdemo</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>cxfdemo</name> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>apache-cxf</artifactId> <version>2.4.1</version> <type>pom</type> </dependency> </dependencies> </project>
4.2 Map適配器
package com.hysec.utils.jaxb; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.xml.bind.annotation.adapters.XmlAdapter; public class MapAdapter extends XmlAdapter<MapEntity[], Map> { @Override public MapEntity[] marshal(Map map) throws Exception { // TODO Auto-generated method stub MapEntity[] list = new MapEntity[map.size()]; Set keyset = map.keySet(); int index =0; for(Iterator itor=keyset.iterator();itor.hasNext();){ MapEntity item = new MapEntity(); item.key = itor.next(); item.value = map.get(item.key); list[index++] = item; } return list; } @Override public Map unmarshal(MapEntity[] list) throws Exception { // TODO Auto-generated method stub Map map = new HashMap(); for(int i=0;i<list.length;i++){ MapEntity item = list[i]; map.put(item.key, item.value); } return map; } }
4.3 Map適配器使用的key-value結構
package com.hysec.utils.jaxb; public class MapEntity{ public Object key; public Object value; }
4.4 JavaBean父類
package cxfdemo; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.xml.bind.Unmarshaller; import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlTransient; @XmlSeeAlso({ MyUser.class }) public class User { private Integer id; private String name; @XmlTransient public User parent; public Map<String,User> children = new HashMap<String,User>(); public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void afterUnmarshal(Unmarshaller u,Object parent) { for(Iterator itor = this.children.values().iterator();itor.hasNext();){ User user = (User)itor.next(); user.parent = this; } } }
4.5 JavaBean子類
package cxfdemo; public class MyUser extends User { public String myProp; }
4.6 webservice接口定義
package cxfdemo; import java.util.List; import java.util.Map; import java.util.Set; import javax.jws.WebService; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import com.hysec.utils.jaxb.MapAdapter; @WebService public interface CXFDemo { String sayHello(String foo); String sayHelloToUser(User user); User getUser(String name); List<User> getUsers(); Integer setUsers(List<User> users); Set<User> getUserSet(); Integer setUserSet(Set<User> users); @XmlJavaTypeAdapter(MapAdapter.class) Map<String,User> getUserMap(); Integer setUserMap(@XmlJavaTypeAdapter(MapAdapter.class)Map<String,User> users); User addChild(User parent,User child); }
4.7 webservice實現類
package cxfdemo; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jws.WebService; @WebService() public class CXFDemoImpl implements CXFDemo { public String sayHello(String foo) { return "hello "+foo; } public String sayHelloToUser(User user){ return "hello "+user.getName(); } public User getUser(String name){ User user = new User(); user.setName(name); return user; } public List<User> getUsers(){ List<User> users = new ArrayList<User>(); users.add(new User()); return users; } public Integer setUsers(List<User> users){ return users.size(); } public Set<User> getUserSet(){ Set<User> set = new HashSet<User>(); set.add(new User()); set.add(new User()); return set; } public Integer setUserSet(Set<User> users){ return users.size(); } public Map<String,User> getUserMap(){ HashMap<String,User> map = new HashMap<String,User>(); User user1 = new User(); user1.setName("Holbrook"); map.put("Holbrook", user1); User user2 = new User(); user2.setName("wanghaikuo"); map.put("wanghaikuo", user2); return map; } public Integer setUserMap(Map<String,User> users){ return users.size(); } public User addChild(User parent,User child){ child.parent = parent; parent.children.put(child.getName(), child); return parent; } }
4.8 測試用例
package cxfdemo.test; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.ws.Endpoint; import junit.framework.Assert; import junit.framework.TestCase; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; import cxfdemo.CXFDemo; import cxfdemo.CXFDemoImpl; import cxfdemo.MyUser; import cxfdemo.User; public class TestEndpoint extends TestCase { private static final String ADDRESS = "http://localhost:9000/cxfdemo"; private static CXFDemo service; @Override protected void setUp() throws Exception { // TODO Auto-generated method stub super.setUp(); if(null==service){ System.out.println("Starting Server"); CXFDemoImpl demo = new CXFDemoImpl(); Endpoint.publish(ADDRESS, demo); System.out.println("Start success"); JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); factory.setServiceClass(CXFDemo.class); factory.setAddress(ADDRESS); service = (CXFDemo)factory.create(); } } public void testSayHello(){ Assert.assertEquals(service.sayHello("foo"), "hello foo"); } public void testSayHelloToUser(){ User user = new User(); user.setName("Holbrook"); String result = service.sayHelloToUser(user); Assert.assertEquals(result,"hello Holbrook"); } public void testGetUser(){ User user = service.getUser("Holbrook"); Assert.assertEquals("Holbrook",user.getName()); } public void testGetUsers(){ List<User> users = service.getUsers(); Assert.assertEquals(1,users.size()); } public void testSetUsers(){ List<User> users = new ArrayList<User>(); users.add(new User()); users.add(new User()); users.add(new User()); Assert.assertEquals(3,service.setUsers(users).intValue()); } public void testGetUserSet(){ Set<User> userSet = service.getUserSet(); Assert.assertEquals(2,userSet.size()); } public void testSetUserSet(){ Set<User> set = new HashSet<User>(); set.add(new User()); set.add(new User()); Assert.assertEquals(2, service.setUserSet(set).intValue()); } public void testGetUserMap(){ Map<String,User> map = service.getUserMap(); Assert.assertTrue(map.containsKey("Holbrook")); Assert.assertTrue(map.containsKey("wanghaikuo")); } public void testSetUserMap(){ HashMap<String,User> map = new HashMap<String,User>(); User user1 = new User(); user1.setName("Holbrook"); map.put("Holbrook", user1); User user2 = new User(); user2.setName("wanghaikuo"); map.put("wanghaikuo", user2); Assert.assertEquals(2, service.setUserMap(map).intValue()); } public void testAddChild(){ User root = new User(); root.setName("root"); //root.parent = root; User child = new User(); child.setName("child"); User parent = service.addChild(root, child); Assert.assertTrue(parent.children.containsKey("child")); Assert.assertEquals(parent.children.get("child").parent, parent); } public void testInheritance(){ User parent = new User(); MyUser child = new MyUser(); child.setName("child"); child.myProp = "subclass Prop"; User root = service.addChild(parent, child); User newChild = root.children.get("child"); System.out.println(newChild instanceof MyUser); System.out.println(((MyUser)newChild).myProp); } }
Reference:
https://my.oschina.net/u/246522/blog/151160
http://www.cnblogs.com/hoojo/archive/2011/03/30/1999563.html