IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  SOA and Web services  >

探索 Web 服务总线,第 2 部分: 比较总线过滤器和 Axis 处理程序

了解一下 Web 服务总线和 Axis 之间的比较

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Greg Flurry (flurry@us.ibm.com), 高级技术人员, IBM Software Group

2003 年 1 月 01 日

与其他 Web 服务框架相比,Web 服务总线(Web Services Bus)提供了什么?首先,它继承了 Web 服务调用框架(Web Services Invocation Framework,WSIF)的特点,这意味着它总是对规范形式的数据进行操作,而不是对特定于 SOAP 的形式的数据进行操作。在这个系列(由两部分组成)的第二篇文章中,Greg Flurry 将带领您一步一步地研究一个简单的示例 Web 服务,从而向您展示 Web 服务总线与基于 SOAP 的 Apache Axis 相比怎么样。但并不是每个决定都是非此即彼;本文还将让您轻松地好好了解一下这两个框架可以互操作的情况。

IBM alphaWorks 上的 Web Services Toolkit 版本 3.2.2 中提供的 Web 服务总线是用于构造 Web 服务处理器的框架。(请参阅下面的 参考资料部分以获取到 WSTK 的链接。)总线提供一种增强的环境使服务请求者(客户机端 入站(on-ramp))和服务提供者(服务器端 出站(off-ramp))能通过 Web 服务实现动态电子商务。

在本系列的 第 1 部分中,您了解了如何利用总线的独特功能发布、发现和调用 Web 服务实现。在本文中,您将了解到关于总线提供的 过滤器的更多信息,该过滤器可以操纵入站和出站中的请求和响应。总线过滤器与 Apache Axis 处理程序类似。总线过滤器与继承了 Web 服务调用框架(WSIF)的特点的总线一致,它对规范形式的信息进行操作,而不对特定于 SOAP 的形式的数据进行操作,这与当前的 Axis 处理程序一样。

总线原型是 WSTK 版本 3.2.2 的一部分,它包括总线各种功能的许多演示(包括几个展示如何部署和使用总线过滤器的示例)。在本文中,您将在这些演示的基础上加入新的示例,并用它们来更深入地研究总线过滤器。另外,我还将对基于总线过滤器的实现和基于 Axis 处理程序的实现进行比较,以说明总线过滤器的优点。请参阅总线原型实现随带的文档以获取额外的详细信息。(请参阅 参考资料。)

示例组件

本文中示例的基础是普遍存在的股票报价 JavaBean。我将对该示例的总线版本和 Axis 版本使用同一个实现;该实现与本系列第 1 部分中使用的实现略有不同。总线过滤器和 Axis 处理程序(它们都归入一般类别的 拦截器(interceptor))使得示例很有趣。示例中的拦截器使用一项非常简单的技术来模拟有效负载的压缩(解压缩)。 图 1显示了示例中使用的总体体系结构。


图 1. 示例的客户机-服务器体系结构
示例的客户机-服务器体系结构

图 1显示了示例的客户机端和服务器端。在客户机端,服务请求者请求报价,使用某种形式的 Web 服务引擎与服务提供者进行交互。这个引擎可以是 Axis 客户机引擎,也可以是总线入站引擎。该引擎调用一个拦截器指出所使用的压缩技术,这个拦截器检查请求、可能对内容进行压缩并将 上下文插入请求中。最后,引擎将修改过的请求发送给服务提供者。

在服务器端,Axis 服务器引擎或总线出站引擎接收传入的请求并调用一个拦截器,该拦截器检查请求并可能根据从请求中检索到的上下文中指出的压缩技术对内容进行解压缩。引擎将修改过的请求继续传递给实际的服务提供者(在本例中是 JavaBean)。在这个示例中,来自于提供者的响应不通过任何拦截器进行传递,虽然 Axis 和总线都支持该功能。

股票报价服务

清单 1 包含示例中所使用的股票报价服务的实现(即 SQ.java )。


清单 1. SQ.java ― 股票报价服务示例
package gaf.filter.sq;
public class SQ {
    public float getQuote(String symbol) throws Exception {
        if (symbol.equals("XXX")) {
            return ((float) 55.25);
        } else if (symbol.equals("YYY")) {
            return ((float) 125.35);
        } else {
            return ((float) 0);
        }
    }
}

这个股票报价服务实现为符号 XXX 和 YYY 返回唯一的值,对别的符号则返回零。该服务不必知道任何可能发生的压缩(解压缩),注意到这一点是很重要的。

股票报价客户机

两个客户机都已经被编写为由 WSDL 驱动。这对总线客户机来说是强制的,因为 WSIF 是由 WSDL 驱动的。这对 Axis 来说是可选的,并使得 Axis 客户机比必要情况下要复杂一点;但一般来说,这是一个好习惯,因为它使得 Axis 能够自动抽取调用服务所必需的信息。

