这几天在公司的项目中,需要调用第三方短信平台的webservice接口群发短信。走了许多弯路,最终解决了问题,并记下实践的一些经验。
在前一个项目中,为外界提供了一些webservice接口,使用的是apache-CXF,这个组件用的挺多的,但限制也是有的,例如它不能调用axis2 RPC模式的接口(网上的说法)。
在之前用它发布服务,和调用传递字符串入参等,都是很方便、简单。挺好用。但是最近调用短信平台接口出现的一些问题,还是感悟很深刻。
服务接口是用axis2发布的,需要调用的方法入参和返回参数都是pojo。
按照一贯的流程,使用wsdl2java工具生成代码,使用jax-ws proxy模式调用生成代理接口,封装好入参pojo参数,执行报异常。
java.lang.IllegalArgumentException: argument type mismatch
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.apache.cxf.databinding.AbstractWrapperHelper.createWrapperObject(AbstractWrapperHelper.java:99)
at org.apache.cxf.jaxws.interceptors.WrapperClassOutInterceptor.handleMessage(WrapperClassOutInterceptor.java:105)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:255)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:516)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:313)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:265)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:285)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:271)
at cn.intellisoft.hoorayos.webservice.client.DynamicClient.executeOperation(DynamicClient.java:39)
at cn.intellisoft.hoorayos.webservice.client.DynamicClient.main(DynamicClient.java:98)
2014-12-9 9:25:16 org.apache.cxf.phase.PhaseInterceptorChain doDefaultLogging
警告: Interceptor for {http://ws.apache.org/axis2}xxtInter#{http://ws.apache.org/axis2}Downsms has thrown exception, unwinding now
org.apache.cxf.interceptor.Fault
at org.apache.cxf.databinding.AbstractWrapperHelper.createWrapperObject(AbstractWrapperHelper.java:107)
at org.apache.cxf.jaxws.interceptors.WrapperClassOutInterceptor.handleMessage(WrapperClassOutInterceptor.java:105)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:255)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:516)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:313)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:265)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:285)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:271)
at cn.intellisoft.hoorayos.webservice.client.DynamicClient.executeOperation(DynamicClient.java:39)
at cn.intellisoft.hoorayos.webservice.client.DynamicClient.main(DynamicClient.java:98)
Exception in thread "main" org.apache.cxf.interceptor.Fault
at org.apache.cxf.databinding.AbstractWrapperHelper.createWrapperObject(AbstractWrapperHelper.java:107)
at org.apache.cxf.jaxws.interceptors.WrapperClassOutInterceptor.handleMessage(WrapperClassOutInterceptor.java:105)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:255)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:516)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:313)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:265)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:285)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:271)
at cn.intellisoft.hoorayos.webservice.client.DynamicClient.executeOperation(DynamicClient.java:39)
at cn.intellisoft.hoorayos.webservice.client.DynamicClient.main(DynamicClient.java:98)
还以为是CXF版本太低造成,换版本,依然异常。在网上,找了很多CXF客户端开发的blog、参考资料,几乎没有一篇可以将客户端传递复杂类型的技术讲得通透,大都是人云亦云、copy或是简单的字符串传递demo。
动态调用走不通,就使用jax-ws proxy,也是报异常。
由于接口使用了IP鉴权,怀疑是服务端没配置好,联系短信平台的客服和开发人员,进行一系列工作。首先服务端给的说明文档太坑人,文档太潦草,没有demo,还有部分错漏。这样的服务质量,汗!
后来,服务端开发人员给了一个小demo,axis2写的RPC客户端模式。
前面的方法都走不通,只好换框架组件为axis2。
换组件后,把之前用CXF工具生成的入参、返回参数pojo复制过来,包名为服务端接口入参的包名(看wsdl文件入参的namespace,可以翻译出来),去除所有jaxb注解(jaxb注解将pojo标注成xml schema)。
服务端开发人员给的axis2 RPC客户端模式代码:
public static void main(String[] args) throws AxisFault {
RPCServiceClient serviceClient = new RPCServiceClient();
Options options = serviceClient.getOptions();
EndpointReference targetEPR = new EndpointReference("http://localhost:8088/services/smssevice");
options.setTo(targetEPR);
RequestMessage bean=new RequestMessage();//调用方法的入参
Message head=new Message();
head.setUserId("0001");
head.setTimeStamp(new SimpleDateFormat("yyyyMMddHHmmssSSSS").format(new Date(System.currentTimeMillis())));
head.setTransId("IT201412090000000000006");
RequestMessageBody body=new RequestMessageBody();
body.setContent("hello,短信测试。。。");
body.setSmstype("4");
body.setReceiveId("1");
bean.setBODY(body);
bean.setHEAD(head);
Object[] opAddEntryArgs = new Object[] {bean};
Class[] classes = new Class[] {DownsmsResponseMessage.class};
QName opAddEntry = new QName("http://ws.apache.org/axis2", "Downsms");
DownsmsResponseMessage beans=(DownsmsResponseMessage)serviceClient.invokeBlocking(opAddEntry, opAddEntryArgs, classes)[0];
System.out.println(beans.getBODY().getRemark());
}
从前面的步骤到这里,短信发送功能正常了。
把正常demo(axis2的demo)的入参和返回参数pojo复制到CXF demo中,执行下面代码,不再报异常,OK,短信发送成功。
CXF客户端动态调用代码:(executeOperation方法疯转了动态调用的细节,这里就不列出,具体可以参考前面关于CXF动态调用的篇章)
public static void main(String[] args) throws Exception {
RequestMessage bean=new RequestMessage();//调用方法的入参
Message head=new Message();
head.setUserId("0001");
head.setTimeStamp(new SimpleDateFormat("yyyyMMddHHmmssSSSS").format(new Date(System.currentTimeMillis())));
head.setTransId("IT201412090000000000006");
RequestMessageBody body=new RequestMessageBody();
body.setContent("hello,短信测试。。。");
body.setSmstype("4");
body.setReceiveId("1");
bean.setBODY(body);
bean.setHEAD(head);
//动态调用
DownsmsResponseMessage respone = (DownsmsResponseMessage)executeOperation(getClient(SERVICE_URL), "Downsms",requestReport)[0];
System.out.println(respone.getBODY().getRemark());
}
-------------------------------
前面写了很多开发中的细节,本篇主要说明总结:
1,CXF动态调用,复杂类型参数的传递,参数POJO的schema(这里包含包名,继承关系,属性类型)一定要和服务端一样,而且一般要实现序列化Serializable,不然在和服务器连接正常的情况下会报参数类型异常。
2,还有注意IP鉴权,验证权限问题,需要和服务端人员了解清楚,细读对方给的接口说明文档(针对不同常见平台都会给demo)。
3,axis2组件有些情况比CXF强大,大多数商业平台使用的是axis2。
4,webservice调用最好选择和服务端组件相同,如axis2-->axis2,CXF-->CXF。
5,百度来的资料不一定好用。