JAX-WS(JWS)

简述

WebService历来都很受重视,特别是Java阵营,WebService框架和技术层出不穷。知名的XFile(新的如CXF)Axis1Axis2等。

Web Services 发展至今已有两种形式:RESTSOAPREST Web Services基于HTTP协议,SOAP Web Services支持多种传输协议:HTTPSMTPMIME等等。

  本文主要介绍 SOAP web services。对于JAVA,目前有两种SOAP Web Services规范:JAX-WSSAAJ

SOAP Web Services 通常要求服务器端提供一個机器可读的描述(通常基于 WSDL),以便客户端辨识服务器端提供的Web服务。

Sun公司也不甘落后,从早期的JAX-RPC到现在成熟的、支持RPC调用与消息传递的JAX-WS都经过了市场的考验,十分成熟,而且使用JAX-WS开发WebService的收益是很大的,它是轻量级的

JAX-WS (Java API for XML Web Services) 是一组专门用于实现 XML Web Services Java APIJDK 1.6自带JAX-WS版本为2.1。不过,JAX-WS只提供web services的基础功能,所以如果你希望实现web services的复杂功能,比如WS-SecurityWS-PolicyWS-RM等,那就需要切换到Apache CXFMetro或者Axis

JAX-WS规范是一组XML web servicesJAVA APIJAX-WS允许开发者可以选择RPC-oriented或者message-oriented来实现自己的web services

  在 JAX-WS中,一个远程调用可以转换为一个基于XML的协议例如SOAP,在使用JAX-WS过程中,开发者不需要编写任何生成和处理SOAP消息的代码。JAX-WS的运行时实现会将这些API的调用转换成为对应的SOAP消息。
  在服务器端,用户只需要通过Java语言定义远程调用所需要实现的接口SEIservice endpoint interface),并提供相关的实现,通过调用JAX-WS的服务发布接口就可以将其发布为WebService接口。
  在客户端,用户可以通过JAX-WSAPI创建一个代理(用本地对象来替代远程的服务)来实现对于远程服务器端的调用。
  当然 JAX-WS 也提供了一组针对底层消息进行操作的API调用,你可以通过Dispatch直接使用SOAP消息或XML消息发送请求或者使用Provider处理SOAPXML消息。
  通过web service所提供的互操作环境,我们可以用JAX-WS轻松实现JAVA平台与其他编程环境(.net等)的互操作。
JAX-WSJAX-RPC之间的关系
Sun最开始的web services的实现是JAX-RPC 1.1 (JSR 101)。这个实现是基于JavaRPC,并不完全支持schema规范,同时没有对BindingParsing定义标准的实现。

JAX-WS2.0 (JSR 224)Sun新的web services协议栈,是一个完全基于标准的实现。在binding层,使用的是the Java Architecture for XML Binding (JAXB, JSR 222),在parsing层,使用的是the Streaming API for XML (StAX, JSR 173),同时它还完全支持schema规范。

 服务端

我们使用JAX-WS开发WebService只需要很简单的几个步骤:写接口和实现=>发布=>生成客户端(测试或使用)。

  而在开发阶段我们也不需要导入外部jar包,因为这些api都是现成的。首先是接口的编写(接口中只需要把类注明为@WebService,把 要暴露给客户端的方法注明为@WebMethod即可,其余如@WebResult、@WebParam等都不是必要的,而客户端和服务端的通信用RPC 和Message-Oriented两种,区别和配置以后再说):

package com.shu.jws;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

/**
 * 定义JWS接口
 * @author: jiangshubian
 * @Description:
 * @Date: Create in 2018-01-14 14:03
 * @Version: 1.0.0
 */
@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface SayHiService {
    /**
     * 无形参和返回值的方法
     */
    @WebMethod
    void sayNothing();


    /**
     * 有形参的方法
     *
     * @param msg
     */
    @WebMethod
    void saySomething(@WebParam(name = "msg") String msg);

    /**
     * 有形参和返回值的方法
     * @param clientTime
     * @return
     */
    @WebMethod
    @WebResult(name = "valid")
    int checkTime(@WebParam(name = "clientTime") java.util.Date clientTime);
}

然后是实现类(注解@WebService及其endpointInterface属性是必要的)

package com.shu.jws;

import java.text.SimpleDateFormat;
import java.util.Date;

import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

/**
 * 实现类
 * @author: jiangshubian
 * @Description:
 * @Date: Create in 2018-01-14 14:10
 * @Version: 1.0.0
 */
//endpointInterface需要指定接口全路径
@WebService(endpointInterface = "com.shu.jws.SayHiService") 
@SOAPBinding(style = SOAPBinding.Style.RPC)
public class SayHiServiceImp implements SayHiService {
    @Override
    public void sayNothing() {
        System.out.println("SayHiServiceImp.sayNothing");
    }

