CXF+JAXB處理複雜數據

CXF默認使用JAXB 來實現對象和XML之間的映射。在前面的例子 中,使用CXF發佈的Webservice,其方法的參數和返回值都是簡單類型。 本文討論對象複雜性的分級,驗證對於各種複雜度JAXB的支持情況,以及使用JAXB時對於Map,循環引用,繼承等情況的處理辦法。 文中的例子沒有直接調用JAXB的API,而是用CXF發佈webservice的形式驗證對象到xml的marshal和unmarshal, 所以本文也可以作爲使用CXF的參考資料。

1 數據複雜性的分類

大體來說,Java中的數據/數據對象按照其複雜度可以分爲以下幾類:

1.1 簡單數據類型

包括基本類型和Java對基本類型的封裝,主要有:

 
基本類型封裝類
floatFloat
doubleDouble
byteByte
shortShort
intInteger
longLong
charCharacter
booleanBoolean
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適配器,代碼如下:

MapAdapter.java

MapEntity是自己定義的一個簡單結構,用於保持Map中的key-value關係:

public class MapEntity{
     public Object key;
     public Object value;
}

 

經過這樣的處理,就能夠實現Map與XML之間的映射。

3.2 斷開循環引用的迴路

對象之間的引用很有可能出現迴路。最簡單的情況是兩個對象之間互相引用。這在ORM中很常見。如果我們在前面的User類中增加父子關係,如下:

User.java

當同時在兩個方向設置引用關係時,就發生了循環引用:

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

關注公衆號,分享乾貨,討論技術


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章