java调用WebService(未完成)记录篇
背景:
因工作需要和一个Sap相关系统以WebService的方式进行接口联调,之前仅听过这种技术,但并没有实操过,所以将本次开发相关的踩坑进行记录
通过一个实例来认识webservice
服务端
首先我们先写一个简单的接口,不同于一般的接口就是我们要使用@WebService,以及需要将这个接口进行发布
public interface ExampleService { String sayHi(String request); } @WebService public class ExampleServiceImpl implements ExampleService{ @Override public String sayHi(String request) { return "request:"+request+"response:hi"; } } public class ExampleServe { public static void main(String[] args) { //接口发布的地址 Endpoint.publish("http://localhost:8080/example",new ExampleServiceImpl()); //查看WebService服务是否启动 url+?wsdl //http://localhost:8080/example?wsdl是否能显示 System.err.println("服务发布成功"); } }
然后,通过请求http://localhost:8080/example?wsdl我们可以看到下面这个xml结构的文档
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://summary.webservice.com/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="ExampleServiceImplService" targetNamespace="http://summary.webservice.com/"> <wsdl:types> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://summary.webservice.com/" elementFormDefault="unqualified" targetNamespace="http://summary.webservice.com/" version="1.0"> <xs:element name="sayHi" type="tns:sayHi"/> <xs:element name="sayHiResponse" type="tns:sayHiResponse"/> <xs:complexType name="sayHi"> <xs:sequence> <xs:element minOccurs="0" name="arg0" type="xs:string"/> </xs:sequence> </xs:complexType> <xs:complexType name="sayHiResponse"> <xs:sequence> <xs:element minOccurs="0" name="return" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:schema> </wsdl:types> <wsdl:message name="sayHiResponse"> <wsdl:part element="tns:sayHiResponse" name="parameters"> </wsdl:part> </wsdl:message> <wsdl:message name="sayHi"> <wsdl:part element="tns:sayHi" name="parameters"> </wsdl:part> </wsdl:message> <wsdl:portType name="ExampleServiceImpl"> <wsdl:operation name="sayHi"> <wsdl:input message="tns:sayHi" name="sayHi"> </wsdl:input> <wsdl:output message="tns:sayHiResponse" name="sayHiResponse"> </wsdl:output> </wsdl:operation> </wsdl:portType> <wsdl:binding name="ExampleServiceImplServiceSoapBinding" type="tns:ExampleServiceImpl"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="sayHi"> <soap:operation soapAction="" style="document"/> <wsdl:input name="sayHi"> <soap:body use="literal"/> </wsdl:input> <wsdl:output name="sayHiResponse"> <soap:body use="literal"/> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="ExampleServiceImplService"> <wsdl:port binding="tns:ExampleServiceImplServiceSoapBinding" name="ExampleServiceImplPort"> <soap:address location="http://localhost:8080/example"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
客户端
接下来我们通过jdk自带的wsimport命令自动将wsdl解析为我们熟悉的java调用类
执行下述命令,wsimport具体参数见附录
wsimport -s ./src/main/java/ -p com.webservice.summary.client -encoding utf-8 http://localhost:8080/example\?wsdl -d ./target/classes
会生成我们需要的文件,大致包含以下这些
写一个客户端类,在确保上面编写的server启动的情况下,执行就会获得调用结果
public class ExampleClient { public static void main(String[] args) { ExampleServiceImplService exampleServiceImplService=new ExampleServiceImplService(); ExampleServiceImpl exampleServiceImplPort = exampleServiceImplService.getExampleServiceImplPort(); String result = exampleServiceImplPort.sayHi("这是参数"); System.out.println(result); } }
总的来说,其实很简单,甚至具体业务中我们作为调用方并不需要编写和发布service,只需要将服务方暴露的wsdl文件或者地址进行转换,进行调用就好了。
WebService的一些概念
官方的概念感兴趣的可以自己了解
https://en.wikipedia.org/wiki/Web_service
https://baike.baidu.com/item/Web%20Service/1215039?fromtitle=webservice&fromid=2342584&fr=aladdin
在我看来,其实就是一种通信的解决方案,如Dubbo一样,处理应用通信的问题。核心包含wsdl和soap;
wsdl文件其实就是xml格式的文件,它定义了服务的相关信息,而soap目前我只清楚是一种协议,包含soap1.1和soap1.2版本,这两个版本影响了我们服务发布和调用的版本
踩坑之旅
这次对接的系统,使用的sap管理和发布webservice服务的,由于我是初次应用webservice,加之他们内网鉴权限制、WSDL鉴权以及接口鉴权,中间一度很不美妙,
务必保证提供方的wsdl可以通过浏览器打开
sap提供的wsdl
sap提供的wsdl不是普通的以?wsdl结尾的链接,而是一个结构类似于下面这个链接的格式http://*******.com:8001/sap/bc/srt/wsdl/flv_10002A111AD1/bndg_url/sap/bc/srt/rfc/sap/yws_get_str/200/yws_get_str/yws_get_str?sap-client=200(网上随便找的)
当时我直接拿着这个链接替换了上面wsimport命令最后的链接,结果竟然报错了;不过好在对方还提供了wsdl文件,结果我将链接替换为文件仍然还是这个错
无法读取 WSDL 文档:*******
原因为 1) 找不到文档; 2) 无法读取文档; 3) 文档的根元素不是 <wsdl:definitions>。
[ERROR] failed.noservice=在提供的 WSDL 中找不到 wsdl:service:
需要至少提供一个 WSDL, 该 WSDL 至少具有一个服务定义。
在我将自己的文件和他们给的文件多次对比后发现,他们所提供的文件,很多<wsdl前面都有一个-,网络上只找到一个类似的案例,但并没有什么参考意义,本能的我认为这像是注释一样的符号应该删掉,于是我将所有-删除,再次尝试
-<wsdl
结果出现了新的错误,在这里这个错误先按下不表,下面还会出现
(这里我在写这篇文档复现时,报的错和上图不一样,错误: 类重复: com.webservice.summary.client.TESTTESTNew,但很相似,不清楚是不是jdk版本的缘故)
在和对面几次沟通之后,对方提供了一套账号密码,同时提供了代理ip(之前都是无法通过浏览器打开的),但是文件里为什么有-,他们确实不清楚
wsdl鉴权问题
由于存在账号密码,所以我们必须使用指定的参数
wsimport使用authfile
wsimport -Xauthfile sap.txt http://*******.com:8001/sap/bc/srt/wsdl/flv_10002A111AD1/bndg_url/sap/bc/srt/rfc/sap/yws_get_str/200/yws_get_str/yws_get_str?sap-client=200
其中,sap.txt内容为 http[s]://user:password@host:port//<url-path>,例如,账号名为:test,密码为:123456,那内容就是
test:123456@http://*******.com:8001/sap/bc/srt/wsdl/flv_10002A111AD1/bndg_url/sap/bc/srt/rfc/sap/yws_get_str/200/yws_get_str/yws_get_str?sap-client=200
然后报错了,我猜测是因为对方提供的密码中带有@字符,导致格式识别失败了;但由于我并没有查询到怎么给服务端wsdl生成账号密码,所以我无法自己复现实例
****************不是有效的授权信息格式。格式应为 http[s]://user:pa
ssword@host:port//<url-path>。
*************
需要授权, 请在C:\Us
ers\****\.metro\auth中提供具有读取访问权限的授权文件, 或者使用 -Xauthfile 指定授权文
件并在每一行上使用以下格式提供授权信息: http[s]://user:password@host:port//<url-path>
不过好在,在浏览器输入对方的wsdl,会自动弹出账号密码输入框,输入之后,就可以正常看到wsdl文件内容了。因此,我们可以选择将wsdl文件下载下来并通过文件生成命令。比如,我将内容保存在example.wsdl文件
wsimport example.wsdl
方法重名
不出所料,又报错了
[WARNING] 忽略 SOAP 端口 "example_interface_12": 它使用非标准 SOAP 1.2 绑定。
必须指定 "-extension" 选项以使用此绑定。
正在生成代码...
[ERROR] 无法生成 SEI, 类com.sap.document.sap.soap.functions.mc_style.example_interface已存在
。请使用 JAX-WS 定制设置重命名 wsdl:portType "{urn:sap-com:document:sap:soap:functions:mc-style}ZC
example_interface"
然后我在wsimport [ERROR] 无法生成 SEI, 类com.xx.XX 已存在。请使用JAX-WS定制设置重命名 wsdl:portType..._bnmnba的博客-CSDN博客 这篇博客中,看到了类似的情况以及处理方式。
为什么重?
根据实验可知,wsimport命令在生成时,对于接口内的方法会生成java类,接口也会生成java类。
报错的原因是猜测是因为提供的提供的这个接口里,有一个和接口名一模一样的方法。所以无法生成两个同名的类。但是,我自己在测试的时候,发现重名的话其实会生成一个_Type的类,于是,这又变成的一个未知项
解决方案
但是柳暗花明又一村,万能的互联网告诉我,我可以用apache-cxf来生成java类,于是我在idea配置了cxf的路徑
然后在idea的工具栏tools-webservice-Generate java code from wsdl,然后就成功了
参数问题
至此,起码代码生成成功了,下面的问题就是调用了。
通过cxf生成的代码中,会有一个测试类,可以方便去调用;
鉴权:
public final class ExampleServiceImpl_ExampleServiceImplPort_Client { private static final QName SERVICE_NAME = new QName("http://summary.webservice.com/", "ExampleServiceImplService"); private ExampleServiceImpl_ExampleServiceImplPort_Client() { } public static void main(String args[]) throws java.lang.Exception { URL wsdlURL = ExampleServiceImplService.WSDL_LOCATION; if (args.length > 0 && args[0] != null && !"".equals(args[0])) { File wsdlFile = new File(args[0]); try { if (wsdlFile.exists()) { wsdlURL = wsdlFile.toURI().toURL(); } else { wsdlURL = new URL(args[0]); } } catch (MalformedURLException e) { e.printStackTrace(); } } ExampleServiceImplService ss = new ExampleServiceImplService(wsdlURL, SERVICE_NAME); ExampleServiceImpl port = ss.getExampleServiceImplPort(); { System.out.println("Invoking sayHi..."); java.lang.String _sayHi_arg0 = ""; java.lang.String _sayHi__return = port.sayHi(_sayHi_arg0); System.out.println("sayHi.result=" + _sayHi__return); } System.exit(0); } }
直接执行测试类,会报如下的错误
Server returned HTTP response code: 401 for URL
是因为我们的接口是带着账号密码的,然后我首先查到的是通过下面这种方式指定账号密码:
ExampleServiceImpl.java
url = new URL("*******"); Authenticator.setDefault( new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication( "username", "passwd".toCharArray()); } }); WSDL_LOCATION = url;
运行报错为:
None of the policy alternatives can be satisfied.
在stack搜了一下发现了这篇文章,Apache CXF - None of the policy alternatives can be satisfied,看了好多篇,指定账号密码都大同小异,我采用的是下面这种方式
final String username = ""; final String password = ""; JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); factory.setAddress(address); factory.setServiceClass(ExampleServiceImpl.class); factory.setUsername(username); factory.setPassword(password); ExampleServiceImpl bean = (ExampleServiceImpl) factory.create(); //组装参数 ... //组装参数结束 bean.hi(参数)
参数:
执行上面的代码,如果参数没有问题,那么就可以正常返回调用结果了,但如果你要是出现下述的错误,很大可能是因为参数问题
Web service processing error; more details in the web service error log on provider side
普通的参数问题我这里就不提了,需要和提供方联调;需要特别注意的是时间、日期类型会被自动转为XMLGregorianCalendar
注意点
- 如果本来将代码放在A包下,因某些业务将代码迁移到B包下之后,记得修改interface类的@RequestWrapper和@ResponseWrapper后的className里的包名
- 在对应的serviceImpl里面:url = new URL("x"); x是wsdl的地址,如果是sap的话,那就是浏览器可以打开的那个地址
- factory.setAddress(address); 这里的address不是wsdl地址,而是你的wsdl和下图位置一样的地方的值
附录
Jax-ws
https://svn.ssec.wisc.edu/repos/APSPS/trunk/SPS/LIB/jaxws_2_1_7/jaxws-ri/docs/UsersGuide.html
网络上可用的wsdl网站
http://www.webxml.com.cn/zh_cn/index.aspx
wsimport 命令
wsimport -help 用法: wsimport [options] <WSDL_URI> \其中 [options] 包括: -b <path> 指定 jaxws/jaxb 绑定文件或附加模式 (每个 <path> 都必须具有自己的 -b) -B<jaxbOption> 将此选项传递给 JAXB 模式编译器 -catalog <file> 指定用于解析外部实体引用的目录文件 支持 TR9401, XCatalog 和 OASIS XML 目录格式。 -d <directory> 指定放置生成的输出文件的位置 -encoding <encoding> 指定源文件所使用的字符编码 -extension 允许供应商扩展 - 不按规范 指定功能。使用扩展可能会 导致应用程序不可移植或 无法与其他实现进行互操作 -help 显示帮助 -httpproxy:<host>:<port> 指定 HTTP 代理服务器 (端口默认为 8080) -keep 保留生成的文件 -p <pkg> 指定目标程序包 -quiet 隐藏 wsimport 输出 -s <directory> 指定放置生成的源文件的位置 -target <version> 按给定的 JAXWS 规范版本生成代码 默认为 2.2, 接受的值为 2.0, 2.1 和 2.2 例如, 2.0 将为 JAXWS 2.0 规范生成兼容的代码 -verbose 有关编译器在执行什么操作的输出消息 -version 输出版本信息 -wsdllocation <location> @WebServiceClient.wsdlLocation 值 -clientjar <jarfile> 创建生成的 Artifact 的 jar 文件以及 调用 Web 服务所需的 WSDL 元数据。 -generateJWS 生成存根 JWS 实现文件 -implDestDir <directory> 指定生成 JWS 实现文件的位置 -implServiceName <name> 生成的 JWS 实现的服务名的本地部分 -implPortName <name> 生成的 JWS 实现的端口名的本地部分 \扩展: -XadditionalHeaders 映射标头不绑定到请求或响应消息不绑定到 Java 方法参数 -Xauthfile 用于传送以下格式的授权信息的文件: http://username:password@example.org/stock?wsdl -Xdebug 输出调试信息 -Xno-addressing-databinding 允许 W3C EndpointReferenceType 到 Java 的绑定 -Xnocompile 不编译生成的 Java 文件 -XdisableAuthenticator 禁用由 JAX-WS RI 使用的验证程序, 将忽略 -Xauthfile 选项 (如果设置) -XdisableSSLHostnameVerification 在提取 wsdl 时禁用 SSL 主机名 验证 \示例: wsimport stock.wsdl -b stock.xml -b stock.xjb wsimport -d generated http://example.org/stock?wsdl
相关文档:
其他很有用的文档