总线客户机
清单 2显示了客户机总线版本代码的大致内容。本文随带的 代码包包括了这两个客户机的完整源代码。


清单 2. 样本客户机总线版本代码的大致内容
package services.demos.wsbus.client.examples;
public class BusSQClient extends SQClientBase {
    public static void main(String[] args) throws Exception {
        System.setProperty(
            WSIFServiceFactory.WSIFFACTORY_CLASSNAME,
            "com.ibm.wsbus.wsif.WSIFBusServiceFactory");
        WSIFBusServiceFactory factory =
            (WSIFBusServiceFactory) WSIFServiceFactory.newInstance();
        WSIFService service = factory.getService(null, args[0]);
        getQuote(service, "XXX");
        getQuote(service, "YYY");
        getQuote(service, "ZZZ");
    }
    private static void getQuote(WSIFService service, String sym) 
        throws Exception {
        javax.wsdl.Port wp = getPort();
        WSIFPort port = service.getPort(wp.getName());
        WSIFOperation operation =
            port.createOperation("getQuote", "getQuoteRequest", null);
        WSIFMessage inputMessage = operation.createInputMessage();
        WSIFMessage outputMessage = operation.createOutputMessage();
        WSIFMessage faultMessage = operation.createFaultMessage();
        inputMessage.setObjectPart("symbol", sym);
        boolean operationSucceeded =
            operation.executeRequestResponseOperation(
                inputMessage, 
                outputMessage,
                faultMessage);
        System.out.println("The quote: " + outputMessage.getFloatPart("return"));
    }
}

在客户机的总线版本中, BusSQClient 首先采取必要的步骤以确保使用了总线;这就要求适当地设置一个系统属性并使用特定于总线的工厂来生成服务表示。接下来,客户机获取传入的 WSDL( arg[0] )所定义的股票报价服务的一个表示。为获取报价,客户机获取为该服务定义的单个端口;用这个端口作为工厂,客户机获得指出的说明(操作名和参数名)所定义的 getQuote 操作的表示。最后,客户机调用该操作。

Axis 客户机
清单 3包含客户机 Axis 版本代码的大致内容。


清单 3. 样本客户机 Axis 版本代码的大致内容
package services.demos.wsbus.client.examples;
public class AxisSQClient extends SQClientBase {
    public static void main(String[] args) throws Exception {
            
        QName serviceQN = wsdlService.getQName();
        Service service = new Service(new URL(args[0]), serviceQN);
        getQuote(service, "XXX");
        getQuote(service, "YYY");
        getQuote(service, "ZZZ");
    }
    private static void getQuote(Service service, String sym) 
        throws Exception {
        QName portQN = new QName(def.getTargetNamespace(), getPort().getName());
        Call call = (Call) service.createCall(portQN, "getQuote");
        Object in[] = new Object[1];
        RPCParam p = new RPCParam("", "symbol", sym);
        in[0] = p;
                
        Float ret = (Float) call.invoke(in);
        System.out.println("The quote: " + ret);
    }
}

在客户机的这个版本中, AxisSQClient 首先获取传入的 WSDL( arg[0] )所定义的股票报价服务的一个表示。为获取报价,客户机为该服务中唯一的端口获取一个操作的表示。最后,客户机调用这个操作。

这两个客户机对于可能发生或可能不发生的任何压缩都一无所知,注意到这一点是很重要的。

拦截器

拦截器当然是作为补充,因为解压缩拦截器必须理解压缩拦截器对消息主体做了什么。

总线过滤器
清单 4包含用于对请求进行压缩的总线过滤器代码的大致内容。本文的 代码包包含了完整的源代码。


清单 4. 用于对请求进行压缩的总线过滤器代码的大致内容
package services.demos.wsbus.client.filters;
public class CompressFilter implements Filter {
    private static QName name = new QName("http://gaf.filter.test", "compressType");
    public FilterAction filterRequest(WSIFRequest request, WSIFResponse response)
        throws WSBusException, FilterException {
        try {
            // get parameter
            WSIFMessage requestMessage = request.getIncomingMessage();
            sym = (String) requestMessage.getObjectPart("symbol");
            // "compress"
            if (sym.equals("XXX")) {
                System.out.println("Filter compressing with X type.");
                requestMessage.setObjectPart("symbol", "X");
                compType = "X";
            } else if (sym.equals("YYY")) {
                System.out.println("Filter compressing with Y type.");
                requestMessage.setObjectPart("symbol", "Y");
                compType = "Y";
            }
            // create element that indicates compression type
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            Document doc = factory.newDocumentBuilder().newDocument();
            Element token = doc.createElementNS(name.getNamespaceURI(), 
                                    name.getLocalPart());
            token.setAttribute("type", compType);
            // insert context that indicates compression type
            WSIFMessage contextMessage = request.getContextMessage();
            if (contextMessage == null) {
                contextMessage = new WSIFDefaultMessage();
                request.setContextMessage(contextMessage);
            }
            contextMessage.setObjectPart(name.toString(), token);
        } catch (Exception e) {
            e.printStackTrace();
            throw new WSBusException("Compressor: didn't work!");
        }
        return action;
    }
}

