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

developerWorks 中国  >  SOA and Web services  >

动态电子商务的Web服务: 调用Web服务 第三部分

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

李 劲 (jinli@ca.ibm.com)IBM软件解决方案多伦多实验室应用开发技术中心

2002 年 10 月 01 日

动态电子商务是电子商务发展的目标,而Web服务是其核心技术,也是Web的下一个革新。Web服务将改变企业之间的商务运作和企业对企业(B2B)的应用的设计与开发。

本文摘选自即将出版的 《动态电子商务的web服务》一书中的第五章,也是调用Web服务系列的第三部分。在 第一部分中,我们讲述了SOAP的架构、基本技术知识、在Web服务应用上常用的技术,在 第二部分中,我们详细讲述了如何使用SOAP来调用Web服务,在本文中,我们将讨论Web服务请求者开发时场景和不同的Web服务访问方法。然后,介绍Web服务代理程序的编制、生成和作用。

Web服务请求者开发时场景

在本书的第3章中已经介绍了主要的Web服务请求者的开发要求和步骤。概述地说,Web服务请求者的开发周期是:发现-访问-测试(Discover-Access-Test)。一般情况下,可以把Web服务请求者的构建归纳为下面的3种方案:

  • 静态连接的方案(Static Requestor Scenario)
  • 开发时动态连接的方案(Provider Dynamic Requestor Scenario)
  • 运行时动态连接的方案(Type Dynamic Requestor Scenario)

封面艺术 书籍 《动态电子商务的Web服务》
本文摘选自清华大学出版社出版的由IBM资深工程师李劲撰写的 《动态电子商务的Web服务》一书(于 2002 年 11 月出版)。有关更多信息,请参阅该书的 内容简介及目录页面(请参阅 参考资料)。

Web服务发现事务是去寻找有关已发布了的服务信息,主要通过两种途径--注册中心(如UDDI)和使用WS-Inspection标准规范介绍部署了的Web服务的应用服务器。访问和测试事务是相连的,主要是调用Web服务的操作。总的来说,访问有3种不同的方法,在下面的章节里,将详细描述如何使用各种方法。





回页首


方案1:静态连接方法

这是一个最简单而且最常用的方法,只访问指定的服务接口和绑定。由于这个方法是基于某一个描述Web服务的WSDL文档以及端口,所以编写的服务调用编码是静态的。用户不能任意使用这个连接方法的编码去访问另一个端口或者同样的服务接口,如图5-7所示。


图5-7 静态连接的场景
图5-7 静态连接的场景

代码表5-21为如何使用静态连接方法去调用第3章中的电话号码簿员工查询Web服务。从示例中可以看到编码已经固定了访问的Web服务的端口和URI,所以是进行静态的服务调用。


代码表5-21 静态连接方法的示例
// Set up SOAP Call object
Call call = new Call();
SOAPMappingRegistry smr = call.getSOAPMappingRegistry();
org.apache.soap.encoding.soapenc.BeanSerializer ser_0 =
    new org.apache.soap.encoding.soapenc.BeanSerializer();
org.apache.soap.encoding.soapenc.BeanSerializer deSer_0 =
    new org.apache.soap.encoding.soapenc.BeanSerializer();
smr.mapTypes("http://schemas.xmlsoap.org/soap/encoding/",
    new QName("http://acme.com/","Employee"),
    com.acme.Employee.class, ser_0, deSer_0);
// Access the specified Web service port
private String stringURL = "http://localhost:9080/
    TelephoneDirectoryWeb/servlet/rpcrouter";
String targetObjectURI = "http://tempuri.org/
    com.acme.TelephoneDirectory";
String SOAPActionURI = "";
// Identify the service operation and its parameters
call.setMethodName("findEmployeeByID");
call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
call.setTargetObjectURI(targetObjectURI);
Vector params = new Vector();
Parameter idParam = new Parameter("id", int.class,
    new Integer(id), Constants.NS_URI_SOAP_ENC);
params.addElement(idParam);
call.setParams(params);
// Invoke the Web service
Response resp = call.invoke(new URL(stringURL), SOAPActionURI);

静态连接方法的特征如下:

  • 已知Web服务接口的定义、端口类型、绑定等。
  • 固定的服务提供者,已知其端口。
  • 编写的代码程序只能对这一个Web服务提供者进行调用。




回页首