    @Override
    public void saySomething(String msg) {
        System.out.println("SayHiServiceImp.saySomething:" + msg);
    }

    @Override
    public int checkTime(Date clientTime) {
        // 精确到秒
        String dateServer = String.valueOf(new java.sql.Date(System.currentTimeMillis())
                + " "
                + new java.sql.Time(System.currentTimeMillis()));
        String dateClient = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                .format(clientTime);
        return dateServer.equals(dateClient) ? 1 : 0;
    }
}

然后是发布(一般有两种方式):

  方式一(此方式只能作为调试,有以下bug:

    jdk1.6u17?以下编译器不支持以Endpoint.publish方式发布document方式的soap,必须在service接口和实现类添加“@SOAPBinding(style = SOAPBinding.Style.RPC)”注解;

    访问受限,似乎只能本机访问(应该会绑定到publish的URL上,如下使用localhost的话就只能本机访问)……):

package com.shu.jws;

import javax.xml.ws.Endpoint;

/**
 * 发布JWS服务
 * @author: jiangshubian
 * @Description:
 * @Date: Create in 2018-01-14 14:15
 * @Version: 1.0.0
 */
public class PublishJWS {
    public static void main(String[] args) {
        System.out.println("EndPoint starting.....");
        Endpoint.publish("http://localhost/jws/service", new SayHiServiceImp());
        System.out.println("EndPont start.....");
    }
}

方式二(基于web服务器Servlet方式):

  以Tomcat为例,首先编写sun-jaxws.xml文件并放到WEB-INF下:

<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
    version="2.0">
    <endpoint name="SayHiService"
        implementation="service.imp.SayHiServiceImpl"
        url-pattern="/service/sayHi" />
</endpoints>

然后改动web.xml,添加listener和servlet(url-pattern要相同哦):

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
    
    <listener>  
        <listener-class>
            com.sun.xml.ws.transport.http.servlet.WSServletContextListener  
        </listener-class>
    </listener>
    <servlet>
        <servlet-name>SayHiService</servlet-name>  
        <servlet-class>
            com.sun.xml.ws.transport.http.servlet.WSServlet  
        </servlet-class>
    </servlet>  
    <servlet-mapping>  
        <servlet-name>SayHiService</servlet-name>  
        <url-pattern>/service/sayHi</url-pattern>  
    </servlet-mapping>
    
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

 maven的pom.xml加入jws依赖包:

!-- https://mvnrepository.com/artifact/com.sun.xml.ws/jaxws-rt -->
            <dependency>
                <groupId>com.sun.xml.ws</groupId>
                <artifactId>jaxws-rt</artifactId>
                <version>2.2.7</version>
            </dependency>

启动Tomcat。

  

  服务端工作就完成了,注意两个事情。

  注意:项目需要使用UTF-8编码(至少sun-jaxws.xml必须是UTF-8格式的);

    多个不同路径的接口也要使用同一个WSServlet;

    最好加上@SOAPBinding(style = SOAPBinding.Style.RPC)注解。

  部署好了之后打开浏览器输入网址:http://localhost/service/sayHi?wsdl 可以看到东西就证明发布成功了。

客户端

由于WebService是平台和语言无关的基于xml的,所以我们完全可以使用不同语言来编写或生成客户端。一般有三种方式来使用(对于Java语言而言):

 一,使用jdk自带工具wsimport生成客户端:

jaxws\src\main\java>wsimport -keep -s . -p com.shu.jwsclient http://localhost/service/sayHi?wsd


  • wsimport.exe在jdk的bin下,使用该工具可以根据wsdl地址生成java的客户端代码。
  • -keep:是否生成java源文件
  • -d:指定输出目录
  • -s:指定源代码输出目录
  • -p:以package的形式生成文件
  • -verbose:在控制台显示输出信息

jdk自带的wsimport工具生成,上图我是把客户端文件生成到了桌面src文件中(-d),并保留了源文件(-keep),指定了包名(-p)。

  然后我们就可以使用生成的文件来调用服务器暴露的方法了:

值得一提的是你生成使用的jdk和你客户端的jre需要配套!

  从上面的目录结构我们可以发现:服务端的每个webmethod都被单独解析成为了一个类(如果使用了实体,实体也会被解析到客户端,并且是源码,所以建议使用实体时慎重)。

  而我们的service则被生成了一个代理类来调用服务,接下来我们看看使用情况:

package com.shu.jwsclient;

import java.util.GregorianCalendar;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

/**
 * 客户端调用服务器端方法
 * @author: jiangshubian
 * @Description:
 * @Date: Create in 2018-01-14 15:26
 * @Version: 1.0.0
 */
public class JWSClientInvoker {
    public static void main(String[] args) throws DatatypeConfigurationException {
        runerTest();
    }