CompressFilter 首先从传入的请求获取符号。然后,它使用一种非常简单的算法对该符号进行压缩。压缩完成后,这个过滤器用已压缩版本代替请求中传入的符号。接下来,该过滤器创建一个上下文(它指出该符号是如何压缩的)并将这些上下文信息放入整体请求上下文中。请注意,所有这些工作都是针对规范形式的信息进行的,在与实际的服务提供者进行交互时无论使用什么样的格式和传输都是这样。

清单 5显示了用于对请求进行解压缩的总线过滤器代码的大致内容。


清单 5. 用于对请求进行解压缩的总线过滤器代码的大致内容
package gaf.filter.sq;
public class DecompressFilter implements Filter {
    private static QName name = new QName("http://gaf.filter.test", "compressType");
    public FilterAction filterRequest(WSIFRequest request, WSIFResponse response)
        throws WSBusException, FilterException {
        try {
            // get parameter
            WSIFMessage requestMessage = request.getIncomingMessage();
            sym = (String) requestMessage.getObjectPart("symbol");
            // get the compression type
            WSIFMessage contextMessage = request.getContextMessage();
            // since do 'custom' name creation from qname, use following format
            Element token = 
                (Element) contextMessage.getObjectPart(name.getNamespaceURI() + 
                   ":" + name.getLocalPart());
            compType = token.getAttribute("type");
            // "decompress"
            WSIFMessage newReq = null;
            if (!compType.equals("none")) {
                newReq = new WSIFDefaultMessage();
                request.setIncomingMessage(newReq);
            }
            if (compType.equals("X")) {
                System.out.println("Filter decompressing with type X.");
                newReq.setObjectPart("symbol", "XXX");
            } else if (compType.equals("Y")) {
                System.out.println("Filter decompressing with type Y.");
                newReq.setObjectPart("symbol", "YYY");
            }
        } catch (Exception ex) {
            throw new WSBusException("Decompressor: can't work!");
        }
        
        return action;
    }
}

DecompressFilter 首先获取传入的已压缩符号。接下来,该过滤器获取指出压缩类型的上下文部分,并从中抽取压缩类型信息。出站中的上下文名称是从上下文的 QName 派生而来的;总线使用 <namespace>:<local name> 这种形式来命名上下文。然后,这个过滤器根据压缩类型对符号进行解压缩,并用解压缩后的符号替换请求中的已压缩符号。

Axis 处理程序
清单 6包含用于对请求进行压缩的 Axis 处理程序代码的大致内容。


清单 6. 用于对请求进行压缩的 Axis 处理程序代码的大致内容
package services.demos.wsbus.client.filters;
public class CompressHandler extends BasicHandler {
    private static QName name = new QName("http://gaf.filter.test", "compressType");
    public void invoke(MessageContext context) throws AxisFault {
        try {
            // get the parameter
            SOAPPart part =
                (org.apache.axis.SOAPPart) context.getCurrentMessage().getSOAPPart();
            SOAPEnvelope envelope = part.getAsSOAPEnvelope();
            RPCElement pel = (RPCElement) envelope.getFirstBody();
            RPCParam parm = pel.getParam("symbol");
            sym = (String) parm.getValue();
            // "compress"
            if (sym.equals("XXX")) {
                System.out.println("Handler compressing with X type.");
                parm.setValue("X");
                compType = "X";
            } else if (sym.equals("YYY")) {
                System.out.println("Handler compressing with Y type.");
                parm.setValue("Y");
                compType = "Y";
            }
            // create element that indicates compression type
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            Document doc = factory.newDocumentBuilder().newDocument();
            Element token = doc.createElementNS(name.getNamespaceURI(), 
                 name.getLocalPart());
            token.setAttribute("type", compType);
            // insert a header that indicates compression type
            envelope.addHeader(new SOAPHeaderElement(token));
            part.setSOAPEnvelope(envelope);
        } catch (Exception ex) {
            AxisFault fault = AxisFault.makeFault(ex);
            throw fault;
        }
    }
}

CompressHandler 首先获取股票符号。然后,它用总线过滤器所使用的同一种算法压缩该符号。这个处理程序将该符号的已压缩版本放入请求中,替换原有的符号。接下来,该处理程序创建一个头(它指出该符号是怎样被压缩的)并将这个头放入整体请求上下文中。请注意,所有这些工作都是针对特定于 SOAP 的形式的信息进行的。这就是两种类型的压缩拦截器之间的显著区别;总线上下文或 SOAP 头中所使用的压缩算法和信息形式是相同的。