方案2:开发时动态连接方法

这是动态连接方法之一,也是最常见的动态连接方法。使用这个方法的目的是只需编写一个Web服务调用程序,就能够调用多个服务提供者所提供的同一Web服务。

如图5-8所示,开发时动态连接的原因可能是想向多个航空公司询问航班和价格,然后选择最适合的航空公司、航班和价格才定票。在这种情况下,各个航空公司提供的询问Web服务会使用统一的服务接口,这就需要通过商务注册中心UDDI寻找提供所需Web服务的航空公司。代码表5-22为如何使用开发时动态连接方法,对多个提供第3章中的电话号码簿员工查询Web服务的服务提供者进行调用。使用UDDI的API去寻找提供此Web服务的服务提供者,然后逐一进行服务调用。主要的编程步骤如下:

  • 访问某个商务注册中心UDDI。
  • 利用服务接口定义,搜集UDDI中的tModel。
  • 找出服务提供者的Business Entity。
  • 根据Business Entity里的Business Service,找出服务绑定。
  • 从绑定中找出服务访问的端点,然后调用此服务。

图5-8 开发时动态连接的场景
图5-8 开发时动态连接的场景

代码表5-22 开发时动态连接方法的示例
// UDDIAccessor class
// Access the IBM Test Registry
String inquiryAPI = "http://www-3.ibm.com/services/uddi/
    testregistry/inquiryapi";
String publishAPI = "http://www-3.ibm.com/services/uddi/
    testregistry/protect/publishapi";
UDDIProxy proxy = new UDDIProxy();
Vector result = new Vector();
proxy.setInquiryURL(inquiryAPI);
proxy.setPublishURL(publishAPI);
// Use service type info to find all the tModels and collect them
TModelBag tmb = new TModelBag();
Vector tmbv = new Vector();
tmb.setTModelKeyVector(tmbv);
TModelList tml = proxy.find_tModel(servicename, null, 0);
TModelInfos tmis = tml.getTModelInfos();
Vector v1 = tmis.getTModelInfoVector();
for (int i1=0; i1 < v1.size(); i1++)
{
    TModelInfo tmi = (TModelInfo)v1.elementAt(i1);
    String tmk = tmi.getTModelKey();
    System.out.println("TModel="+tmi.getNameString()+" "+tmk);
    TModelKey tmKey = new TModelKey(tmk);
    tmbv.addElement(tmKey);
    if (i1==14)
    {
        System.out.println("Too many tModels-stop");
        break;
    }
}
// Find the business entity
BusinessList bl = proxy.find_business(provider, null, 0);
BusinessInfos bis = bl.getBusinessInfos();
Vector v2 = bis.getBusinessInfoVector();
// Find the business services of the business entity
for (int i2 = 0; i2 < v2.size(); i2++)
{
    BusinessInfo bi = (BusinessInfo)v2.elementAt(i2);
    String bkey = bi.getBusinessKey();
    System.out.println(" Business="+bi.getNameString()+" "+bkey);
    // find service of business
    ServiceInfos sis = bi.getServiceInfos();
    Vector v3 = sis.getServiceInfoVector();
    // Find bindings of service that implements the tModel
    for (int i3 = 0; i3 < v3.size(); i3++)
    {
        ServiceInfo si = (ServiceInfo)v3.elementAt(i3);
        String skey = si.getServiceKey();
        System.out.println(" Service="+si.getNameString()+" "+skey);
        // find binding of service that implements tmodel
        BindingDetail bd = proxy.find_binding(null, skey, tmb, 0);
        Vector v4 = bd.getBindingTemplateVector();
        if (v4.size()==0)
            System.out.println(" no binding templates");
        for (int i4 = 0; i4 < v4.size(); i4++)
        {
            BindingTemplate bt = (BindingTemplate)v4.elementAt(i4);
            // Collect access points from the bindings
            AccessPoint accessPoint = bt.getAccessPoint();
            System.out.println("AccessPoint="+accessPoint.getText());
            // add access point to result
            result.addElement(accessPoint.getText());
        }
    }
}
// Use the access points to dynamically invoke the service
UddiAccessor uddi = new UddiAccessor();
Vector endpoints = uddi.findServiceImplementations(provider, service);
for (int i=0; i < endpoints.size(); i++)
{
    String endpoint = (String)endpoints.elementAt(i);
    proxy.setEndPoint( new java.net.URL(endpoint) );
    try
    {
        // invoke the service via the proxy like this:
        // proxy.MethodWeWantToInvoke()
    }
    catch (Exception e)
    {
        // handle exceptions here
    }
}

