zoukankan      html  css  js  c++  java
  • 认识 WebService

    什么是服务?

    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 该用什么方式导出客户端代码呢?

    请看这篇文章:https://www.cnblogs.com/jwen1994/p/10630417.html

  • 相关阅读:
    es6新增特性,数组的操作方法
    导航钩子有哪几种,如何将数据传入下一个点击的路由页面
    父组件如何获取子组件数据,子组件如何获取父组件数据,父子组件如何传值
    watch和computed的区别
    vue页面中定时器的使用
    table表格,顶端对齐
    计算机组成原理8-FPGA、ASIC、TPU、虚拟机
    vue中使用socket连接后台
    计算机组成原理7-CISC和RISC、GPU
    计算机组成原理6-流水线、多发射和超标量、SIMD、异常
  • 原文地址:https://www.cnblogs.com/jwen1994/p/10585297.html
Copyright © 2011-2022 走看看