清单 7包含用于对请求进行解压缩的 Axis 处理程序代码的大致内容。


清单 7. 用于对请求进行解压缩的 Axis 处理程序代码的大致内容
package gaf.filter.sq;
public class DecompressHandler extends BasicHandler {
    private static QName name = new QName("http://gaf.filter.test", "compressType");
    public void invoke(MessageContext context) throws AxisFault {
               
        try {
            // get the parameter
            SOAPPart part =
                (org.apache.axis.SOAPPart) context.getCurrentMessage().getSOAPPart();
            SOAPEnvelope envelope = part.getAsSOAPEnvelope();
            RPCElement pel = (RPCElement) envelope.getFirstBody();
            RPCParam parm = pel.getParam("symbol");
            sym = (String) parm.getValue();
            // get the compression type
            SOAPHeaderElement header =
                envelope.getHeaderByName(name.getNamespaceURI(), name.getLocalPart());
            Element token = header.getAsDOM();
            compType = token.getAttribute("type");
            // "decompress"
            if (compType.equals("X")) {
                System.out.println("Handler decompressing with type X.");
                parm.setValue("XXX");
            } else if (compType.equals("Y")) {
                System.out.println("Handler decompressing with type Y.");
                parm.setValue("YYY");
            }
        } catch (Exception ex) {
            AxisFault fault = AxisFault.makeFault(ex);
            throw fault;
        }
    }
}

DecompressHandler 首先获取传入的已压缩符号。接下来,这个处理程序获取指出压缩类型的头,并从中抽取压缩类型。然后,该处理程序根据压缩类型对符号进行解压缩,并用解压缩的符号替换请求中的已压缩符号。与压缩拦截器一样,此处的显著差别在于总线过滤器对规范的数据进行操作,而 Axis 过滤器对特定于 SOAP 的数据进行操作;总线上下文或 SOAP 头的解压缩算法和内容是相同的。





回页首


准备安装示例

如果您想运行本文中的示例,那么您必须安装并配置 Web Services Toolkit 版本 3.2.2,您可以从 IBM alphaWorks(请参阅 参考资料)获得它。WSTK 包含一个 Axis 实现和一个 Web 服务总线原型实现。总线体系结构允许总线使用多种数据格式和传输协议进行客户机和服务器之间的通信。然而,总线原型仅用 Axis 来实现 SOAP/HTTP。

WSTK 可以在许多 Windows 和 Linux Web 应用程序服务器上运行。本文的示例假设您的 WSTK 安装在 Windows 上的 IBM WSDK 中;如果您使用的是其他平台,那么您可能需要为您的环境调整路径名、主机名、命令文件以及其他构件。(请参阅 参考资料。)

一旦安装好了 WSTK,您就应该启动自己的 Web 应用程序服务器并确认总线原型工作正常。打开一个命令窗口并进入 WSTK_HOME/services/demos/wsbus/client 目录,其中的 WSTK_HOME 指的是 WSTK 的安装目录。运行该目录下的一个或多个演示; SimpleClientFilter.bat 是一个很好的演示,因为它测试 Axis 以及总线的客户机端和服务器端。如果这些演示运行成功,那么您就可以继续进行其他的安装工作了。

接下来,您需要把本文随带的 代码包中的许多文件复制到它们的适当位置。这个包对 WSTK 目录结构进行镜像。包目录 services/demos/wsbus/client 下的文件(包括子目录下的所有文件)应该复制到 WSTK_HOME/services/demos/wsbus/client 下。包目录 services/demos/wsbus/webapp 下的文件应该复制到您的 Web 应用程序服务器的已安装应用程序目录的 WSDK WAR 目录下的相应目录中;对于 WSDK 来说,这个目录就是 WSDK_HOME/WebSphere/installedApps/wstk.ear/wstk.war ,其中的 WSDK_HOME 是 WSDK 的安装目录。

当然,要运行示例不仅仅是要复制文件,还有更多工作要做。您必须在客户机端和服务器端部署股票报价服务和拦截器。上面复制的文件包括部署必要的组件、请求股票报价以及随后取消组件的部署所需的批处理文件和部署描述符。





回页首


运行示例:同构环境

刚开始时我将只是在总线服务器上运行总线客户机,并在 Axis 服务器上运行 Axis 客户机。在后面的几部分中,我将讨论一些高级技巧,但现在我们还是先从基础部分入手。

运行总线客户机和服务器

您可以通过执行批处理文件 WSTK_HOME/services/demos/wsbus/client/RunBusSQ.bat 在总线服务器上运行总线客户机。这个批处理文件将:

  1. 为总线出站部署一个总线服务和一个解压缩过滤器。
  2. 为总线入站部署一个总线服务和一个压缩过滤器并运行 BusSQClient
  3. 取消为总线出站部署的总线服务和解压缩过滤器。