开发时动态连接方法的特征如下:

  • 已知Web服务接口的定义、端口类型、绑定等。
  • 没有固定的服务提供者。
  • 编写的代码程序能对提供此Web服务的不同服务提供者进行调用。




回页首


方案3:运行时动态连接方法

这种连接方法与方案2相似,但是要求更高,因为没有Web服务的接口定义,而需要在运行时到商务注册中心UDDI首先把它寻找出来,然后再按照方案2的方法进行连接。由于这种连接方法的参数变化多,所以一般情况下使用此方法时,都会提供一个客户机界面让用户选择需要的服务接口定义。运行时根据商务分类寻找某行业的公司,然后从这些公司中寻找所需的服务接口定义。接下来的步骤与方案2相同。

运行时动态连接方法的特征如下:

  • 没有Web服务接口的定义。
  • 没有固定的服务提供者。
  • 编写的代码程序能对所需的商务分类中的某行业提供的Web服务进行调用。




回页首


Web服务代理对象

在调用Web服务时,需要向服务提供者发出一个SOAP请求消息。在前面已经解释了SOAP请求消息的结构,当然可以按照其要求构建SOAP消息,但是这样就非常麻烦而且容易出错。所以,一般都是通过代理对象(Proxy Object)来调用Web服务。Web服务代理可以基于如何编程语言,它的主要目的是能够通过一个简单方便的RPC调用接口去访问Web服务。下面介绍如何使用WSAD的Web服务客户机生成向导,为电话号码簿员工查询Web服务示例创建服务代理。在WSAD工作台上使用Web透视图,单击TelephoneDirectoryService.wsdl文档,通过菜单项中的"生成Java代理"来启动如图5-9所示的Web服务客户机生成向导。


图5-9 WSAD的Web服务客户机生成向导
图5-9 WSAD的Web服务客户机生成向导

在默认的状态下,单击"完成"而生成如代码表5-23所示的TelephoneDirectory Proxy.java Web服务代理程序。


代码表5-23 TelephoneDirectory服务代理
package proxy.soap;
import java.net.*;
import java.util.*;
import org.w3c.dom.*;
import org.apache.soap.*;
import org.apache.soap.encoding.*;
import org.apache.soap.encoding.soapenc.*;
import org.apache.soap.rpc.*;
import org.apache.soap.util.xml.*;
import org.apache.soap.messaging.*;
public class TelephoneDirectoryProxy
{
    private Call call = createCall();
    private URL url = null;
    private String stringURL = "http://localhost:9080/
        TelephoneDirectoryWeb/servlet/rpcrouter";
    public TelephoneDirectoryProxy()
    {
    }
    public synchronized void setEndPoint(URL url)
    {
        this.url = url;
    }
    public synchronized URL getEndPoint() throws MalformedURLException
    {
        return getURL();
    }
    private URL getURL() throws MalformedURLException
    {
        if (url == null && stringURL != null && stringURL.length() > 0)
        {
            url = new URL(stringURL);
        }
        return url;
    }
    public synchronized com.acme.Employee findEmployeeByID(int id)
        throws Exception
    {
        String targetObjectURI = "http://tempuri.org/
            com.acme.TelephoneDirectory";
        String SOAPActionURI = "";
        If (getURL() == null)
        {
            throw new SOAPException(Constants.FAULT_CODE_CLIENT,
                "A URL must be specified via 
                TelephoneDirectoryProxy.setEndPoint(URL).");
        }
        call.setMethodName("findEmployeeByID");
        call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
        call.setTargetObjectURI(targetObjectURI);
        Vector params = new Vector();
        Parameter idParam = new Parameter("id", int.class,
            new Integer(id), Constants.NS_URI_SOAP_ENC);
        params.addElement(idParam);
        call.setParams(params);
        Response resp = call.invoke(getURL(), SOAPActionURI);
        //Check the response.
        if (resp.generatedFault())
        {
            Fault fault = resp.getFault();
            call.setFullTargetObjectURI(targetObjectURI);
            throw new SOAPException(fault.getFaultCode(),
                fault.getFaultString());
        }
        else
        {
            Parameter refValue = resp.getReturnValue();
            return ((com.acme.Employee)refValue.getValue());
        }
    }
    private static Call createCall()
    {
        Call call = new Call();
        SOAPMappingRegistry smr = call.getSOAPMappingRegistry();
        org.apache.soap.encoding.soapenc.BeanSerializer ser_0 =
            new org.apache.soap.encoding.soapenc.BeanSerializer();
        org.apache.soap.encoding.soapenc.BeanSerializer deSer_0 =
            new org.apache.soap.encoding.soapenc.BeanSerializer();
        smr.mapTypes("http://schemas.xmlsoap.org/soap/encoding/",
          new QName("http://acme.com/","Employee"),
          com.acme.Employee.class, ser_0, deSer_0);
        return call;
    }
}

基于代码表5-23的代理程序,下面详细解释其重要的部分代码。首先,需要一个SOAP Call对象,它在org.apache.soap.rpc包中的Call对象负责完成客户机和服务器之间的消息传送。

Call call = new Call();

其次,需要Web服务的端口地址,也是SOAP router servlet的URL。

private String stringURL = "http://localhost:9080/TelephoneDirectoryWeb/servlet/rpcrouter";

这个Java代理只有一个findEmployeeByID的方法,在调用此方法前,必须按照下面的步骤装置Call对象:

