首页 > 开发 > 综合 > 正文

SOAP净化有线协议(二):Apache SOAP介绍(1)

2020-02-03 15:08:20
字体:
来源:转载
供稿:网友
  • 网站运营seo文章大全
  • 提供全面的站长运营经验及seo技术!
  •   soap(简单对象访问协议)是一种利用xml编码数据的有线协议。它是同类协议中要求最低的一个规范,只定义了有线协议所要求的最关键的部分,有意地忽略了垃圾收集、对象激活等方面的细节。

    soap对于java开发者来说尤其重要,因为它让平台无关和可移植的java程序更容易协同操作,使得java的宝贵特性进一步增值。事实上,如果java 2平台企业版(j2ee)的下一个版本让soap成为一种必须遵循的有线协议,规定所有遵从j2ee规范的应用服务器都必须支持soap协议,我也不会感到奇怪。不过就现在来说,我想我的猜想应该暂停了。

    这个系列的文章总共四篇,这是第二篇。在这里,我要介绍的是apache soap实现。

    一、apache soap概述
    apache soap,即apache software foundation对soap规范的实现,建立在ibm的soap4j的基础上。和所有其他apache工程类似,apache soap源代码开放,按照apache许可协议发行。我觉得这是当前最好的soap实现之一。然而,虽然apache soap遵从soap规范1.1版本,但它缺乏soap 1.1所包含的某些功能(参见本文最后的“参考资源”,了解apache soap支持功能的清单)。

    1.1、下载和安装apache soap
    如前所述,apache soap可以免费下载(参见“参考资源”中提供的下载链接)。我为我的windows nt便携机下载了soap-bin-2.0.zip文件,该文件包含apache soap 2.0,这是写作本文时的最新版本。安装apache soap可谓轻而易举,共包含如下三个简单的步骤:

    1. 解开下载所得文件的zip压缩:解开压缩之后就得到了一个soap-2_0子目录。我把zip文件的内容解压缩到e盘的根目录下,因此有了一个包含apache soap的e:/soap-2_0目录。
    2. 配置web环境:要有一个支持servlet和jsp的web服务器。至此,你可能遇到下面两种情况之一:
      • 情况1:已经有一个支持servlet和jsp的web服务器,而且你觉得配置这个服务器没有问题。在这种情况下,请配置服务器,使得浏览器可以访问http://localhost:8080/apache-soap/,打开soap-2_0 /webapps/soap/目录下面的index.html文件。
      • 情况2:没有支持servlet和jsp的web服务器,或者虽然有这样一个服务器,却不想拿它做试验。在这种情况下,我建议你下载tomcat的最新版本(写作本文时,最新版本是3.1)(参见“参考资源”中的链接)。tomcat是apache创建和免费提供给软件开发者的又一个优秀软件。下载合适的zip文件之后(jakarta-tomcat-3.1.1.zip),解开压缩时创建一个jakarta-tomcat子目录。和前面相似,我把解压缩得到的文件放入e盘的根目录之下。在jakarta-tomcat/conf/server.xml配置文件中增加一个新的<context>标记,如下所示: <context path="/apache-soap" docbase="e:/soap-2_0/webapps/soap"
        debug="1" reloadable="true">
        </context>在context元素的docbase属性中,你应该在指定soap-2_0目录时把e:替换成合适的盘符。要启动tomcat,执行startup.bat(对于unix,执行startup.sh)。要关闭tomcat,执行shutdown.bat(对于unix,执行shutdown.sh)。但请稍等——现在请不要启动tomcat。
    3. 设置web服务器classpath:apache soap要求有1.1.2版本或更高的apache xerces(java),它支持dom(文档对象模型)level 2规范,支持名称空间。我使用1.2版本的xerces,即apache网站的xerces-j-bin.1.2.0.zip。解开这个压缩文件,得到xerces-1_2_0子目录。和前面一样,我把解压缩得到的文件放到了e:盘的根目录之下。你应该配置web服务器,使它能够用xerces.jar(它在xerces-1_2_0子目录下)进行所有xml解析——而不是用服务器附带的库或jar解析xml。例如,tomcat附带了一个xml解析器(jakarta-tomcat/lib/xml.jar),支持dom level 1接口。即使你把xerces.jar放入了classpath,tomcat下运行的java代码也可能找错接口,因为在用来启动tomcat的shell脚本/批命令文件中,xerces.jar被放到了classpath的最后。因此,必须编辑jakarta-tomcat/bin目录下的tomcat.bat(对于unix,则是tomcat.sh),把xerces.jar放到classpath的前面。下面是我在jakarta-tomcat/bin/tomcat.bat文件中所作的修改: set classpath=e:/xerces-1_2_0/xerces.jar;%classpath%;%cp%

    如果你在第二个步骤中属于情况2,也必须配置服务器,使它能够使用xerces.jar。

    不管你属于哪一种情况,除了配置xerces.jar之外,你还必须配置web服务器的classpath使它能够使用soap-2_0/lib/目录下的soap.jar。

    1.2、检查安装是否成功
    现在,启动web服务器,用浏览器打开http://localhost:8080/apache-soap/admin,验证安装是否成功。这时,你应该看到下图所示的管理屏幕。


    图一:web界面的apache soap管理工具

    二、实例:helloworld
    现在你已经设置好了apache soap,我们来实际使用一下,构造一个简单的helloworld应用。在soap术语中,应用称为服务。一般地,创建服务分两个步骤,这两个步骤可能由同一个人或组织实施,也可能不是。第一个步骤是在选定的语言中定义和实现服务本身,这里我们选择java语言。第二个步骤是创建实际调用服务的客户程序。首先我们来看helloworld服务。

    2.1、helloworld服务
    我在第一篇文章中讨论了一个用soap实现的helloworld服务实例。这个服务要求输入一个用户名字,返回一个定制的hello消息给调用者。下面的代码显示了helloworld服务的完整java代码。


    package hello;
    public class helloserver
    {
    public string sayhelloto(string name)
    {
    system.out.println("sayhelloto(string name)");
    return "hello " + name + ", how are you doing?";
    }
    }
    这就是全部的代码吗?如果这是真的话,实在太简单了!

    apache soap使创建服务变得极其简单。服务主要由业务逻辑构成,不管服务以哪种方式提供给外界使用,业务逻辑代码都是必须编写的。换句话说,服务不会和任何soap相关的代码混合,因为apache soap底层体系——它由rpcrouter servlet和soap.jar构成——帮助我们完成了所有错综复杂的工作。我们来简要地探讨一下这些错综复杂的工作究竟包含些什么,例如,apache soap如何处理http上的远程过程调用(rpc)请求?理解这一点将给创建客户程序带来方便(不错,是客户程序)。

    在apache soap中,org.apache.soap.rpc包支持在soap上进行rpc调用。apache rpc支持的关键在于对象id。所有的apache soap服务必须有一个唯一的id,它被视为服务的对象id。众所周知,唯一性是一个相对的概念;在apache soap中,对象id的唯一性相对于服务所部署的apache soap服务器而言。也就是说,部署在不同apache soap服务器上的两个服务可能有同样的对象id。

    想要使用服务的客户程序设置一个org.apache.soap.rpc.call对象,指定目标服务的对象id、待调用方法的名字以及提供给方法的参数(如果有的话)。设置好call对象之后,客户程序调用它的invoke()方法。invoke()方法需要两个参数,第一个参数是一个执行rpcrouter servlet的url,如http://localhost:8080/apache-soap/servlet/rpcrouter;第二个参数是soapaction头(请参考本系列的第一篇文章,了解soapaction头的重要性和可能的取值)。

    invoke()方法把call对象转换成xml soap请求(类似第一篇文章所提供的示例),把请求发送给第一个参数中的url所指定的rpcrouter servlet。当servlet返回应答,invoke()方法返回一个org.apache.soap.rpc.response对象,这个对象包含了服务的应答(如果有的话)或错误信息(如果出现了错误)。http规定每一个请求都必须有一个应答;因此,即使服务本身不返回任何东西,rpcrouter servlet总是会返回一些内容。因此,invoke()方法总是返回一个response对象。

    在服务端,apache soap服务器(也就是rpcrouter servlet)接收客户程序发送的soap请求,重新构造出call对象。servlet使用重新构造得到的call对象中的对象id在服务管理器中确定具体的对象。

    接下来,servlet在已经确定的对象上检验被调用方法的名字并调用方法。完成后,servlet串行化该调用的返回值,在http应答中把它发送给客户程序。

    从上述讨论中,我们可以发现一个有趣的问题:apache soap如何知道串行化某种给定数据类型的方法?apache soap通过一个类型注册器(org.apache.soap.encoding.soapmappingregistry),以及通过所有装配器(marshaller)和反装配器(marshaller)分别必须实现的串行化(org.apache.soap.util.xml.serializer)和反串行化(org.apache.soap.util.xml.deserialization)接口,实现java数据类型和xml之间的装配和反装配。apache soap提供了许多实现这些接口的内建的装配器和反装配器。例如,我们可以用org.apache.soap.encoding.soapenc.beanserializer类装配和反装配javabean。本文后面我将介绍如何使用这个类。

    对于java基本数据类型(int,long,double,等等)及其对应的对象化形式(integer,long,double,等等)来说,它们的串行化器和反串行化器已经预先在类型映射注册器中注册。因此,对于客户程序来说,用java基本数据类型及其对象形式作为方法参数是无缝的。然而,如果服务所要求的参数是一个javabean,则必须手工在类型映射注册器中注册beanserializer。服务永远不会做任何额外的工作,最后客户程序的负担总是较多。在这个系列的第四篇文章中,我将介绍用动态代理构造的框架,它将使创建客户程序和创建服务程序一样简单。

    2.2、部署helloworld服务
    部署apache soap服务有两种方法:使用web界面的管理工具,或通过命令行进行部署。所有这两种方法都可以部署服务,使服务可被客户程序访问。

    ■ 使用管理工具部署服务

    要使用管理工具,用浏览器打开http://localhost:8080/apache-soap/admin。浏览器将显示出图一所示的界面。点击窗口左边的deploy按钮,一个带有许多输入框的窗口就会出现。并非所有的输入框现在都要用到。我将在用到这些输入框的时候介绍它们的含义。由于本文无需用到所有的输入框,所以我们将忽略部分输入框的含义。但不用担心,到第三篇文章结束时,我将介绍完所有的输入框。

    id输入框用来设置对象id;如前所述,soap基础设施利用对象id把rpc请求绑定到soap服务。我在前面已经提到,所有apache soap服务必须有一个对象id,这个对象id在该服务器上部署的所有服务之间唯一。我通常使用“urn:<uniqueserviceid>”格式,其中uniqueserviceid是服务的唯一对象id。在本例中,把id设置成“urn:hello”。

    scope输入框用来定义响应调用请求的服务实例的生存范围和时间。scope可以是下列值之一:

  • page:服务实例一直有效,直至应答发送完毕或把请求传递给了另一个页面——如果使用标准的部署机制,向前传递请求不太可能发生。
  • request:服务实例在请求处理期间一直有效,不管是否出现请求传递。
  • session:服务实例对于整个会话都有效。
  • application:服务实例被用于所有对服务的调用请求。
      scope的值对安全有着重要的影响,记住这一点很重要。page和request值确保了连续调用之间的隔离。在另一个极端,application值意味着所有soap的用户共享服务实例。细心的读者可能已经注意到,jsp的<jsp:usebean>标记同样要用到这些值。事实上,rpcrouter servlet曾经就是一个jsp页面,这也许是这些值被选用的原因。在本例中,我们把scope的值设置成application。

      在methods输入框中,输入用空白字符分隔的方法名字,这些方法名字指示出当前部署的服务上允许调用的方法。我们的服务示例只支持一个方法,即sayhelloto()。

      把provider type设置成java。它意味着服务用java实现,而且你必须为apache soap提供服务完整的类名。这个任务在provider class输入框完成,我们把它设置成hello.helloserver。由于sayhelloto()方法不是静态的,保持static输入框原来的值,即no。

      现在滚动到页面的下方,点击表单下面的deploy按钮(不是左边的deploy按钮)。要验证服务已经部署完毕,点击左边的list按钮,这时列表所显示的服务中应该包含一个urn:hello服务。

      ■ 从命令行部署服务

      部署服务除了可以用web界面的管理工具,还可以用命令行java工具org.apache.soap.server.servicemanagerclient,它是一个apache soap附带的类。这个类要求有两个必不可少的参数,一个指向apache soap路由servlet(即rpcrouter)的url,以及一个动作。这个动作可以是以下四者之一:deploy,undeploy,list,或query。根据指定动作的不同,有时候还要提供额外的参数。例如,如果动作是deploy,则必须提供xml部署描述器文件的名字。部署描述器文件应该包含apache soap服务器成功部署服务所需要的全部信息。例如,描述helloworld部署细节的部署xml文件可以如下:


      <isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
      id="urn:hello">
      <isd:provider type="java" scope="application" methods="sayhelloto">
      <isd:java class="hello.helloserver" static="false"/>
      </isd:provider>
      </isd:service>
      上述xml代码所包含的信息和我们在web界面的管理工具中所输入的信息一样。接下来,输入下面的命令,从命令行部署helloworld服务:


      java org.apache.soap.server.servicemanagerclient
      http://localhost:8080/apache-soap/servlet/rpcrouter
      deploy deploymentdescriptor.xml
      deploymentdescriptor.xml是上面显示的描述部署信息的xml文件名字。要验证服务是否部署成功,输入以下命令:


      java org.apache.soap.server.servicemanagerclient
      http://localhost:8080/apache-soap/servlet/rpcrouter query urn:hello
      这时,我们应该看到和deploymentdescriptor.xml文件内一样的xml。

      2.3、helloworld客户程序
      编写客户程序要比编写helloworld服务复杂得多。不过,你应该不会对此感到奇怪,因为前面已经提到,客户程序(至少)必须负责设置call对象,这需要不少工作。顺便说一下,本系列文章的第四篇将介绍一个框架,这个框架以java 2 1.3版新引入的动态代理类为基础,使创建客户程序和创建服务一样简单。

      listing 1显示了完整的客户程序。接下来我们一步一步地仔细看看这个程序。这个程序需要一个必不可少的参数:程序要向他说hello信息的用户名字。


      listing 1: client.java
      package hello;
      import java.net.url;
      import java.util.vector;
      import org.apache.soap.soapexception;
      import org.apache.soap.constants;
      import org.apache.soap.fault;
      import org.apache.soap.rpc.call;
      import org.apache.soap.rpc.parameter;
      import org.apache.soap.rpc.response;

      public class client
      {
      public static void main(string[] args) throws exception
      {
      if(args.length == 0)
      {
      system.err.println("usage: java hello.client [soap-router-url] ");
      system.exit (1);
      }
      try
      {
      url url = null;
      string name = null;
      if(args.length == 2)
      {
      url = new url(args[0]);
      name = args[1];
      }
      else
      {
      url = new url("http://localhost:8080/apache-soap/servlet/rpcrouter");
      name = args[0];
      }
      // 构造call对象
      call call = new call();
      call.settargetobjecturi("urn:hello");
      call.setmethodname("sayhelloto");
      call.setencodingstyleuri(constants.ns_uri_soap_enc);
      vector params = new vector();
      params.addelement(new parameter("name", string.class, name, null));
      call.setparams(params);
      // 发出调用
      response resp = null;
      try
      {
      resp = call.invoke(url, "");
      }
      catch( soapexception e )
      {
      system.err.println("caught soapexception (" + e.getfaultcode() + "): " +
      e.getmessage());
      system.exit(-1);
      }

      // 检查应答
      if( !resp.generatedfault() )
      {
      parameter ret = resp.getreturnvalue();
      object value = ret.getvalue();
      system.out.println(value);
      }
      else
      {
      fault fault = resp.getfault();
      system.err.println("generated fault: ");
      system.out.println (" fault code = " + fault.getfaultcode());
      system.out.println (" fault string = " + fault.getfaultstring());
      }
      }
      catch(exception e)
      {
      e.printstacktrace();
      }
      }
      }
      客户程序首先设置call对象,它需要如下信息:

    • 被调用服务的对象id,它通过call对象的settargetobjecturi()方法设置。本例的对象id是urn:hello。
    • 待调用方法的名字,它通过call对象的setmethodname()方法设置。本例的方法名字是sayhelloto()。
    • 参数的编码方式,它通过call对象的setencodingstyleuri()方法设置。本例我们使用标准的soap编码方式,这种编码方式由名称空间http://schemas.xmlsoap.org/soap/encoding/定义。
    • 方法调用的参数通过call对象的setparams()方法设置。setparams()方法的参数是一个java vector(向量)。这个向量包含所有的参数,向量中索引为0的参数是被调用方法从左边数起的第一个参数,索引为1的参数是被调用方法从左边数起的第二个参数,依此类推。向量中的每一个元素都是一个org.apache.soap.rpc.parameter的实例。parameter构造函数要求指定参数的名字、java类型和值,还有一个可选的编码方式。如果指定了null编码方式(正如本例所做的那样),则默认使用call对象的编码方式。虽然每一个参数对应着一个名字,但这个名字可以设置成任何内容,apache soap服务器调用方法时不会用到这个名字。因此,绝对有必要让向量中参数的次序和被调用方法的参数次序一致。
        下面的代码片断显示了客户程序创建call对象的过程:


        // 构造call对象
        call call = new call();
        call.settargetobjecturi("urn:hello");
        call.setmethodname("sayhelloto");
        call.setencodingstyleuri(constants.ns_uri_soap_enc);
        vector params = new vector();
        params.addelement(new parameter("name", string.class, name, null));
        call.setparams(params);
        现在,该是实际调用helloworld远程服务所提供方法的时候了。为此,客户程序调用了call对象的invoke()方法,这个方法返回一个org.apache.soap.rpc.response对象,如下所示:


        // 发出调用
        response resp = null;
        try
        {
        resp = call.invoke(url, "");
        }
        catch( soapexception e )
        {
        system.err.println("caught soapexception (" + e.getfaultcode() + "): " +
        e.getmessage());
        system.exit(-1);
        }
        接下来,客户程序检查response对象。如果方法调用过程中出现了错误,generatefault()方法返回一个true值,客户程序提取并显示实际的错误信息:


        fault fault = resp.getfault();
        system.err.println("generated fault: ");
        system.out.println (" fault code = " + fault.getfaultcode());
        system.out.println (" fault string = " + fault.getfaultstring());
        如果方法调用成功,则客户程序提取并显示hello信息:


        // 检查应答
        if( !resp.generatedfault() )
        {
        parameter ret = resp.getreturnvalue();
        object value = ret.getvalue();
        system.out.println(value);
        }
      • 发表评论 共有条评论
        用户名: 密码:
        验证码: 匿名发表