deployBusSQ.xml (上面 第 1 步中使用的部署描述符)如 清单 8所示。


清单 8. deployBusSQ.xml
<deploy force="true">
  <filters>
    <filter name="decompressor" started="true" 
        className="gaf.filter.sq.DecompressFilter"/>
  </filters>
  <busServices>
    <busService serviceName="SQBus" ...>
      <channels>
        <channelReference channel="axis"/>
      </channels>
      <targetServices>
        <targetService className="gaf.filter.sq.SQ" serviceName="SQService" .../>
      </targetServices>
      <preFilters>
        <filterReference filter="decompressor" />
      </preFilters>
    </busService>
  </busServices>  
</deploy>

这个部署描述符使总线出站将类 gaf.filter.sq.DecompressFilter 作为一个总线过滤器来部署,名为 decompressor ,并将 JavaBean gaf.filter.sq.SQ 作为一个总线服务来部署,名为 SQBus 。而且,出站还将 decompressor 列为 SQBus 的一个预过滤器(prefilter)。这就促使总线出站在将请求传递给 JavaBean 之前要运行解压缩过滤器。

上面的清单的 第 2 步使用总线入站的动态配置机制建立完整的配置以运行客户机。这对 WSTK 随带的演示所使用的缺省入站配置没有持久的影响。 清单 9 显示了为运行客户机而建立的配置(由 wsbusClientBusSQ.cfg 建立的)。


清单 9. 为运行客户机而建立的配置
<busInfo started="true" >
  <filters >
    <filter name="compressor" 
         className="services.demos.wsbus.client.filters.CompressFilter" ... />
  </filters>
  <busServices >
    <busService serviceName="SQBus" ... >
      <preFilters >
        <filterReference  filter="compressor" >
        </filterReference>
      </preFilters>
    </busService>
  </busServices>
</busInfo>

这个入站配置将类 services.demos.wsbus.client.filters.CompressFilter 作为一个总线过滤器来部署,名为 compressor ,并将 compressor 列为名为 SQBus 的总线服务的一个预过滤器。这就促使总线入站在将请求发送给 WSIF 提供者进行序列化,WSIF 提供者将其再发送给实际的服务提供者之前要先运行压缩过滤器。请注意,对总线而言,服务名称来自于描述该服务的 WSDL 的 service 元素的 name 属性。如果您深入看一下 RunBusSQ.bat ,您将发现描述部署在总线中的股票报价服务的 WSDL 的 URL 是 http://<hostname>:<port>/wstk/wsbus/ServiceDefinition?name=SQBus ,主机名和端口都设置为适合您的 Web 应用程序服务器的值。这个 URL 反映了总线出站的动态 WSDL 生成功能,该功能产生 SQBus 作为 service 元素的 name 属性。

当您执行 RunBusSQ.bat (不带参数)时,您应该会看到命令窗口中的结果与 清单 10中的结果类似。首先,您会看到出站部署成功( 第 1 步)。然后,您会看到 WSDL 文档检索成功,而且最重要的是,您还会看到客户机上的压缩和服务器上的解压缩之间来回进行成功的指示( 第 2 步)。最后,您会看到取消出站部署成功( 第 3 步)。


清单 10. 运行总线客户机和服务器:结果
C:\wstk-3.2.2\services\demos\wsbus\client>RunBusSQ.bat
 ===> Deploy the service and filter in the Bus off-ramp engine
Results: OK
 ===> Deploy service and filter in Bus on-ramp engine
 ===> Request quotes
Retrieving document at 'http://<hostname>:<port>/wstk/wsbus/ServiceDefinition?name=SQBus'.
Retrieving import document at 'http://<hostname>:<port>/wstk/wsbus/ServiceInterface?name=SQBus
Using WSIFServiceFactory
BusServiceFactory: no class name for discovery or selection. Using default.
Filter compressing with X type.
The quote: 55.25
Filter compressing with Y type.
The quote: 125.35
The quote: 0.0
 ===> Undeploy the service and filter in the Bus off-ramp
Results: OK

如果您希望这样做,那么您可以通过适当地检查控制台或日志来进行确认。您会看到一条类似于清单 11 的日志条目。


清单 11. 运行总线客户机和服务器产生的日志条目
[<date & time >] 6fa289b6 SystemOut     U Filter decompressing with type X.
[<date & time >] 6fa289b6 SystemOut     U Filter decompressing with type Y.

运行 Axis 客户机和服务器

您可以通过执行批处理文件 WSTK_HOME/services/demos/wsbus/client/RunAxisSQ.bat 在 Axis 服务器上运行 Axis 客户机。这个批处理文件将:

  1. 为 Axis 服务器部署一个服务和一个解压缩处理程序。
  2. 为 Axis 客户机部署一个服务和一个压缩处理程序。
  3. 运行 AxisSQClient
  4. 取消为 Axis 客户机部署的服务和压缩处理程序。
  5. 取消为 Axis 服务器部署的服务和解压缩处理程序。

