什么是服务?
1)现在的应用程序变得越来越复杂,甚至只靠单一的应用程序无法完成全部的工作。更别说只使用一种语言了。
2)大家在写应用程序查询数据库时,并没有考虑过为什么可以将查询结果返回给上层的应用程序,甚至认为,这就是数据库应该做的,其实不然,这是数据库通过 TCP/IP 协议与另一个应用程序进行交流的结果,而上层是什么样的应用程序,是用什么语言,数据库本身并不知道,它只知道接收到了一份协议,这就是 SQL92 查询标准协议。
3)既然数据库可以依据某些标准对外部其他应用程序提供服务、而且不关心对方使用什么语言,那我们为什么就不能实现跨平台、跨语言的服务呢?只要我们用 Java 写的代码,可以被任意的语言所调用,我们就实现了跨平台,跨语言的服务!
复杂的网络应用:
什么是WebService
WebService 定义:顾名思义就是基于Web的服务。它使用Web(HTTP)方式,接收和响应外部系统的某种请求,从而实现远程调用。
Webservice 理解:我们可以调用互联网上查询天气信息Web服务,然后将它嵌入到我们的程序(C/S或B/S程序)当中来,当用户从我们的网点看到天气信息时,他会认为我们为他提供了很多的信息服务,但其实我们什么也没有做,只是简单了调用了一下服务器上的一段代码而已。WebService 可以将你的服务(一段代码)发布到互联网上让别人去调用,也可以调用别人机器上发布的 WebService,就像使用自己的代码一样。
既然 WebService 是一种远程调用技术,那么最直接的问题就是:1、我怎么去调用别人的程序。2、我的程序怎么才能被别人调用
作为一种通用技术,那么是否有通用的使用说明书呢?答案是肯定的:WSDL 就是 WebService 的说明书(网上有更详细的教程,这里不特别介绍)
WSDL:WebService Description Language——Web服务描述语言。 通过XML形式说明服务在什么地方——地址。通过XML形式说明服务提供什么样的方法——如何调用。
分享一个 Webservice 服务网站:http://www.webxml.com.cn
WSDL文档从下往上读,它包含几个值得注意的关键节点
Types:数据类型定义的容器,它使用某种类型系统(一般地使用XML Schema中的类型系统)。(入参和出参的数据类型)
Message:通信消息的数据结构的抽象类型化定义。使用Types所定义的类型来定义整个消息的数据结构(入参和出参)。
Operation:对服务中所支持的操作的抽象描述,一般单个Operation描述了一个访问入口的请求/响应消息对(方法)。
PortType:对于某个访问入口点类型所支持的操作的抽象集合,这些操作可以由一个或多个服务访问点来支持(服务类)。
Binding:特定服务访问点与具体服务类的绑定(不看内容,看关系)。
Port:定义为webservice单个服务访问点。
Service:相关服务访问点的集合。
下面演示 jdk 怎样通过 WSDL 生成客户端代码,通过客户端代码完成调用。
1. wsimport 是 jdk 自带的,可以根据 wsdl 文档生成客户端调用代码的工具。
2. 无论服务器端的 WebService 是用什么语言写的,都将在客户端生成Java代码。服务器端用什么写的并不重要。
3. wsimport.exe位于 JAVA_HOME\bin 目录下。
常用参数为:
1)-d<目录> - 将生成.class文件。默认参数。
2)-s<目录> - 将生成.java文件和class文件。
3)-p<生成的新包名> -将生成的类,放于指定的包下。
(wsdlurl) - http://server:port/service?wsdl,必须的参数。
注意:-s不能分开,-s后面有个小点,用于指定源代码生成的目录。点即当前目录。如果使用了-s参数则会在目录下生成两份代码,一份为.class代码。一份为.java代码。.class代码,可以经过打包以后使用。.java代码可以直接Copy到我们的项目中运行。
我们在F盘下新建一个文件夹,将生成的代码保存在此文件夹中。现在文件夹里是空的
打开命令行,输入java命令。参照上面的命令解释,下面命令的意思是:生成 java 文件和 class 文件,创建子文件夹来放这些文件
最后生成的结果如下
上面是通过直接访问WSDL的URL这种方式来生成的,我们也可以把 WSDL下载下来,比如,放在f:/wsCode/EnglishChinese.wsdl
用下面的命令
生成效果是一样的
注意:如果用第一种方式生成报错,那么就把WSDL下载下来,删除报错提示的那几行,然后用第二种方式重新生成。
原因:wsimport 只适用于 SOAP1.1 协议,如果 WebService 是用 SOAP1.2 协议发布的,则使用 wsimport 导出代码会报错,生成的文件也不全。
生成的class文件是不需要的,只需要 java 文件,接下来就是使用这些java文件。
第一步:将jdk生成的java文件复制到项目中
写测试方法进行调用测试。这其实就是调用普通的Java方法,但是需要读懂 WSDL 明白创建哪些对象,调用哪些方法,这是关键!
调用WebService步骤
1)打开WSDL文档
2)从下往上读WSDL文档,先找到Services(服务访问点集合),根据Services里面binding属性找到binding元素,再根据binding元素的type属性找到绑定的portType(服务类)
3)根据WSDL的地址生成客户端代码 wsimport -s . -p com.jwen.trans d:/wsCode/EnglishChinese.wsdl
4)把客户端代码拷贝到项目中
5)创建服务访问点集合对象
6)根据服务访问点获得服务类
7)调用服务类的方法
下面演示 jdk 发布 WebService 服务
注意:用 jdk1.6.0_21 以后的版本发布一个 WebService 服务。在JDK1.6中JAX-WS规范定义了如何发布一个WebService服务。 JAX-WS是指Java Api for XML – WebService。
与Web服务相关的类,都位于javax.xml.ws.*包中。主要类有:
a)@WebService:它是一个注解,用在类上指定将此类发布成一个webservice服务.
b)Endpoint:此类为端点服务类,它的方法publish用于将一个已经添加了@WebService 注解对象绑定到一个地址的端口上。Endpoint 是 jdk 提供的一个专门用于发布服务的类,它的publish方法接收两个参数,一个是本地的服务地址,二是提供服务的类。它位于 javax.xml.ws.* 包中。
public static Endpoint.publish(String address, Object implementor) 在给定地址处针对指定的实现者对象创建并发布端点。stop方法用于停止服务。
其他注意事项:
1)给类添加上@WebService注解后,类中所有的非静态方法都将会对外公布。不支持静态方法,final方法。
2) 如果希望某个方法(非static,非final)不对外公开,可以在方法上添加@WebMethod(exclude=true),阻止对外公开。
3) 如果一个类上,被添加了@WebService注解,则必须此类至少有一个可以公开的方法,否则将会启动失败。
4) 服务类中不能没有方法
5) @WebMethod(exclude=true)屏蔽方法
项目结构如下图所示
HelloServer.java 的代码如下:
@WebService public class HelloServer { /** * 1.需要方法权限是public * 2.不能是final类型 * 3.方法不能是静态的 * 4.服务类至少有一个方法 * @param name * @return */ public String sayHello(String name){ return name + " hello!"; } @WebMethod(exclude=true) public String sayBye(String name){ return name + " bye!"; } }
ServerPublish.java 的代码如下:
public class ServerPublish { public static void main(String[] args) { //jdk发布webservice服务, 第一个参数服务地址,第二个参数具体服务类 Endpoint.publish("http://127.0.0.1:8080/hello", new HelloServer()); } }
运行 main 方法后,在浏览器地址栏中输入 http://127.0.0.1:8080/hello?wsdl 查看 WSDL,可见,WebService成功发布。可以用前面介绍的方式进行调用。
调用WebService的方式是不是只有调用客户端代码这一种呢?显然不是,还是有其他方式的。
下面演示 ajax 调用WebService,注意运行前设置IE可信任站点和自定义级别
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <script type="text/javascript"> var xhr; function invoke(){ //创建ajax对象 xhr = new ActiveXObject("Microsoft.XMLHTTP"); //指定要访问的地址,就是我们WSDL地址,说明书 var url = "http://127.0.0.1:8080/hello?wsdl"; //打开连接,参数1.请求方式, 2,url地址. 3.是否同步,true异步,false同步 xhr.open("POST", url, true); //指定发送的数据类型 xhr.setRequestHeader("Content-Type", "text/xml;charset=UTF-8"); //设置回调函数 xhr.onreadystatechange = _back; var mytext = document.getElementById("mytext").value; //定义消息体 var data ='<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:q0="hello.ren.liang" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +'<soapenv:Body>' +'<q0:sayHello>' +' <arg0>'+mytext+'</arg0>' +'</q0:sayHello>' +'</soapenv:Body>' +'</soapenv:Envelope>'; //发送消息体 xhr.send(data); } //回调函数 function _back(){ //判断成功状态 if(xhr.readyState == 4 && xhr.status == 200){ //以文本形式 var result = xhr.responseText; //以xml文档对象 var obj = xhr.responseXML; //解析文档 var returns = obj.getElementsByTagName("return"); alert(returns[0].text); } } </script> </head> <body> <input type="text" id="mytext"> <input type="button" value="click" onclick="invoke();"> </body> </html>
Java 也可以通过 WSDL 直接调用 WebService
package com.rl.client; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.StringReader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.List; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; public class TestHttpURLConnClient { public static void main(String[] args) throws Exception { //定义webservice的URL URL url = new URL("http://127.0.0.1:8080/hello?wsdl"); //打开连接获得URLConnection URLConnection uc = url.openConnection(); //强转成HttpURLConnection HttpURLConnection httpuc = (HttpURLConnection)uc; //打开输入输出的开关 httpuc.setDoInput(true); httpuc.setDoOutput(true); //设置请求方式 httpuc.setRequestMethod("POST"); //设置content-type text/xml;charset=UTF-8 httpuc.setRequestProperty("Content-Type", "text/xml;charset=UTF-8"); //组装消息体 String data = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:q0=\"http://server.rl.com/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +"<soapenv:Body>" +" <q0:sayBye>" +" <arg0>wangwu</arg0>" +" </q0:sayBye>" +" </soapenv:Body>" +"</soapenv:Envelope>"; //根据HttpURLConnection获得输出流 OutputStream out = httpuc.getOutputStream(); //用输出流把消息发送到服务端 out.write(data.getBytes()); //如果请求成功 if(httpuc.getResponseCode() == 200){ //获得输入流 InputStream in = httpuc.getInputStream(); //使用输入缓冲区 BufferedReader br = new BufferedReader(new InputStreamReader(in)); //读取响应的消息 StringBuffer sb = new StringBuffer(); String line = null; while((line = br.readLine()) != null){ sb.append(line); } //解析消息,定义SAXReader对象 SAXReader reader = new SAXReader(); //获得文档对象 Document doc = reader.read(new StringReader(sb.toString())); //使用XPath的方式获得到return这个元素的集合 List<Element> eList = doc.selectNodes("//return"); //遍历元素集合 for(Element ele : eList){ System.out.println(ele.getText()); } } } }
上面的两种方式共同点都是需要知道消息体的组织方式,与客户端调用相比较,上面的两种方式不需要导出代码,但需要组织消息体,而客户端调用对参数的处理则方便的多,因为对象都是现成的。
关于怎样查看消息体,参考https://www.cnblogs.com/jwen1994/p/10589150.html
自动生成的 WSDL 文档的名字有时不规范,可以手动进行修改。
修改前的普通代码
@WebService public class HelloServer { @WebMethod(exclude=false) public String sayHello(String name){ return name + " hello!"; } @WebMethod(exclude=false) public String sayBye(String name){ return name + " bye!"; } }
修改后的代码
@WebService( serviceName="MyHelloServerService", portName="MyHelloServer", name="MyHelloServer", targetNamespace="hello.jwen.com" ) public class HelloServer { /** * 1.需要方法权限是public * 2.不能是final类型 * 3.方法不能是静态的 * 4.服务类至少有一个方法 * @param name * @return */ @WebMethod(exclude=false) public String sayHello(String name){ return name + " hello!"; } @WebMethod(exclude=false) public @WebResult(name="byeResult") String sayBye(@WebParam(name="personName") String name){ return name + " bye!"; } }
参数说明:
@WebService( portName="myHelloService",修改端口名字 serviceName="HelloServices",修改服务访问点集合名字 name="HelloService",修改服务类的名字 targetNamespace="hello.rl.com" 修改命名空间名字 ) @WebResult(name="sirHello")修改返回值的元素的父标签名字 @WebParam(name="sir")修改传入参数的元素的父标签名字
本文还没有解决的两个问题:
1、难道只能用 jdk 自带方法 EndPoint.publish 这种方式发布 WebService 吗?
2、使用 SOAP1.2 协议发布的 WebService 该用什么方式导出客户端代码呢?