如何处理 WebService 中的 Map 对象

2024-12-20 16:09:54
推荐回答(2个)
回答1:

对于普通的 Java 数据类型、JavaBean、List 而言,SOAP 服务可以完全将其处理(序列化与反序列化),这些都没有任何问题,但对于 Map 对象而言,似乎就有些麻烦了。

请看下面这个例子:

 

   

@WebService(value = "/soap/ProductService", type = WebService.Type.SOAP)

public interface ProductService {

 

    boolean createProduct(Map productFieldMap);

}

   


为了创建一个 Product,我们需要传递一个 Map 类型的参数。实现该接口应该不难,关键是客户端能否将 Map 对象传递过来?

Whatever,我们都要用一个客户端来验证一下:

 

public class ProductServiceSOAPTest {

 

    private String wsdl = "http://localhost:8080/smart-sample/ws/soap/ProductService";

    private ProductService productService = SOAPHelper.createClient(wsdl, ProductService.class);

 

    @Test

    public void createProductTest() {

        Map productFieldMap = new HashMap();

        productFieldMap.put("productTypeId", 1);

        productFieldMap.put("name", "1");

        productFieldMap.put("code", "1");

        productFieldMap.put("price", 1);

        productFieldMap.put("description", "1");

 

        boolean result = productService.createProduct(productFieldMap);

        Assert.assertTrue(result);

    }

}

   


看来开发一个客户端也不难,关键是我们使用了 SOAPHelper,它为我们创建了一个 ProductService 的代理对象,所以接下来的一切都是那么简单!

运行一下,看看结果究竟如何吧!


异常告诉我们:Marshalling Error: java.util.Map is not known to this context,意思是说,java.util.Map 序列化(Marshalling)错误。

看来 SOAP 果无法处理 Map 对象啊!怎么解决呢?

对于 SOAP 而言,确实有些复杂,JDK 的 JAXB 规范为我们提供了一个解决方案。

我们得自定义一个 XmlAdapter(XML 适配器),将 Map 对象转换为 SOAP 可以处理的对象。

我们做的有两件事情:

定义一个 StringObjectMapAdapter 类扩展 javax.xml.bind.annotation.adapters.XmlAdapter,目的是为了转换 Map 对象。

使用 javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter 注解,标注需要转换的 Map 对象。

Come on!

 

   

public class StringObjectMapAdapter extends XmlAdapter> {

 

    @Override

    public Map unmarshal(Data data) throws Exception {

        Map map = new HashMap();

        for (Data.Entry entry : data.getList()) {

            map.put(entry.getKey(), entry.getValue());

        }

        return map;

    }

 

    @Override

    public Data marshal(Map map) throws Exception {

        Data data = new Data();

        for (Map.Entry entry : map.entrySet()) {

            data.addEntry(entry.getKey(), entry.getValue());

        }

        return data;

    }

 

    public static class Data {

 

        private List list = new ArrayList();

 

        public void addEntry(String fieldName, Object fieldValue) {

            Entry entry = new Entry();

            entry.setKey(fieldName);

            entry.setValue(fieldValue);

            list.add(entry);

        }

 

        public List getList() {

            return list;

        }

 

        public void setList(List list) {

            this.list = list;

        }

 

        public static class Entry {

 

            private String key;

            private Object value;

 

            public String getKey() {

                return key;

            }

 

            public void setKey(String key) {

                this.key = key;

            }

 

            public Object getValue() {

                return value;

            }

 

            public void setValue(Object value) {

                this.value = value;

            }

        }

    }

}

   


我们写类一个 StringObjectMapAdapter 类,让它继承 XmlAdapter,只需实现两个方法即可:

unmarshal:反序列化,将 Data 对象转为 Map 对象。

marshal:序列化,将 Map 对象转为 Data 对象。

注意,这里的 Data 可作为 StringObjectMapAdapter 的静态内部类,当然也可独立存在。在 Data 类中还有另一个静态内部类 Entry,它实际上就是 Map 中的若干条目,可将 Map 看做是用一个 List 对 Entry 的包装,这是我们上面看到的 Data 类。

随后,我们需要将 StringObjectMapAdapter 作用在 Map 上,只需在方法的参数中使用一个 @XmlJavaTypeAdapter 注解即可实现。

 

   

@WebService(value = "/soap/ProductService", type = WebService.Type.SOAP)

public interface ProductService {

 

    boolean createProduct(@XmlJavaTypeAdapter(StringObjectMapAdapter.class) Map productFieldMap);

}

   


这样,再次调用 WebService,就会看到运行成功的信息!

在这个解决方案中比较复杂的就是 StringObjectMapAdapter 了,而且我们要知道,它仅仅能处理 Map 类型的数据而已,对于其它不同泛型的 Map 对象还无能为力,我们只能编写其它对应的 XxxMapAdapter,确实够折腾的!

对于 REST 而言,以上这一切都似乎不算什么了,不相信您就往下看把。

先写一个 REST 服务端:

 

   

@Bean

@WebService(value = "/rest/ProductService", type = WebService.Type.REST)

@Consumes(MediaType.APPLICATION_JSON)

@Produces(MediaType.APPLICATION_JSON)

public class ProductService extends BaseService {

 

    @POST

    @Path("/product")

    @Transaction

    public boolean createProduct(Map productFieldMap) {

        return DataSet.insert(Product.class, productFieldMap);

    }

}

   


接口免了,直接为 Service 类发布 REST 服务,我们可定义输入与输出的数据类型,不妨都为 JSON 吧,当然也可以为 XML。

再写一个 REST 客户端:

 

   

public class ProductServiceRESTTest {

 

    private String wadl = "http://localhost:8080/smart-sample/ws/rest/ProductService";

    private ProductService productService = RESTHelper.createClient(wadl, ProductService.class);

 

    @Test

    public void createProductTest() {

        Map productFieldMap = new HashMap();

        productFieldMap.put("productTypeId", 1);

        productFieldMap.put("name", "1");

        productFieldMap.put("code", "1");

        productFieldMap.put("price", 1);

        productFieldMap.put("description", "1");

 

        boolean result = productService.createProduct(productFieldMap);

        Assert.assertTrue(result);

    }

}

   


注意,这里使用的是 RESTHelper 获取 REST 客户端代理对象的,而不是 SOAPHelper。此外,我们使用的 WADL,而不是 WSDL。

运行一下,完全正确!

看来在对象序列化方面,REST 确实比 SOAP 要优秀一些。如果实际应用场景中,只能使用 SOAP 那么我们应该尽可能回避 Map 对象,实在不行的话,就只能使用 XmlAdapter 的解决方案了。如果条件允许的话,推荐尽量使用 REST。

或许有些朋友提出质疑,在 Security 方面,REST 也提供了类似 SOAP 那样的 WS-Security 解决方案吗?将来有机会再与大家讨论这方面的问题吧!

回答2:

定义一个 StringObjectMapAdapter 类扩展 javax.xml.bind.annotation.adapters.XmlAdapter,目的是为了转换 Map 对象。
使用 javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter 注解,标注需要转换的 Map 对象。