第 1 步中使用的部署描述符( deployAxisSQ.wsdd )如 清单 12所示。


清单 12. deployAxis.wsdd
<deployment xmlns="http://xml.apache.org/axis/wsdd/" 
            xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
  <handler name="decompressor" type="java:gaf.filter.sq.DecompressHandler">
  </handler>
  <service name="SQAxis" provider="java:RPC">
    <parameter name="className" value="gaf.filter.sq.SQ"/>
    <parameter name="allowedMethods" value="getQuote"/>
    <requestFlow>
      <handler type="decompressor"/>
    </requestFlow>
  </service>
</deployment>

这个部署描述符使 Axis 服务器引擎将类 gaf.filter.sq.DecompressHandler 作为一个处理程序来部署,名为 decompressor ,并将 JavaBean gaf.filter.sq.SQ 作为一个服务来部署,名为 SQAxis 。而且,它还在 SQAxis 的请求流中列出 decompressor 。这就促使 Axis 服务器引擎在将请求传递给 JavaBean 之前先运行解压缩过滤器。

上面的清单的 第 2 步使用了 清单 13 所示的部署描述符 deployAxisClient.wsdd


清单 13. deployAxisClient.wsdd
<deployment xmlns="http://xml.apache.org/axis/wsdd/" 
            xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
  <handler name="compressor" 
      type="java:services.demos.wsbus.client.filters.CompressHandler">
  </handler>
  <service name="http://localhost/wstk/services/SQAxis/wstk/services/SQAxis" 
       provider="java:RPC">
    <requestFlow>
      <handler type="compressor"/>
    </requestFlow>
  </service>
</deployment>

这个部署描述符将类 services.demos.wsbus.client.filters.CompressHandler 作为一个处理程序来部署,名为 compressor ,并在名为 http://localhost/wstk/services/SQAxis/wstk/services/SQAxis 的服务的请求流中列出 compressor 。这就促使 Axis 客户机引擎在将请求发送给实际的服务提供者之前先运行压缩过滤器。Axis 的服务名称的来源与总线不同;描述服务的 WSDL 包含一个 SOAP binding 元素;该元素包含一个 input 元素,Axis 从这个 input 元素的 namespace 属性中派生出服务名称。如果您深入看一下 RunAxisSQ.bat ,您将发现描述部署在 Axis 中的股票报价服务的 WSDL 的 URL 是 http://<hostname>:<port>/wstk/service/SQAxis?wsdl ,主机名和端口都设置为适合您的 Web 应用程序服务器的值。这个 URL 反映了 Axis 的动态 WSDL 生成功能。上面使用的名称代表一个使用缺省端口(80)的 Web 应用程序服务器;如果您使用其他的端口,您将不得不修改部署描述符(以及对应的、用于取消部署的部署描述符)。

当您执行 RunAxisSQ.bat (不带参数)时,您在命令窗口中看到的结果应该与 清单 14中的结果非常类似。您会看到 Axis 服务器部署成功( 第 1 步)和客户机部署成功( 第 2 步)。然后,您会看到 WSDL 文档检索成功,而且最重要的是,您还会看到客户机上的压缩和服务器上的解压缩之间来回进行成功的指示( 第 3 步)。最后,您会看到取消部署成功( 第 4 步和第 5 步)。


清单 14. 运行 Axis 客户机和服务器:结果
C:\wstk-3.2.2\services\demos\wsbus\client>RunAxisSQ.bat
 ===> Deploy the service and handler in the Axis server engine
- Processing file deployment/deployAxisSQ.wsdd
<Admin>Done processing</Admin>
 ===> Deploy the service and handler in the Axis client engine
 
 ===> Request quotes
Retrieving document at 'http://<hostname>:<port>/wstk/services/SQAxis?wsdl'.
Handler compressing with X type.
The quote: 55.25
Handler compressing with Y type.
The quote: 125.35
The quote: 0.0
 ===> Undeploy the service and handler from Axis client engine
 ===> Undeploy the service and handler from the Axis server engine
- Processing file deployment/undeployAxisSQ.wsdd
<Admin>Done processing</Admin>

如果您希望这样做,那么您可以通过适当地检查控制台或日志来进行确认。您会看到一条类似于 清单 15的日志条目。


清单 15. 运行 Axis 客户机和服务器产生的日志条目
[<date & time >] 6fce09b6 SystemOut     U Handler decompressing with type X.
[<date & time >] 6fce09b6 SystemOut     U Handler decompressing with type Y.





回页首


高级示例:运行一个异构环境

