zoukankan      html  css  js  c++  java
  • 处理 WebService 中的 Map 对象

    最近,我们讨论了关于 WebService 的相关问题。目前在 Smart 中,可发布两种类型的 WebService,它们是:SOAP 服务 与 REST 服务,您可以根据需要自由选择。

    今天,我要与大家分享的是,在 WebService 中,处理复杂 Java 数据类型的解决方案。

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

    请看下面这个例子:

    @WebService(value = "/soap/ProductService", type = WebService.Type.SOAP)
    public interface ProductService {
    
        boolean createProduct(Map<String, Object> productFieldMap);
    }

    为了创建一个 Product,我们需要传递一个 Map<String, Object> 类型的参数。实现该接口应该不难,关键是客户端能否将 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<String, Object> productFieldMap = new HashMap<String, Object>();
            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 的代理对象,所以接下来的一切都是那么简单!

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

    org.apache.cxf.interceptor.Fault: Marshalling Error: java.util.Map is not known to this context
        at org.apache.cxf.jaxb.JAXBEncoderDecoder.marshall(JAXBEncoderDecoder.java:265)
        at org.apache.cxf.jaxb.io.DataWriterImpl.write(DataWriterImpl.java:169)
        at org.apache.cxf.interceptor.AbstractOutDatabindingInterceptor.writeParts(AbstractOutDatabindingInterceptor.java:114)
        at org.apache.cxf.interceptor.BareOutInterceptor.handleMessage(BareOutInterceptor.java:68)
        at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:272)
        at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:565)
        at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:474)
        at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:377)
        at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:330)
        at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:96)
        at org.apache.cxf.frontend.ClientProxy.invoke(ClientProxy.java:81)
        at com.sun.proxy.$Proxy31.createProduct(Unknown Source)
        at com.smart.sample.test.ProductServiceSOAPTest.createProductTest(ProductServiceSOAPTest.java:41)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
        at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
        at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:202)
        at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:65)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
    Caused by: javax.xml.bind.MarshalException
     - with linked exception:
    [javax.xml.bind.JAXBException: java.util.Map is not known to this context]
        at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:326)
        at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:251)
        at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:75)
        at org.apache.cxf.jaxb.JAXBEncoderDecoder.writeObject(JAXBEncoderDecoder.java:612)
        at org.apache.cxf.jaxb.JAXBEncoderDecoder.marshall(JAXBEncoderDecoder.java:240)
        ... 38 more
    Caused by: javax.xml.bind.JAXBException: java.util.Map is not known to this context
        at com.sun.xml.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:247)
        at com.sun.xml.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:262)
        at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:148)
        at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:131)
        at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeBody(ElementBeanInfoImpl.java:333)
        at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:340)
        at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl.serializeRoot(ElementBeanInfoImpl.java:76)
        at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:494)
        at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:323)
        ... 42 more
    Caused by: javax.xml.bind.JAXBException: java.util.Map is not known to this context
        at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:624)
        at com.sun.xml.bind.v2.runtime.ElementBeanInfoImpl$1.serializeBody(ElementBeanInfoImpl.java:145)
        ... 48 more
    ...

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

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

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

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

    我们做的有两件事情:

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

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

    Come on!

    public class StringObjectMapAdapter extends XmlAdapter<StringObjectMapAdapter.Data, Map<String, Object>> {
    
        @Override
        public Map<String, Object> unmarshal(Data data) throws Exception {
            Map<String, Object> map = new HashMap<String, Object>();
            for (Data.Entry entry : data.getList()) {
                map.put(entry.getKey(), entry.getValue());
            }
            return map;
        }
    
        @Override
        public Data marshal(Map<String, Object> map) throws Exception {
            Data data = new Data();
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                data.addEntry(entry.getKey(), entry.getValue());
            }
            return data;
        }
    
        public static class Data {
    
            private List<Entry> list = new ArrayList<Entry>();
    
            public void addEntry(String fieldName, Object fieldValue) {
                Entry entry = new Entry();
                entry.setKey(fieldName);
                entry.setValue(fieldValue);
                list.add(entry);
            }
    
            public List<Entry> getList() {
                return list;
            }
    
            public void setList(List<Entry> 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,只需实现两个方法即可:

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

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

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

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

    @WebService(value = "/soap/ProductService", type = WebService.Type.SOAP)
    public interface ProductService {
    
        boolean createProduct(@XmlJavaTypeAdapter(StringObjectMapAdapter.class) Map<String, Object> productFieldMap);
    }

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

    在这个解决方案中比较复杂的就是 StringObjectMapAdapter 了,而且我们要知道,它仅仅能处理 Map<String, Object> 类型的数据而已,对于其它不同泛型的 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<String, Object> 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<String, Object> productFieldMap = new HashMap<String, Object>();
            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 解决方案吗?将来有机会再与大家讨论这方面的问题吧!

  • 相关阅读:
    文本环加载动画html代码
    自己总结的特殊符号,大家有用可以复制
    《名侦探柯南》动画登陆bilibili
    复古风,vivo申请滑盖结构的手机外观专利
    GitHub 热榜第一!可交互全球疫情地图
    最新免费领哔哩哔哩会员教程
    百度网盘免费领取会员
    Apple Watch Series 6或将增加焦虑监测和睡眠追踪功能
    谷歌更新其社区人员流动报告 帮助了解民众遵守封锁令的情况
    计算5的n次幂html代码
  • 原文地址:https://www.cnblogs.com/henuyuxiang/p/6721541.html
Copyright © 2011-2022 走看看