    private static void runerTest() throws DatatypeConfigurationException {
        SayHiService sayHiService = new SayHiServiceImpService().getSayHiServiceImpPort();
        //invoke sayNothing method
        sayHiService.sayNothing();

        //invoke saySomething method
        sayHiService.saySomething("Something...");

        //invoke checkTime method
        GregorianCalendar calender = new GregorianCalendar();
        calender.setTime(new java.util.Date(System.currentTimeMillis()));
        XMLGregorianCalendar xmldate = DatatypeFactory.newInstance().newXMLGregorianCalendar(calender);
        System.out.println(sayHiService.checkTime(xmldate));
    }
}


看看服务器的输出,我们是否调用成功:

此时服务器和客户端都输出结果!


Https 与 web services

Web Services 采用网络传输数据,数据很容易暴露在外。在这种情况下,我们可以采用 Https 协议传输数据,建立一个信息安全通道,来保证数据传输的安全。当客户端与服务器端建立 https 连接时,客户端与服务器端需要经过一个握手的过程来完成身份鉴定,确保网络通信的安全。

对此,我们需要在客户端代码里做相应的处理:

    1. 利用浏览器导出 server 端证书,并保存为 demo.crt 。不同浏览器的导出步骤不一样,请查询相应的导出方法。
    2. 在客户端创建 truststore:keytool.exe -genkey -alias MYDOMAIN -keyalg RSA -keystore clienttruststore.jks
    3. 把 demo.crt 加入到 truststore 中: keytool.exe -import -trustcacerts -alias STOREALIAS -file demo.crt -keystore clienttruststore.jks
    4. 在客户端 HelloWorldClient.java 加入如下代码:

 
package com.shu.jwsclient;

import java.util.GregorianCalendar;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

/**
 * 客户端调用服务器端方法
 *
 * @author: jiangshubian
 * @Description:
 * @Date: Create in 2018-01-14 15:26
 * @Version: 1.0.0
 */
public class JWSClientInvoker {
    public static void main(String[] args) throws DatatypeConfigurationException {
        loadTrustStore();
        runerTest();
    }

    private static void runerTest() throws DatatypeConfigurationException {
        SayHiService sayHiService = new SayHiServiceImpService().getSayHiServiceImpPort();
        //invoke sayNothing method
        sayHiService.sayNothing();

        //invoke saySomething method
        sayHiService.saySomething("Something...");

        //invoke checkTime method
        GregorianCalendar calender = new GregorianCalendar();
        calender.setTime(new java.util.Date(System.currentTimeMillis()));
        XMLGregorianCalendar xmldate = DatatypeFactory.newInstance().newXMLGregorianCalendar(calender);
        System.out.println(sayHiService.checkTime(xmldate));
    }

    public static void loadTrustStore() {
        System.setProperty("javax.net.ssl.trustStore", "PATH\\clienttruststore.jks"); //(truststore) 
        System.setProperty("javax.net.ssl.trustStorePassword", "password"); //(truststore 密码 ) 
        System.setProperty("javax.net.ssl.trustStoreType", "JKS"); //(truststore 类型 ) 
    }

}
身份鉴定完成,可以继续 web services 的探险了。
 

MTOM 与 web services

我们先来了解默认情况下 SOAP 是如何传输数据的:

  在 SOAP 消息中所有的二进制数据都必须以编码之后的形态存在于 XML 文件中(为避免字符冲突)。正常文本 XML 使用 Base64 对二进制数据进行编码,这就要求每三个字节对应四个字符,从而使得数据的大小增加三分之一。如果我们需要传送 10M 的文件,编码之后文件大小就 13M。这种情况下,JAVA 引入了 MTOM(消息传输优化机制)消息编码。MTOM 就是针对 SOAP 消息传输的基础上提出的改进办法。对于大量数据的传递,不会进行 Base64 编码,而是直接以附件的二进制原始数据的形式封装在 SOAP 消息的 MIME 部分,进行传输。使用 MTOM 的目的在于优化对较大的二进制负载的传输。对于较小的二进制负载来说,使用 MTOM 发送 SOAP 消息会产生显著的开销,但是,当这些负载增大到几千个字节时,该开销会变得微不足道。

现在,我们利用 MTOM 机制实现一个提供上传和下载文件功能的 HelloWorld Web Service。首先,在服务器端增加 upload() 和 download() 两个方法,并通过添加标注 @MTOM,在服务器端开启 MTOM 消息传输功能,同时选用 datahandler 类型封装传输文件。并使用 @XmlMimeType("application/octet-stream") 标注 datahandler,以表示这是一个附件类型的二进制数据。

package com.shu.jws.mtom;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.ws.soap.MTOM;