现在,我将使我们的工作变得真正有趣,并将演示 Web 服务中内在的互操作性。在下面两部分中,您将首先使用总线客户机与部署在 Axis 中的服务进行交互,然后使用 Axis 客户机与部署在总线中的服务进行交互;这些示例不需要更改代码。这只是一种可能,因为总线原型用 Axis 作为它唯一的传输机制。理论上,总线可以使用另一种格式和协议(例如 RMI/IIOP),这样 Axis 处理程序将不再适用,而总线过滤器仍将适用。

运行总线客户机和 Axis 服务器

您可以通过执行批处理文件 WSTK_HOME/services/demos/wsbus/client/RunBusSQX.bat 在 Axis 服务器上运行总线客户机。这个批处理文件将:

  1. 为 Axis 服务器部署一个服务和一个解压缩处理程序。
  2. 为总线入站部署一个总线服务和一个压缩过滤器,并运行 BusSQClient
  3. 取消为 Axis 服务器部署的总线服务和解压缩处理程序。

第 1 步中使用的部署描述符( deployAxisSQ.wsdd )与 Axis 客户机到 Axis 服务器示例中用于部署 Axis 服务器组件的部署描述符是同一个;您可以在 清单 12中详细查看它的代码。

第 2 步使用总线入站的动态配置机制建立完整的配置以运行客户机。 清单 16 显示了为运行客户机而建立的配置(由 wsbusClientBusSQX.cfg 建立的)。


清单 16. 为运行客户机而建立的配置
<busInfo started="true" >
  <filters >
    <filter name="compressor" 
         className="services.demos.wsbus.client.filters.CompressFilter" ... />
  </filters>
  <busServices >
    <busService serviceName="SQService" ... >
      <preFilters >
        <filterReference  filter="compressor" >
        </filterReference>
      </preFilters>
    </busService>
  </busServices>
</busInfo>

这个入站配置与总线客户机到总线服务器示例中配置的差别仅在于总线服务的名称,其总线服务的名称必须是 SQService ,而不是 SQBus 。如果您深入看一下 RunBusSQX.bat ,您将发现描述部署在总线入站中的股票报价服务的 WSDL 的 URL 是 http://<hostname>:<port>/wstk/services/SQAxis?wsdl 。Axis 生成名称 SQService 作为总线用来标识服务的 service 元素的 name 属性的值。

当您执行 RunBusSQX.bat (不带参数)时,您应该会看到命令窗口中的结果与 清单 17中的结果类似。您会看到 Axis 服务器部署成功( 第 1 步)。然后,您会看到 WSDL 文档检索成功,而且最重要的是,您还会看到总线客户机上的压缩和 Axis 服务器上的解压缩之间来回进行成功的指示( 第 2 步)。最后,您会看到取消 Axis 服务器的部署成功( 第 3 步)。


清单 17. 运行总线客户机和 Axis 服务器:结果
C:\wstk-3.2.2\services\demos\wsbus\client>RunBusSQX.bat
 ===> Deploy the service and handler in the Axis server engine
- Processing file deployment/deployAxisSQ.wsdd
<Admin>Done processing</Admin>
 ===> Deploy service and filter in Bus on-ramp engine
 ===> Request quotes
Retrieving document at 'http://<hostname>:<port>/wstk/services/SQAxis?wsdl'.
using WSIFServiceFactory
BusServiceFactory: no class name for discovery or selection. Using default.
Filter compressing with X type.
The quote: 55.25
Filter compressing with Y type.
The quote: 125.35
The quote: 0.0
 ===> Undeploy the service and handler from the Axis server engine
- Processing file deployment/undeployAxisSQ.wsdd
<Admin>Done processing</Admin>

如果您希望这样做,那么您可以通过适当地检查控制台或日志来进行确认。您会看到一条类似于 清单 18的日志条目。


清单 18. 运行总线客户机和 Axis 服务器产生的日志条目
[<date & time >] 6fa289b6 SystemOut     U Handler decompressing with type X.
[<date & time >] 6fa289b6 SystemOut     U Handler decompressing with type Y.

运行 Axis 客户机和总线服务器

您可以通过执行批处理文件 WSTK_HOME/services/demos/wsbus/client/RunAxisSQX.bat 在总线服务器上运行 Axis 客户机。这个批处理文件将:

  1. 为总线出站部署一个总线服务和一个解压缩过滤器。
  2. 为 Axis 客户机部署一个服务和一个压缩处理程序。
  3. 运行 AxisSQClient
  4. 取消为 Axis 客户机部署的服务和压缩处理程序。
  5. 取消为总线出站部署的总线服务和解压缩过滤器。

第 1 步中使用的部署描述符( deployBusSQ.xml )和总线客户机到总线服务器示例中用于部署总线入站组件的部署描述符是同一个;您可以在 清单 8中查看它的详细内容。

