这两天在工作中要和一个短信供应商进行接口对接,该供应商的短信接口是通过WebService实现的。
很久不用WebService的我便重新温习了WebService。
什么是WebService?
Web Service技术, 能使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件, 就可相互交换数据或集成。依据Web Service规范实施的应用之间, 无论它们所使用的语言、 平台或内部协议是什么, 都可以相互交换数据。
简单的说,WebService就是一种跨编程语言和跨操作系统平台的远程调用技术。所谓跨编程语言和跨操作平台,就是说服务端程序采用java编写,客户端程序则可以采用其他编程语言编写,反之亦然。跨操作系统平台则是指服务端程序和客户端程序可以在不同的操作系统上运行。 远程调用,就是一台计算机的应用可以调用其他计算机上的应用。例如:支付宝,支付宝并没有银行卡等数据,它只是去调用银行提供的接口来获得数据。还有天气预报等,也是气象局把自己的系统服务以webservice服务的形式暴露出来,让第三方网站和程序可以调用这些服务功能。
WebService原理
XML,SOAP和WSDL就是构成WebService平台的三大技术 。
WebService采用Http协议来在客户端和服务端之间传输数据。WebService使用XML来封装数据,XML主要的优点在于它是跨平台的。
WebService通过HTTP协议发送请求和接收结果时,发送的请求内容和结果内容都采用XML格式封装,并增加了一些特定的HTTP消息头,以说明HTTP消息的内容格式,这些特定的HTTP消息头和XML内容格式就是SOAP协议规定的。
WebService服务器端首先要通过一个WSDL文件来说明自己有什么服务可以对外调用。简单的说,WSDL就像是一个说明书,用于描述WebService及其方法、参数和返回值。 WSDL文件保存在Web服务器上,通过一个url地址就可以访问到它。客户端要调用一个WebService服务之前,要知道该服务的WSDL文件的地址。WebService服务提供商可以通过两种方式来暴露它的WSDL文件地址:1.注册到UDDI服务器,以便被人查找;2.直接告诉给客户端调用者。
WebService交互的过程就是,WebService遵循SOAP协议通过XML封装数据,然后由Http协议来传输数据。
JAVA WebService规范
WSDL
WSDL(Web Services Description Language), web服务描述语言,他是webservice服务端使用说明书,说明服务端接口、方法、参数和返回值,WSDL是随服务发布成功,自动生成,无需编写。
文档结构
Service:相关端口的集合,包括其关联的接口、操作、消息等。
Binding:特定端口类型的具体协议和数据格式规范
portType: 服务端点,描述 web service可被执行的操作方法,以及相关的消息,通过binding指向portType
message: 定义一个操作(方法)的数据参数
types: 定义 web service 使用的全部数据类型
阅读方式
WSDL文档应该从下往上阅读。
1.先看service标签,看相应port的binding属性,然后通过值查找上面的binding标签。
2.通过binding标签可以获得具体协议等信息,然后查看binding的type属性
3.通过binding的type属性,查找对应的portType,可以获得可操作的方法和参数、返回值等。
4.通过portType下的operation标签的message属性,可以向上查找message获取具体的数据参数信息。
SOAP
SOAP即简单对象访问协议,他是使用http发送的XML格式的数据,它可以跨平台,跨防火墙,SOAP不是webservice的专有协议。
SOAP=http+xml
SOAP结构
必需的 Envelope 元素,可把此 XML 文档标识为一条 SOAP 消息
可选的 Header 元素,包含头部信息
必需的 Body 元素,包含所有的调用和响应信息
可选的 Fault 元素,提供有关在处理此消息所发生错误的信息
Java 中共有三种WebService 规范,分别是JAXM&SAAJ、JAX-WS(JAX-RPC)、JAX-RS。
(1)JAX-WS:
JAX-WS(Java API For XML-WebService)。早期的基于SOAP 的JAVA 的Web 服务规范JAX-RPC(java API For XML-Remote Procedure Call)目前已经被JAX-WS 规范取代,JAX-WS 是JAX-RPC 的演进版本,但JAX-WS 并不完全向后兼容JAX-RPC,二者最大的区别就是RPC/encoded 样式的WSDL,JAX-WS 已经不提供这种支持。JAX-RPC 的API 从JAVA EE5 开始已经移除,如果你使用J2EE1.4,其API 位于javax.xml.rpc.包。JAX-WS(JSR 224)规范的API 位于javax.xml.ws.包,其中大部分都是注解,提供API 操作Web 服务(通常在客户端使用的较多,由于客户端可以借助SDK 生成,因此这个包中的API 我们较少会直接使用)。
(2)JAXM&SAAJ:
JAXM(JAVA API For XML Message)主要定义了包含了发送和接收消息所需的API,相当于Web 服务的服务器端,其API 位于javax.messaging.*包,它是Java EE 的可选包,因此你需要单独下载。
SAAJ(SOAP With Attachment API For Java,JSR 67)是与JAXM 搭配使用的API,为构建SOAP 包和解析SOAP 包提供了重要的支持,支持附件传输,它在服务器端、客户端都需要使用。这里还要提到的是SAAJ 规范,其API 位于javax.xml.soap.*包。
JAXM&SAAJ 与JAX-WS 都是基于SOAP 的Web 服务,相比之下JAXM&SAAJ 暴漏了SOAP更多的底层细节,编码比较麻烦,而JAX-WS 更加抽象,隐藏了更多的细节,更加面向对象,实现起来你基本上不需要关心SOAP 的任何细节。那么如果你想控制SOAP 消息的更多细节,可以使用JAXM&SAAJ。
(3)JAX-RS:
JAX-RS 是JAVA 针对REST(Representation State Transfer)风格制定的一套Web 服务规范,由于推出的较晚,该规范(JSR 311,目前JAX-RS 的版本为1.0)并未随JDK1.6 一起发行。
WebService的一般实现
1、 JDK开发webservice方式
java的JDK为我们实现了webservice的实现方式。wsimport是jdk自带的webservice客户端工具,可以根据wsdl文档生成客户端调用代码(java代码).当然,无论服务器端的WebService是用什么语言写的,都可以生成调用webservice的客户端代码。
如:
wsimport -s . http://127.0.0.1:12345/weather?wsdl
-s是指编译出源代码文件,后面的.(点)指將代码放到当前目录下.
最后面的http….是指获取wsdl说明书的地址
- 生成客户端调用方式
注意:该种方式使用简单,但一些关键的元素在代码生成时写死到生成代码中,不方便维护,所以仅用于测试。
(1)Wsimport命令介绍
Wsimport就是jdk(1.6版本之后)提供的的一个工具,他的作用就是根据WSDL地址生成客户端代码;
Wsimport位置JAVA_HOME/bin
Wsimport常用的参数:
-s,生成java文件的
-d,生成class文件的,默认的参数
-p,指定包名的,如果不加该参数,默认包名就是wsdl文档中的命名空间的倒序;
Wsimport仅支持SOAP1.1客户端的生成;
(2)调用公网手机号归属地查询服务
第一步:wsimport生成客户端代码
wsimport -p cn.itcast.mobile -s . http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx?wsdl
第二步:阅读使用说明书(wsdl文档),使用生成的客户端代码调用服务端
public class MobileClient {
public static void main(String[] args) {
//创建服务访问点集合的对象,例如:<wsdl:service name=“MobileCodeWS”>
MobileCodeWS mobileCodeWS = new MobileCodeWS();
//获取服务实现类,例如:<wsdl:portType name=“MobileCodeWSSoap”>,port–binding–portType
//MobileCodeWSSoap mobileCodeWSSoap = mobileCodeWS.getPort(MobileCodeWSSoap.class);
//根据服务访问点的集合中的服务访问点的绑定对象来获得绑定的服务类
//获得服务类的方式是get+服务访问点的name:getWSServerPort
MobileCodeWSSoap mobileCodeWSSoap = mobileCodeWS.getMobileCodeWSSoap();
String mobileCodeInfo = mobileCodeWSSoap.getMobileCodeInfo(“18518114962”, “”);
System.out.println(mobileCodeInfo);
}
}
2、Axis2方式
这种方式在以前没有研究过,只是在最近才尝试使用,但使用的过程中发现需要引用的第三方jar包实在太多。查询网络,竟然没找到一个完整的maven引用文件,折腾了半天直接放弃(项目比较赶)。
3、xfire
这种方式是我以前有过接触的,也是我本次想到的第一种解决方式,因为该方式有一种调用方式确实很不错,如下:
//在线信使调用接口地址
String url="http://address:11009/ocservice/service/msgWebService?wsdl";
try {
Client c = new Client(new URL(url));
Object[] o = c.invoke(msgType, new Object[]{proxy,pwd,mobile,content,seqno});
returnStr = o[0].toString();
if("1".equals(returnStr)){
return new SendMsgResult(returnStr, "发送成功", true);
}else
return new SendMsgResult(returnStr, returnStr, false);
} catch (Exception e) {
logger.error("短信发送异常",e);
return new SendMsgResult("-1", "短信服务异常,请联系管理员", false);
}
但是后面使用调新的接口时,发现测试过程中一直在报参数错误的问题,关键是参数再三确认了是无误的,找不出错误的原因(放弃)。
在Axis2,xfire都不能使用的情况下,我尝试了JDK自带的webservice方式(实在是不想用,我掉个接口,生成这么多代码),还是不行,提示找不到某个类中的某个方法,一般使用不会出现这问题,我的原因是我们项目中先前有使用xfire,xfire中有个类和JDK自带的webservice需要使用的一个类实现了相同接口,单实现冲突了。导致使用了JDK自带的webservice就不能使用xfire。
调用个webservice接口至于这么纠结吗?一个个实现咋这么复杂呢,jar包的引用无数个,杂七杂八的代码生成一大堆。静下心来想了一想。
终极解决方案
webservice的本质是什么?
WebService是遵循SOAP协议通过XML封装数据,然后由Http协议来传输数据。
讲的天花乱坠,说白了不就是通过http协议发送的一个xml数据吗,只是这个数据要遵循SOAP协议。
http数据发送容易实现,但是这个SOAP协议的解析我们得借助工具才行。从网上下载SoapUi 5.0(该工具可以测试Soap、rest等请求)
下载完成新建一个SOAP调用,接口调用成功,并且发现SoapUi中实际使用的也是我将要使用的这种模式。只是他可以通过解析wsdl来换取的xml的格式(这正是我们需要的)。
在SoapUi中我们可以获取到我们需要的两个东西。
1、xml数据的格式
实际上webservice就是通过http请求给服务器上发送了这个一个数据。(这个数据是基于soap协议的,是xml内容格式的)。
http请求头信息
注意Content-Type: text/xml ,这个很关键,如果不是这个的话,服务端就没法解析传输数据中xml的内容。
先在SoapUi 中测试下接口,输入参数,点击三角按钮,收到测试返回的数据(也是一个xml格式的)
返回结果根据接口定义的不同,返回的数据也不一样。我调用的这个接口返回结果为0,说明成功,并且手机上也收到了短信。说明接口没有问题。返回的数据虽然是xml格式,我们有需要的话可以根据数据格式获取我们需要的内容。
开始编码:
/**
* 发送http请求
**/
public static String sendPost(String url, String param) {
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("Accept-Encoding", "gzip,deflate");
// 很关键 一定要是xml格式的
conn.setRequestProperty("Content-Type", "text/xml;charset=UTF-8");
conn.setRequestProperty("SOAPAction", "");
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
conn.getOutputStream().write(param.getBytes("UTF-8"));
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
} finally {// 使用finally块来关闭输出流、输入流
try {
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
private SendMsgResult send(String mobile, String content,String softwareSerialNo,String key){
try {
// 组装SOAP协议的xml数据
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ws=\"http://ws.hdwinfo.cn/\">");
stringBuffer.append(" <soapenv:Header/>");
stringBuffer.append(" <soapenv:Body>");
stringBuffer.append(" <ws:sendSMS>");
stringBuffer.append(" <!--Optional:-->");
stringBuffer.append(" <arg0>").append(softwareSerialNo).append("</arg0>");
stringBuffer.append(" <!--Optional:-->");
stringBuffer.append(" <arg1>").append(key).append("</arg1>");
stringBuffer.append(" <!--Optional:-->");
stringBuffer.append(" <arg2></arg2>");
stringBuffer.append(" <!--Zero or more repetitions:-->");
stringBuffer.append(" <arg3>").append(mobile).append("</arg3>");
stringBuffer.append(" <!--Optional:-->");
stringBuffer.append(" <arg4>【短信签名】 ").append(content).append("</arg4>");
stringBuffer.append(" <!--Optional:-->");
stringBuffer.append(" <arg5>802</arg5>");
stringBuffer.append(" <!--Optional:-->");
stringBuffer.append(" <arg6>GBK</arg6>");
stringBuffer.append(" <arg7>1</arg7>");
stringBuffer.append(" <arg8>0</arg8>");
stringBuffer.append(" </ws:sendSMS>");
stringBuffer.append(" </soapenv:Body>");
stringBuffer.append(" </soapenv:Envelope>");
String param = stringBuffer.toString();
String xml = sendPost("http://ws.******.cn:8080/sdk/SDKService", param);
if(StringUtil.isEmpty(xml)){
return new SendMsgResult("-1", "短信服务异常,请联系管理员", false);
}
// 按照业务解析返回的结果
String returnStr = xml.substring(xml.indexOf("<return>"), xml.indexOf("</return>")).substring("<return>".length());
if("0".equals(returnStr)){
return new SendMsgResult(returnStr, "发送成功", true);
}else{
return new SendMsgResult(returnStr,"发送失败,错误码:" + returnStr, false);
}
} catch (Exception e) {
logger.error("短信发送异常",e);
return new SendMsgResult("-1", "短信服务异常,请联系管理员!"+e.getMessage(), false);
}
}
测试通过!
这种方式对第三方依赖基本可以忽略,一两个自己写的方法直接搞定。复杂一点的在于协议数据的组装。
以上是个人总结,写的时候借鉴了: https://blog.csdn.net/c99463904/article/details/76018436
关于webservice在实际中基本没有接触,解决方法也基本是基于服务的调用。有什么更好的方式,路过、看到的大大们请不吝赐教,谢谢!