  • 服务的URN
    String targetObjectURI = "http://tempuri.org/
        com.acme.TelephoneDirectory";
    call.setTargetObjectURI(targetObjectURI);
    

  • 服务的方法名称和编码规则
    call.setMethodName("findEmployeeByID");
    call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);
    

  • 将被调用方法的参数
    Vector params = new Vector();
    Parameter idParam = new Parameter("id", int.class,
        new Integer(id), Constants.NS_URI_SOAP_ENC);
    params.addElement(idParam);
    call.setParams(params);
    

  • 调用服务
    Response resp = call.invoke(getURL(), SOAPActionURI);





回页首


总 结

到目前为止,我们已经讲述了SOAP的架构、基本技术知识、在Web服务应用上常用的技术以及如何使用SOAP来调用Web服务, 讨论了Web服务请求者开发时场景和不同的Web服务访问方法以及Web服务代理程序的编制、生成和作用。在本系列的最后一篇文章中,我们将向您解释为何Web服务需要独立于绑定的可扩展调用框架,描述了WSIF及其使用示例。



参考资料

  • 调用Web服务系列 第一部分讲述了SOAP的架构、基本技术知识、在Web服务应用上常用的技术。

  • 调用Web服务系列 第二部分讨论如何使用SOAP来调用Web服务。

  • 《动态电子商务的Web服务》一书第一章: 动态电子商务的Web服务:电子商务的演变

  • 《动态电子商务的Web服务》一书即将由清华大学出版社于2002年11月出版。该书描述了有关动态电子商务的Web服务的概念和理论,并且显示了相应的程序源码和样本应用,演示了如何使用IBM的WSAD开发工具来创建Web服务和基于Web服务的J2EE应用。以多个有代表性的、基于Web服务应用的具体案例,来详细说明使用Java的实施过程以及在实施过程中应注意的事项。通过基础理论、场景示例和应用程序示例来介绍和解释Web服务的技术,包括:WSDL、SOAP、UDDI、WSFL、WSIF等。本书针对广大的中国软件开发者而写,内容深入浅出,理论结合实际,有较强的实用性。在 《动态电子商务的Web服务》网页上包括内容简介、完整的目录以及定购该书的方法。

  • 将应用程序的功能封装成为Web Services一文介绍了如何用IBM的开发工具WebSphere Studio Application Developer(WSAD)来定义、发布、定位和调用Web服务。

  • 在developerWorks的 Web services专区查找更多的资料。


关于作者

李劲, IBM软件解决方案多伦多实验室应用开发技术中心的部门负责人,他负责从收集用户要求到软件实现及测试的全部设计周期,即收集、分析用户的要求,并在软件以及WebSphere应用开发工具和面向Web应用的交互式设计技术,实现用户的要求和使用方案。 Jin目前的主要工作是Web服务和B2B应用集成,他具有十多年的软件业从业经验,曾多次使用VisualAge实现了IBM的Java和WebSphere应用服务器的客户承诺. 可以通过 jinli@ca.ibm.com与他联系.




对本文的评价










回页首


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