第 2 步使用 清单 19 中显示的部署描述符 deployAxisClientX.wsdd


清单 19. deployAxisClientX.wsdd
<deployment xmlns="http://xml.apache.org/axis/wsdd/" 
            xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
  <handler name="compressor" 
      type="java:services.demos.wsbus.client.filters.CompressHandler">
  </handler>
  <service name=""http://mybus/#SQBus" provider="java:RPC">
    <requestFlow>
      <handler type="compressor"/>
    </requestFlow>
  </service>
</deployment>

这个部署描述符与 Axis 客户机到 Axis 服务器示例中部署描述符的区别仅在于它使用 http://mybus/#SQBus 作为服务名称,而不是使用 http://localhost/wstk/services/SQAxis/wstk/services/SQAxis 作为服务名称。请注意,对 Axis 来说,服务名称来自于描述该服务的 WSDL 的 SOAP binding 元素的 input 元素的 namespace 属性。如果您深入看一下 RunAxisSQ.bat ,您将发现描述部署在 Axis 中的股票报价服务的 WSDL 的 URL 是 http://<hostname>:<port>/wstk/wsbus/ServiceDefinition?name=SQBus 。总线为 SOAP binding 元素生成了 http://mybus/#SQBus ,因为总线原型配置的 namespaceURI 属性是 http://mybus/ ,总线附加了一个 # 以及服务的名称。

当您执行 RunAxisSQX.bat (不带参数)时,您应该会看到命令窗口中的结果看上去和 清单 20中的结果类似。您首先会看到总线入站部署成功( 第 1 步)和 Axis 客户机部署成功( 第 2 步)。然后,您会看到 WSDL 文档检索成功,而且最重要的是,您还会看到 Axis 客户机上的压缩和总线服务器上的解压缩之间来回进行成功的指示( 第 3 步)。最后,您会看到取消部署成功( 第 4 步和第 5 步)。


清单 20. 运行 Axis 客户机和总线服务器:结果
C:\wstk-3.2.2\services\demos\wsbus\client>RunAxisSQX.bat
 ===> Deploy the service and filter in the Bus off-ramp engine
Results: OK
 ===> Deploy the service and handler in the Axis client engine
 ===> Request quotes
Retrieving document at 'http://<hostname>:<port>/wstk/wsbus/ServiceDefinition?name=SQBus'.
Retrieving import document at 'http://<hostname>:<port>/wstk/wsbus/ServiceInterface?name=SQBus
Handler compressing with X type.
The quote: 55.25
Handler compressing with Y type.
The quote: 125.35
The quote: 0.0
 
 ===> Undeploy the service and handler from Axis client engine
 ===> Undeploy the service and filter in the Bus off-ramp
Results: OK

如果您希望这样做,那么您可以通过适当地检查控制台或日志来进行确认。您会看到一条类似于 清单 21的日志条目.


清单 21. 运行 Axis 客户机和总线服务器产生的日志条目
[<date & time >] 6fbe49b6 SystemOut     U Filter decompressing with type X.
[<date & time >] 6fbe49b6 SystemOut     U Filter decompressing with type Y.





回页首


结束语

在本文中,您学习了如何在总线入站(客户机)和总线出站(服务器)上使用 Web 服务总线过滤器。而且,您还看到了如何建立客户机端过滤器和服务器端过滤器之间的上下文。另外,您还比较了总线过滤器的使用和 Axis 处理程序的使用。最后,您明白了在某些情况下可以将总线客户机和 Axis 服务器混合搭配,也可以将 Axis 客户机和总线服务器混合搭配。

本系列已经说明了 Web 服务总线的几个功能。这些功能组合在一起使总线成了一个使用 Web 服务的异构的、实现动态电子商务的优秀框架。我希望现在您已经做好准备要开始亲自探索 Web 服务总线并将在自己的项目中了解它对您多么有用。



参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • 请阅读本系列的第一篇文章 探索 Web 服务总线,第 1 部分developerWorks,2002 年 11 月)。

  • 请下载本文随带的 代码包,它包含示例所需要的源代码、WSDL 文件、批处理文件以及其他文件。这些代码是写来在 WSTK 环境中运行的,并且是用和其他 WSTK 演示相同的方式构建的

  • Web Services Toolkit 版本 3.3演示了许多 Web 服务技术,包括 Web 服务总线。

  • IBM WebSphere SDK for Web Services (WSDK)为 Web 服务提供者提供了一个环境并支持 WSTK 的安装。

  • Axis为 Web 服务请求者和提供者提供了一个环境;您可以在 WSTK 中获得它。


关于作者

Greg Flurry

Greg Flurry 是 IBM 的 Software Group Emerging Technologies 小组中的成员,这个组研究用于大范围电子商务环境的前沿技术。您可以通过 flurry@us.ibm.com与 Greg 联系。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款