• Webservice详解


    WebService是什么? 

      1. 基于Web的服务:服务器端整出一些资源让客户端应用访问(获取数据)

      2. 一个跨语言、跨平台的规范(抽象)

      3. 多个跨平台、跨语言的应用间通信整合的方案(实际)

      以各个网站显示天气预报功能为例: 

        气象中心的管理系统将收集的天气信息并将数据暴露出来(通过WebService Server), 而各大站点的应用就去调用它们得到天气信息并以不同的样式去展示(WebService Client)。网站提供了天气预报的服务,但其实它们什么也没有做,只是简单了调用了一下气象中心服务器上的一个服务接口而已。

      4. WebService的优点:能够解决跨平台,跨语言,以及远程调用之间的问题。

      5. WebService的应用场合

        a. 同一家公司的新旧应用之间

        b. 不同公司的应用之间,例如电商和物流之间的应用相互调用

        c. 一些提供数据的内容聚合应用:天气预报、股票行情

    WebService预备知识

      几个重要术语

      1.WSDL/web service definition language

        webservice定义语言, 对应.wsdl文档, 一个webservice会对应一个唯一的wsdl文档, 定义了客户端与服务端发送请求和响应的数据格式和过程。

      2.SOAP/simple object access protocal

        一种简单的、基于HTTP和XML的协议, 用于在WEB上交换结构化的数据,在webservice中分为请求消息和响应消息。

      3.SEI/WebService EndPoint Interface

        webService服务器端用来处理请求的接口

      4.CXF/Celtix + XFire

        一个apache的用于开发webservice服务器端和客户端的框架

     

    WebService开发

      开发方式:

        1. 使用JDK开发(JDK6及以上版本)

        2. 使用apache CXF开发(工作中)

      组成:

        1. 服务端开发

        2. 客户端开发

      一. 使用JDK开发webservice

      服务端

    /**
     * SEI
     * @author byron
     */
    @WebService
    public interface HelloWs {
        
        @WebMethod
        public String sayHello(String name);
        
    }
    
    /**
     * SEI的实现
     * @author byron
     */
    @WebService
    public class HelloWsImpl implements HelloWs {
    
        @WebMethod
        public String sayHello(String name) {
            System.out.println("sayHello " + name);
            return "hello " + name;
        }
    
    }
    
    
    /**
     * 发布webservice
     * @author byron
     */
    public class WsPublish {
    
        public static void main(String[] args) {
            Endpoint.publish("http://192.168.1.106:8989/ws/hello", new HelloWsImpl());
            System.out.println("发布Webservice 服务成功!");
        }
    
    }
    View Code

      发布的webservice服务的wsdl的URL为:http://192.168.1.106:8989/ws/hello?wsdl

      客户端:

         1. 进入生成客户端代码的目录,执行jdk自带的生成客户端代码的命令:wsimport -keep http://192.168.1.106:8989/ws/hello?wsdl

         2. 调用webservice服务

    /**
     * 调用webservice
     * @author byron
     */
    public class HelloClient {
        
        public static void main(String[] args) {
            HelloWsImplService hws = new HelloWsImplService();
            HelloWs ws = hws.getHelloWsImplPort();
            String result = ws.sayHello("Jack");
            System.out.println("Result:" + result);
        }
    
    }
    View Code

      二. WSDL文件分析

      。。。。。

      三. 使用CXF开发WebService

      服务端

        添加apache CXF相关jar包,代码参照JDK开发无需修改

        使用maven构建项目时,在pom.xml添加如下依赖即可:

    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http</artifactId>
        <version>2.7.18</version>
    </dependency>

      客户端

        下载apache cxf软件,解压后,设置环境变量CXF_HOME,将$CXF_HOME/bin加入PATH,然后可以使用wsdl2java生成客户端WebService代码,调用方式与JDK方式相同。

      使用maven构建项目时,在pom.xml添加如下依赖即可:

                <dependencies>
                <dependency>
                    <groupId>org.apache.cxf</groupId>
                    <artifactId>apache-cxf</artifactId>
                    <version>${cxf.version}</version>
                    <type>pom</type>
                </dependency>
                <dependency>
                    <groupId>org.apache.cxf</groupId>
                    <artifactId>cxf-rt-frontend-jaxws</artifactId>
                    <version>${cxf.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.apache.cxf</groupId>
                    <artifactId>cxf-rt-transports-http</artifactId>
                    <version>${cxf.version}</version>
                </dependency>
            </dependencies>       

      1. CXF支持的数据类型

       基本类型:int/float/boolean等

       引用类型:String, 数组/List/Set/Map, 自定义类型(Student)

       示例代码:

    // 服务端代码
    @WebService
    public interface DataTypeWS {
    
        @WebMethod
        public boolean addStudent(Student student);
        
        @WebMethod
        public Student getStudentById(int id);
        
        @WebMethod
        public List<Student> getStudentsByPrice(float price);
        
        @WebMethod
        public Map<Integer, Student> getAllStudentsMap();
    }
    
    
    @WebService
    public class DataTypeWSImpl implements DataTypeWS {
    
        @Override
        public boolean addStudent(Student student) {
            System.out.println("Server addStudent...");
            return true;
        }
    
        @Override
        public Student getStudentById(int id) {
            System.out.println("Server getStudentById...");
            return new Student(id, "Tom", 8500);
        }
    
        @Override
        public List<Student> getStudentsByPrice(float price) {
            System.out.println("Server getStudentsByPrice...");
            List<Student> list = new ArrayList<>();
            list.add(new Student(1, "Jim", price + 1000));
            list.add(new Student(2, "Tim", price + 2000));
            list.add(new Student(3, "Lim", price + 3000));
            return list;
        }
    
        @Override
        public Map<Integer, Student> getAllStudentsMap() {
            System.out.println("Server getStudentsMap...");
            Map<Integer, Student> map = new HashMap<>();
            map.put(1, new Student(1, "Jim", 1000));
            map.put(2, new Student(2, "Tim", 2000));
            map.put(3, new Student(3, "Lim", 3000));
            return map;
        }
    
    }
    
    /**
     * 发布服务
     */
    public class DataTypeServer {
    
        public static void main(String[] args) {
            Endpoint.publish("http://192.168.1.110:8990/ws/type", new DataTypeWSImpl());
            System.out.println("发布 DataType Webservice 服务成功!");
        }
    }
    View Code

      使用wsdl2java命令生成客户端webservice代码,客户端调用代码如下:

    public class DataTypeClientTest {
    
        @Test
        public void testInteger() {
            DataTypeWSImplService factory = new DataTypeWSImplService();
            DataTypeWS dataTypeWS = factory.getDataTypeWSImplPort();
            Student student = dataTypeWS.getStudentById(1);
            System.out.println(student);
        }
        
        @Test
        public void testObject() {
            DataTypeWSImplService factory = new DataTypeWSImplService();
            DataTypeWS dataTypeWS = factory.getDataTypeWSImplPort();
            dataTypeWS.addStudent(new Student(10, "Tony", 12000));
        }
        
        @Test
        public void testList() {
            DataTypeWSImplService factory = new DataTypeWSImplService();
            DataTypeWS dataTypeWS = factory.getDataTypeWSImplPort();
            List<Student> students = dataTypeWS.getStudentsByPrice(5000);
            for (Student stu : students) {
                System.out.println(stu);
            }
        }
        
        @Test
        public void testMap() {
            DataTypeWSImplService factory = new DataTypeWSImplService();
            DataTypeWS dataTypeWS = factory.getDataTypeWSImplPort();
            Return ret = dataTypeWS.getAllStudentsMap();
            List<Entry> entrys = ret.getEntry();
            for (Entry e : entrys) {
                System.out.println(e.getKey() + ">>" + e.getValue());
            }
        }
        
    }
    View Code

      说明:调用成功说明CXF支持上述数据类型;将项目向CXF相关的jar包移除,让webservice以JDK api方式运行,验证得知JDK不支持Map类型.

      2. CXF拦截器

       作用:在文本service请求过程中,动态操作请求和响应数据。

       分类:按位置分(服务端拦截器 & 客户端拦截器),按消息方向分(入拦截器 & 出拦截器),按定义方式分(系统拦截器&自定义拦截器)

       ① 服务端拦截器的使用方法,代码如下:

    public class HelloInterceptor {
    
        public static void main(String[] args) {
            Endpoint publish = Endpoint.publish("http://192.168.1.110:8989/ws/hello", new HelloWsImpl());
            
            EndpointImpl impl = (EndpointImpl) publish;
            
            // 服务端的日志入拦截器
            List<Interceptor<? extends Message>> inInterceptors = impl.getInInterceptors();
            inInterceptors.add(new LoggingInInterceptor());
            
            // 服务端的日志出拦截器
            List<Interceptor<? extends Message>> outInterceptors = impl.getOutInterceptors();
            outInterceptors.add(new LoggingInInterceptor());
            
            System.out.println("发布Webservice 服务成功!");
        }
    
    }
    View Code

      重新发布webservice,并生成客户端基础代码,再调用webservice,可以发现在服务端会打印请求和响应信息的日志。

      ② 同理,客户端也可以添加拦截器

    public class InterceptClient {
        
        public static void main(String[] args) {
            InterceptorWsImplService fatory = new InterceptorWsImplService();
            InterceptorWs interceptorWs = fatory.getInterceptorWsImplPort();
            
            // 获取发送请求的客户端
            Client client = ClientProxy.getClient(interceptorWs);
            
            // 客户单日志出拦截器
            List<Interceptor<? extends Message>> outInterceptors = client.getOutInterceptors();
            outInterceptors.add(new LoggingInInterceptor());
            
            // 客户端日志入拦截器
            List<Interceptor<? extends Message>> inInterceptors = client.getInInterceptors();
            inInterceptors.add(new LoggingInInterceptor());
            
            String result = interceptorWs.intercept("Tom");
            System.out.println("Client " + result);
        }
    }
    View Code

      3. CXF自定义拦截器

       下面使用示例说明cxf中自定义拦截器的用户,在代码中定义了一个校验用户名密码的拦截器,代码如下:

       客户端使用出拦截器附加上用户名密码信息:

    /**
     * 客户端发送webservice前的拦截器,将用户名密码加入请求消息
     * 自定义拦截器继承AbstractPhaseInterceptor即可
     */
    public class AddUserInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
    
        private String username;
        private String password;
    
        public AddUserInterceptor(String username, String password) {
            // 在构造器中指定拦截器拦截的时机
            super(Phase.PRE_PROTOCOL);
            this.username = username;
            this.password = password;
        }
    
        @Override
        public void handleMessage(SoapMessage message) throws Fault {
            List<Header> headers = message.getHeaders();
            
            Document document = DOMUtils.createDocument();
            Element element = document.createElement("user");
            Element nameEle = document.createElement("username");
            nameEle.setTextContent(username);
            element.appendChild(nameEle);
            
            Element pwdEle = document.createElement("password");
            pwdEle.setTextContent(password);
            element.appendChild(pwdEle);
            
            headers.add(new Header(new QName("user"), element));
            System.out.println("Client User Interceptor...");
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
    }
    
    /**
     * 客户单拦截器测试代码
     */
    public class CustomInterceptClient {
        
        public static void main(String[] args) {
            InterceptorWsImplService fatory = new InterceptorWsImplService();
            InterceptorWs interceptorWs = fatory.getInterceptorWsImplPort();
            
            // 获取发送请求的客户端
            Client client = ClientProxy.getClient(interceptorWs);
            
            // 客户单日志出拦截器
            List<Interceptor<? extends Message>> outInterceptors = client.getOutInterceptors();
            outInterceptors.add(new AddUserInterceptor("Tom", "12345"));
            
            String result = interceptorWs.intercept("Tom");
            System.out.println("Client " + result);
        }
    }
    View Code  

       服务端使用入拦截器校验用户名密码:

    /**
     * 服务端接收webservice请求前的拦截器,校验用户名密码
     */
    public class CheckUserInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
    
        public CheckUserInterceptor() {
            // 在构造器中指定拦截器拦截的时机
            super(Phase.PRE_PROTOCOL);
        }
    
        @Override
        public void handleMessage(SoapMessage message) throws Fault {
            Header header = message.getHeader(new QName("user"));
            if (header != null) {
                Element user = (Element) header.getObject();
                String username = user.getElementsByTagName("username").item(0).getTextContent();
                String password = user.getElementsByTagName("password").item(0).getTextContent();
                if ("Tom".equals(username) && "12345".equals(password)) {
                    System.out.println("拦截器校验通过...");
                    return;
                }
            }
            
            System.out.println("拦截器校验失败...");
            throw new Fault(new RuntimeException("用户名或密码不正确!"));
        }
    
    }
    
    /**
     * 服务端带拦截器发布
     */
    public class CustomInterceptorServer {
    
        public static void main(String[] args) {
            Endpoint publish = Endpoint.publish("http://192.168.1.110:8991/ws/intercept", new InterceptorWsImpl());
            
            EndpointImpl impl = (EndpointImpl) publish;
            
            // 服务端的自定义入拦截器
            List<Interceptor<? extends Message>> inInterceptors = impl.getInInterceptors();
            inInterceptors.add(new CheckUserInterceptor());
            
            System.out.println("发布Webservice 服务成功!");
        }
    
    }
    View Code

      

      4. 基于Spring的CXF webservice

       1>webservice服务端代码如下:

    public class Order {
    
        private int id;
        private String name;
        private double price;
    
        public Order() {
            super();
        }
    
        public Order(int id, String name, double price) {
            super();
            this.id = id;
            this.name = name;
            this.price = price;
        }
    
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("Order [id=");
            builder.append(id);
            builder.append(", name=");
            builder.append(name);
            builder.append(", price=");
            builder.append(price);
            builder.append("]");
            return builder.toString();
        }
    
    }
    
    @WebService
    public class OrderWSImpl implements OrderWS {
        
        public OrderWSImpl() {
            System.out.println("OrderWSImpl constructor...");
        }
    
        @Override
        public Order getOrderById(int id) {
            System.out.println("Server getOrderById...");
            return new Order(1001, "TD002", 2000);
        }
    
    }
    View Code

      spring配置(bean.xml)如下:

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:jaxws="http://cxf.apache.org/jaxws"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
            
        <import resource="classpath:META-INF/cxf/cxf.xml" />
        <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
        <import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
        <jaxws:endpoint id="orderWS" implementor="com.stu.ws.spring.OrderWSImpl"
            address="/orderws" />
    </beans>
    View Code

      web.xml配置如下:

    <web-app id="WebApp_ID" version="3.0" 
        xmlns="http://java.sun.com/xml/ns/javaee" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
    
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:bean.xml</param-value>
        </context-param>
           <listener>
              <listener-class>
                 org.springframework.web.context.ContextLoaderListener
              </listener-class>
           </listener>
           <servlet>
              <servlet-name>CXFServlet</servlet-name>
            <servlet-class>
                org.apache.cxf.transport.servlet.CXFServlet
              </servlet-class>
              <load-on-startup>1</load-on-startup>
           </servlet>
           <servlet-mapping>
              <servlet-name>CXFServlet</servlet-name>
              <url-pattern>/*</url-pattern>
           </servlet-mapping>
        
    </web-app>
    View Code

      把上述项目直接部署到tomcat即可。可以通过 http://localhost:8080/ws.server/orderws?wsdl访问到wsdl文档

      2>客户端代码开发:

      进入客户端项目所在目录,使用命令 "wsdl2java http://localhost:8080/ws.server/orderws?wsdl" 生成客户端代码。

         

       spring配置文件(client-bean.xml)

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:jaxws="http://cxf.apache.org/jaxws"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
            
        <jaxws:client id="orderClient" serviceClass="com.stu.ws.spring.OrderWS"
            address="http://localhost:8080/ws.server/orderws" />
    </beans>
    View Code

       编写客户端调用代码:

        public static void main(String[] args) {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("client-bean.xml");
            OrderWS orderWs = (OrderWS) context.getBean("orderClient");
            Order order = orderWs.getOrderById(1002);
            System.out.println(order);
        }
    View Code

      5. 基于spring的CXF拦截器

      复用3中得拦截器代码和4中得配置,进行修改即可

      服务端配置入拦截器:bean.xml

    <jaxws:endpoint id="orderWS" implementor="com.stu.ws.spring.OrderWSImpl"
            address="/orderws" >
        <jaxws:inInterceptors>
            <bean class="com.stu.ws.interceptor.custom.CheckUserInterceptor"/>
        </jaxws:inInterceptors>
    </jaxws:endpoint>

      客户端出拦截器配置:client-bean.xml

    <jaxws:client id="orderClient" serviceClass="com.stu.ws.spring.OrderWS"
            address="http://localhost:8080/ws.server/orderws" >
        <jaxws:outInterceptors>
            <bean class="com.stu.ws.user.AddUserInterceptor">
                <constructor-arg name="username" value="Tom" />
                <constructor-arg name="password" value="12345" />
            </bean>
        </jaxws:outInterceptors>
    </jaxws:client>

      6. 使用Ajax请求webservice

       在页面自行组装符合格式要求的请求数据发送post请求即可,代码如下:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!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>Ajax 请求 Webservice</title>
    <script type="text/javascript" src="js/jquery-1.10.1.min.js"></script>
    <script type="text/javascript">
    $(function(){
        var sendData = 
              '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'
            + '<soap:Body><ns2:intercept xmlns:ns2="http://interceptor.ws.stu.com/">'
            + '<arg0>' + $('#username').val() + '</arg0>'
            + '</ns2:intercept></soap:Body></soap:Envelope>';
        $("#requestWs2").click(function(){
            $.post(
                "http://localhost:8080/ws.server/intercept",
                sendData,
                function(msg) {
                    var result = $(msg).find("return").text();
                    alert(result);
                },
                "xml"
            );
        });
    })
    
    // JS发送Ajax请求
    function requestWs1() {
        var request = getRequest();
        request.onreadystatechange = function(){
            if (request.readyState == 4 && request.status == 200) {
                var result = request.responseXML;
                var value = result.getElementsByTagName("return")[0].firstChild.data;
                alert(value);
            }
        }
        
        request.open("POST", "http://localhost:8080/ws.server/intercept");
        request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        
        var sendData = 
              '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'
            + '<soap:Body><ns2:intercept xmlns:ns2="http://interceptor.ws.stu.com/">'
            + '<arg0>' + document.getElementById('username').value + '</arg0>'
            + '</ns2:intercept></soap:Body></soap:Envelope>';
        alert(sendData);
        request.send(sendData);
    }
    
    function getRequest() {
        var xmlHttp = null;
        try {
            xmlHttp = new XMLHttpRequest();
        } catch(e) {
            try {
                xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
            } catch (e) {
                xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
        }
        return xmlHttp;
    }
    </script>
    </head>
    <body>
        用户名:<input id="username" name="username" value="" />
        <button onclick="requestWs1()">Ajax请求ws(Js)</button>
        <button id="requestWs2">Ajax请求ws(jQ)</button>
    </body>
    </html>
    View Code

       本次测试中,请求页面和webservice服务都在同一台机器,使用相同的IP访问,否则在页面请求会发生跨域问题。

       跨域问题解决方法:在页面请求后台的一个Servlet,在Java代码去请求webservice,即可。

      页面代码修改如下:

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!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>Ajax 请求 Webservice</title>
    <script type="text/javascript" src="js/jquery-1.10.1.min.js"></script>
    <script type="text/javascript">
    $(function(){
        $("#requestWs2").click(function(){
            $.post(
                "wsHelperServlet",
                {username:$('#username').val()},
                function(msg) {
                    var result = $(msg).find("return").text();
                    alert(result);
                },
                "text"
            );
        });
    })
    </script>
    </head>
    <body>
        用户名:<input id="username" name="username" value="" />
        <button id="requestWs2">Ajax请求ws(jQ)</button>
    </body>
    </html>
    View Code

      Servlet代码如下:

    @WebServlet("/wsHelperServlet")
    public class WsHelperServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
           
        public WsHelperServlet() {
            super();
        }
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            String name = request.getParameter("username");
            String data = "<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">"
                    + "<soap:Body><ns2:intercept xmlns:ns2="http://interceptor.ws.stu.com/">"
                    + "<arg0>" + name + "</arg0>"
                    + "</ns2:intercept></soap:Body></soap:Envelope>";
            URL url = new URL("http://192.168.1.110:8080/ws.server/intercept");
            
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "text/xml;charset=UTF-8");
            conn.getOutputStream().write(data.getBytes("UTF-8"));
            
            response.setContentType("text/xml;charset=utf-8");
            if (conn.getResponseCode() == 200) {
                InputStream is = conn.getInputStream();
                System.out.println(is.available());
                
                ServletOutputStream os = response.getOutputStream();
                
                int len = 0;
                byte[] buffer = new byte[1024];
                while ((len=is.read(buffer)) > 0) {
                    os.write(buffer, 0, len);
                }
                os.flush();
            }
        }
    
    }
    View Code

      8. 通过注解修改wsdl文档

  • 相关阅读:
    三联生活周刊:女游戏设计师之死
    HTML
    营运社区所需要的基本心理学常识
    对C++下struct 和 类默认继承的认识
    什么是列表?
    什么是个人网站?
    DevExpress ASPxListBox can't get selected items after postback
    ListItemEventHandler does not fire on the prospective list
    SPF和SharePoint Server的区别
    什么是网站?
  • 原文地址:https://www.cnblogs.com/techroad4ca/p/4970350.html
Copyright © 2020-2023  润新知