/**
 * @author: jiangshubian
 * @Description:
 * @Date: Create in 2018-01-14 15:46
 * @Version: 1.0.0
 */
@WebService
@MTOM
public class MtomUpload {
    private final static String UploadStorePath = "E:/jws";

    @WebMethod
    public String sayHello() {
        return "Hello World!";
    }

    @WebMethod
    public String upload(@XmlMimeType("application/octet-stream") DataHandler dataHandler, String fileName) {
        OutputStream out = null;
        System.out.println(fileName);
        try {
            File file = new File(UploadStorePath, fileName);
            out = new BufferedOutputStream(new FileOutputStream(file));
            dataHandler.writeTo(out);
        } catch (IOException e) {
            e.printStackTrace();
            return "error";
        } finally {
            close(out);
        }
        return "success";
    }

    @WebMethod
    public @XmlMimeType("application/octet-stream")
    DataHandler downLoad(String fileName) {
        return new DataHandler
                (new FileDataSource(UploadStorePath + "/" + fileName));
    }

    private void close(OutputStream out) {
        if (out != null)
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
}

WEB-INF/sun-jaxws.xml添加endpoint服务:
<endpoint name="MtomUpload" implementation="com.shu.jwsclient.mtom.MtomUpload" url-pattern="/service/MtomUpload" />
WEB-INF/web.xml添加servlet配置:
<servlet>
        <servlet-name>MtomUpload</servlet-name>
        <servlet-class>
            com.sun.xml.ws.transport.http.servlet.WSServlet
        </servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MtomUpload</servlet-name>
        <url-pattern>/service/MtomUpload</url-pattern>
    </servlet-mapping>

启动Tomcat.

每次重新发布 web service 之后,都需要在客户端重新运行 wsimport,保持客户端 Stub 与 web service 一致。现在,我们就在客户端调用 MtomUpload 的 upload() 方法上传 test.txt 文件到服务器,并通过 downLoad() 从 Server 下载名为 wsDemo 文件。

客户端开启 MTOM 的方式和 Server 端不同,请参见客户端代码。

java>wsimport -keep -s . -p com.shu.jwsclient.mtom.generatecode http://localhost/service/MtomUpload?wsdl

客户端调用:

package com.shu.jws.mtom;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.jws.WebMethod;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.ws.soap.MTOM;

/**
 * @author: jiangshubian
 * @Description:
 * @Date: Create in 2018-01-14 15:46
 * @Version: 1.0.0
 */
@WebService
@MTOM
public class MtomUpload {
    private final static String UploadStorePath = "E:/jws";

    @WebMethod
    public String sayHello() {
        return "Hello World!";
    }

    @WebMethod
    public String upload(@XmlMimeType("application/octet-stream") DataHandler dataHandler, String fileName) {
        OutputStream out = null;
        System.out.println(fileName);
        try {
            File file = new File(UploadStorePath, fileName);
            out = new BufferedOutputStream(new FileOutputStream(file));
            dataHandler.writeTo(out);
        } catch (IOException e) {
            e.printStackTrace();
            return "error";
        } finally {
            close(out);
        }
        return "success";
    }

    @WebMethod
    public @XmlMimeType("application/octet-stream")
    DataHandler downLoad(String fileName) {
        return new DataHandler
                (new FileDataSource(UploadStorePath + "/" + fileName));
    }

    private void close(OutputStream out) {
        if (out != null)
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    }
}
利用 upload() 方法上传 5 个不同大小的文件 (1M,10M,50M,100M,300M),并统计上传这些文件分别消耗的时间:

图 6. Upload() 消耗时间统计
Upload() 消耗时间统计 
图 7. Upload() 消耗时间线性图
Upload() 消耗时间线性图

可以看出,随着文件大小的增长,上传文件所消耗的时间几乎是呈线性增长的。并且实际消耗时间和预期消耗时间(以上传 1M 文件的时间为基准,线性计算)的相差值也是在可以接受的范围内。这完全符合我们的预期期望。

结束语

本文展示利用 JAVA Web Services 规范 JAX-WS 实现 web services 客户端和服务器端通信。SOAP Web Services 使用 HTTP 传送 XML,不过由于 HTTP 的限制以及需要额外的消耗解析 XML 文件,使得 SOAP 的通信速度低于其它方案。但另一方面,XML 是一个开放、健全、有语义的讯息机制,而 HTTP 是一个广泛又能避免许多关于防火墙的问题,从而使 SOAP 得到了广泛的应用。所以,如果效率对项目是一项很重要的指标的话,就需要慎重考虑是否使用 SOAP 实现 Web Services。


阅读参考:

http://www.mkyong.com/webservices/jax-ws/deploy-jax-ws-web-services-on-tomcat/

http://www.mkyong.com/webservices/jax-ws/jax-ws-attachment-with-mtom/


                
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值