加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

如何处理 WebService 中的 Map 对象?

发布时间:2020-12-16 23:57:30 所属栏目:安全 来源:网络整理
导读:最近,我们讨论了关于 WebService 的相关问题。 目前 在 Smart 中,可发布两种类型的? WebService,它们是: SOAP 服务 与 REST 服务,您可以根据需要自由选择。 今天,我要与大家分享的是,在 WebService 中,处理复杂 Java 数据类型的解决方案。 对于普通

最近,我们讨论了关于 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,?"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 解决方案吗?将来有机会再与大家讨论这方面的问题吧!

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读