深蓝海域KMPRO

技巧:通过Web Service让Delphi/Visual Basic程序访问EJB

2002-08-27 09:09

技巧:通过Web Service让Delphi/Visual Basic程序访问EJB    

 
 
陈凯琪 (
chenkq@sina.com)

2002 年 5 月

问题的提出

EJB是使用Java语言编写分布式的、面向对象的、商业应用的标准组件架构(参见EJB 1.1规范)。一些企业正在将他们以前的基于COM+组件的应用移植到EJB组件技术,或者采用EJB构建新的应用,同时为保护以前的IT投入还要保留以前的采用Delphi或者Visual Basic等编程语言编写的客户端应用。这就提出了Delphi/Visual Basic访问EJB的需求。

本篇文章将首先介绍Delphi/Visual Basic访问EJB的两种方法,然后介绍Delphi/Visual Basic如何通过Web Service访问EJB:

将EJB封装为Web Service并发布到应用服务器上

Delphi访问Web Service

Visual Basic访问Web Service

最后文章中讨论了在Delphi和Visual Basic中调用Web service时遇到的中文问题。

Delphi/Visual Basic访问EJB的两种方法

Delphi通过CORBA ORB访问EJB

Delphi企业版中含有CORBA Visibroker server,可以在Borland App Server服务器端将EJB封装为CORBA服务,然后在客户端用SIDL (Simple IDL)的来描述封装EJB的CORBA服务,最后用Delphi工具idl2pas将描述文件翻译成pascal程序。这个方案在部署的时候需要在客户端安装Visibroker server。

Visual Basic通过ActiveX to EJB Bridge访问EJB

IBM WebSphere Application Server Enterprise Edition 4.x中的Enterprise Service当中包含ActiveX to EJB bridge,它为VB、VBScript以及ASP提供了一个ActiveX组件来方便地访问EJB。

客户端程序首先初始化一个ActiveX控件,该控件初始化一个Java虚拟机,ActiveX控件和Java虚拟机通过JNI(Java Native Interface)的方式交互。Java虚拟机通过Java ORB和运行在应用服务器上的EJB进行交互。Active Bridge支持使用J2EE Client Container或者直接通过JNDI的方式访问EJB。

图一:ActiveX to EJB Bridge系统结构


通过Web Service让Delphi/Visual Basic访问EJB

采用Web Service的方式封装EJB,我们理论上可以让支持Web Service的任何编程语言来访问该EJB,比如Java, Visual Basic, Visual C++,Delphi,perl,PHP等。

将EJB封装为web service的工具有很多种,这里我们以开放源码的Apache SOAP为例来说明这个开发流程,开发工具采用WSAD(IBM WebSphere Studio Application Developer)。这是一个以Java语言编写的,可以运行在Windows和Linux平台上的J2EE开发工具,支持servlet、jsp、ejb、web service和xml的开发和调试,可以在http://www-3.ibm.com/software/ad/studioappdev/免费下载试用版。我们将以一个程序员都非常熟悉的例子-HelloWorld来展示开发流程。下面,是我们动手开始做的时间了!

第一步,编写一个EJB,名字叫做HelloWorld。

1)建立一个EJB项目(EJB project):在J2EE perspective选择菜单File > New > EJB Project,输入Project Name为HelloWorldEJB,Enterprise Application project name为HelloWorldEAR,选择Finish建立该EJB项目,如图二所示:

图二:建立EJB项目


2)建立EJB:在J2EE perspective中的J2EE View中选择EJB Modules > HelloWorldEJB,然后选择菜单File > New > Enterprise Bean,输入Bean Name为HelloWorld,Bean class为ejbs.HelloWorld(这里使用package名为ejbs)。选择Finish建立该EJB。如图三所示:

图三 建立EJB


3)给HelloWorldBean添加一个方法:在J2EE perspective中的J2EE View中选择EJB Modules > HelloWorldEJB > HelloWorld > HelloWorldBean,鼠标双击打开并编辑该EJB。在该EJB后面加入方法sayHelloWorld
 /** sayHello
  * @return "Hello someone"
   * @param theName the name of someone
  */
 public String sayHello(String theName) {
  return "Hello " + theName;
 }

图四 给HelloWorldBean增加一个方法sayHello


4)将新添加的方法sayHello加入到EJB remote interface中:鼠标选中WSAD左下角处Java的Outline窗口中的方法sayHelloWorld,选择鼠标右键菜单Enterprise Bean > Promote to Remote Interface。WSAD将在HelloWorld EJB的remote Interface HelloWorld中加入一行: public String sayHello(String theName) throws java.rmi.RemoteException;

远程接口HelloWorld.java的完整的代码:
package ejbs;
/**
 * Remote interface for Enterprise Bean: HelloWorld
 */
public interface HelloWorld extends javax.ejb.EJBObject {
 /** sayHello
  * @return "Hello someone"
   * @param theName the name of someone
  */
 public String sayHello(String theName) throws java.rmi.RemoteException;
}

图五 将新添加的方法sayHello加入到EJB remote interface中


5)让WSAD生成EJB的部署代码(Deploy code)。如图六操作即可。

图六 生成EJB的部署代码


6)现在我们可以部署并测试HelloWorld EJB了! 如图七所示,运行HelloWorld EJB。

图七 选择在服务器上运行EJB


我们将看到WSAD将这个EJB项目发布到一个WebSphere v4.0 Test Environment中,启动该WebSphere测试环境。在控制台中可以看到
02.04.14 11:56:59:171 CST] 55e67dfe EJBEngine     I WSVR0037I:正在启动 EJB jar:HelloWorldEJB
[02.04.14 11:56:59:661 CST] 55e67dfe EJBEngine     I WSVR0038I:未找到 HelloWorld 的 JNDI 名,绑定起始对象名为 HelloWorldHome

[02.04.14 11:57:05:830 CST] 55e67dfe Server        A WSVR0023I:服务器 Default Server 为电子商务开放

最后WSAD将打开一个EJB Test Client窗口。选择EJB References > HelloWorld > HelloWorldHome > HelloWorld create()方法,EJB Test Client获得一个HelloWorld EJB Home Stub。如图八所示:

图八 使用EJB Test Client测试EJB(1)


7)调用HelloWorld EJB Home Stub对象的方法sayHello测试EJB。

图九 使用EJB Test Client测试EJB(2)


第二步,使用WSAD中的 Web Service生成向导生成包装该EJB的Web Service、测试该Web Service的java proxy(一个Java Bean)和JSP测试程序,最后运行生成的程序来测试这个Web Service。

1)建立一个名叫HelloWorldWeb的Web Project:选择菜单File > New > Web Project,输入project name为HelloWorldWeb,选择Enterprise Application project name为HelloWorldEAR,如图十所示。选择next命令按钮,选中available dependent JARs HelloWorldEJB.jar,如图十一所示。

图十                                                                                                                        图十一



 选择HelloWorldWeb > source,选择菜单File > New > Web Service,如图十二所示:

图十二


在"Web Service"窗口中选择部署该Web Service的Web project为HelloWorldWeb,如图十三所示:

图十三


选择Next进入下一个向导窗口,选择Web service type为EJB Web service。

选择Next进入下一个向导窗口,选择命令按钮"Browse EJB Bean…",在弹出窗口中选择EAR Project为HelloWorldEAR,EJB bean为HelloWorld(如图十四所示)

图十四


关闭弹出窗口,输入EJB JNDI name 为HelloWorldHome,如图十五所示。

图十五


选择Next进入下一个向导窗口,向导将要求输入Web service URI的名称和多个描述文件的文件名,如图十六、十七所示。Web service URI是符合uniform resource Naming convention标准(http://www.ietf.org/rfc/rfc2141.txt)web service的标识符,应用程序访问Web service的时候需要指定这个参数。

ISD文件是Apache SOAP内部使用的web service的发布描述文件。/wsdl/HelloWorld-service.wsdl文件描述了相关的web service port信息,应用程序可以通过该文件获得服务器上各种web service的信息。

<?xml version="1.0" encoding="UTF-8"?>
<definitions name="HelloWorldService"
 targetNamespace="
http://localhost:8080/HelloWorldWeb/wsdl/HelloWorld-service.wsdl"
 xmlns="
http://schemas.xmlsoap.org/wsdl/"
 xmlns:tns="
http://localhost:8080/HelloWorldWeb/wsdl/HelloWorld-service.wsdl"
 xmlns:binding="
http://www.helloworld.com/definitions/HelloWorldRemoteInterface"
 xmlns:soap="
http://schemas.xmlsoap.org/wsdl/soap/">
  <import namespace="
http://www.helloworld.com/definitions/HelloWorldRemoteInterface"
  location="
http://localhost:8080/HelloWorldWeb/wsdl/HelloWorld-binding.wsdl"/>
  <service name="HelloWorldService">
    <port name="HelloWorldPort" binding="binding:HelloWorldBinding">
      <soap:address location="
http://localhost:8080/HelloWorldWeb/servlet/rpcrouter"/>
    </port>
  </service>
</definitions>
 /wsdl/HelloWorld-bind.wsdl则具体定义了具体一个portType的操作和消息的消息格式和协议。
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="HelloWorldRemoteInterface"
 targetNamespace="
http://www.helloworld.com/definitions/HelloWorldRemoteInterface"
 xmlns="
http://schemas.xmlsoap.org/wsdl/"
 xmlns:tns="
http://www.helloworld.com/definitions/HelloWorldRemoteInterface"
 xmlns:xsd="
http://www.w3.org/2001/XMLSchema"
 xmlns:soap="
http://schemas.xmlsoap.org/wsdl/soap/">
  <message name="sayHelloRequest">
    <part name="theName" type="xsd:string"/>
  </message>
  <message name="sayHelloResponse">
    <part name="result" type="xsd:string"/>
  </message>
  <portType name="HelloWorldJavaPortType">
    <operation name="sayHello">
      <input name="sayHelloRequest" message="tns:sayHelloRequest"/>
      <output name="sayHelloResponse" message="tns:sayHelloResponse"/>
    </operation>
  </portType>
  <binding name="HelloWorldBinding" type="tns:HelloWorldJavaPortType">
    <soap:binding style="rpc" transport="
http://schemas.xmlsoap.org/soap/http"/>
    <operation name="sayHello">
      <soap:operation soapAction="" style="rpc"/>
      <input name="sayHelloRequest">
        <soap:body use="encoded" encodingStyle="
http://schemas.xmlsoap.org/soap/encoding/" namespace="http://tempuri.org/ejbs.HelloWorld"/>
      </input>
      <output name="sayHelloResponse">
        <soap:body use="encoded" encodingStyle="
http://schemas.xmlsoap.org/soap/encoding/" namespace="http://tempuri.org/ejbs.HelloWorld"/>
      </output>
    </operation>
  </binding>
</definitions>

图十六

 

图十七


3)WSAD可以生成访问刚才发布的Web service的Java类,如图十八、十九所示。该类的方法 public synchronized java.lang.String sayHello(java.lang.String theName) throws Exception包装了发布为Web service的EJB HelloWorld的方法sayHello(java.lang.String theName)。在发布EJB为Web service的向导中可以生成这个Java类(web service binding proxy),同时还可以生成使用这个类访问web service的jsp页面。

图十八

 

图十九


最后,你可以选择将Web Service发布为到UDDI服务器(比如免费的IBM的UDDI4J)上。

第三步,在Delphi中调用Web Service

使用Delphi Web Services importer将定义Web Service的WSDL文件(可以是从Web服务器上下载下来的本地wsdl文件,也可以是一个指向该文件的HTTP URL,比如http://localhost:8080/HelloWorldWeb/wsdl/HelloWorld-service.wsdl)导入,这将自动生成定义和注册interfaces and types的pascal代码。

选择菜单File > New > Other… > 出现New Iterms窗口,选择WebServices,选择Web Services importer。如图二十、二十一所示:

图二十

 

图二十一


生成的代码如下:
Unit Unit_2;
interface
uses Types, XSBuiltIns;
type

HelloWorldJavaPortType = interface(IInvokable)
    ['{521A5B71-4CB9-4FAA-82AD-0F9CCF423FB9}']
//为方便使用,我们把Delphi声明的过程 sayHello改为方法
    //procedure sayHello(const theName: WideString; out result: WideString);  stdcall;
function sayHello(const theName: WideString): WideString;  stdcall;
end;
implementation
uses InvokeRegistry;
initialization
  InvRegistry.RegisterInterface(TypeInfo(HelloWorldJavaPortType), '', 'UTF-8');
end.

现在,我们可以利用THTTPRio对象来调用Web Service。THTTPRio是Delphi提供的支持SOAP over HTTP的有源代码的SOAP支持类库。部分代码如下:

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, SoapHTTPClient, Unit2, StdCtrls;

procedure TForm_main.Button_callClick(Sender: TObject);
var
        //使用Delphi的Unit SoapHTTPClient,Delphi
        X :THTTPRio;
        //使用Unit_2中定义的HelloWorld Interface
        InterfaceVariable: HelloWorld;
        para1, para2: WideString;
begin
        X := THTTPRio.Create(nil);
        //设置Delphi在提出HTTP请求的时候设置Content-Type为text/xml; charset= UTF-8
       X.HTTPWebNode.UseUTF8InHeader := true ;

        //WSDLLocation也可以是在本地文件系统中的wsdl文件。
        X.WSDLLocation := 'http://localhost:8080/HelloWorldWeb/wsdl/HelloWorld-service.wsdl';
       
// THTTPRio 的Service参数和Port参数对应HelloWorld-service.wsdl文件中的定义。
// <service name="HelloWorldService">
//    <port name="HelloWorldPort" binding="binding:HelloWorldBinding">
  //      <soap:address location="
http://localhost:8080/HelloWorldWeb/servlet/rpcrouter"/>
   //    </port>
  //</service>
        X.Service := 'HelloWorldService';
        X.Port := 'HelloWorldPort';

        InterfaceVariable := X as HelloWorld;
        para1 := '中文';
        //从输入文本框中获得输入参数
        para1 := Edit_para1.Text;
        para2 := InterfaceVariable.sayHello(para1);
        //显示调用web service返回的结果
        Label_return_value.Caption := 'Return Value: ' + para2;
        X.free;
end;

使用Microsoft SOAP Toolkit 编写SOAP客户端程序

使用Microsoft SOAP Toolkit - High Level API编写SOAP客户端程序非常简单,只需要四条语句就可以完成基本的SOAP请求。使用SOAP Toolkit之前,需要在项目增加Microsoft XML v3.0和Microsoft SOAP Type Library的引用(reference)。

如下是访问HelloWorld web service的代码:

'定义Client为可以发送SOAP请求到服务器并能处理服务器返回的应答的SoapClient Object

Private Client As SoapClient

Set Client = New SoapClient

'使用WSDL文件作为输入文件初始化SoapClient

Client.mssoapinit "
http://localhost:8080/HelloWorldWeb/wsdl/HelloWorld-service.wsdl"

'调用sayHello方法

txtEquals.Text = CStr(Client.sayHello(txtA.Text)

这段使用High Level API的代码在运行的时候,MS SOAP Toolkit将报错:

WSDLReader: No valid schema specification was found. This version of the SOAP Toolkit only supports 1999 and 2000 XSD schema specifications

 错误的原因是使用High Level API,Microsoft SOAP Toolkit提交的XML格式的SOAP请求和Apache SOAP要求的格式不同。文章http://www-1.ibm.com/support/techdocs/atsmastr.nsf/PubAllNum/TD100540详细地介绍了格式的具体不同。

使用Low Level API编写SOAP客户端程序需要使用程序员使用SoapConnector的一个实现HttpConnector来建立HTTP连接,使用SoapSerializer object来建立一个SOAP message并发出SOAP请求;SoapReader从HttpConnector对象中获得返回结果,生成一个XML DOM对象SoapReader.DOM,使用Microsoft XML API分析SoapReader.DOM获得返回结果。

文章http://www.soapuser.com/client4.html非常清楚地介绍了如何使用Low Level API编写SOAP客户端程序。访问HelloWorld EJB Web Service的demo代码请在此处下载。

Delphi Web Service中文问题

通过监控SOAP调用的全过程我们发现Delphi向Web Server提出HTTP请求的时候,数据是以UTF-8编码通过POST方式提交给Web服务器的,但是另一方面,Delphi在HTTP请求头中设置Content-Type为text/xml,造成Apache SOAP Toolkit认为POST方式的数据是iso8859-1编码,出现了所谓中文问题。

我们可以设置Delphi在提出HTTP请求的时候设置Content-Type为text/xml; charset= UTF-8。具体做法是在创建了 THTTPRio对象之后设置它的属性UseUTF8InHeader

X.HTTPWebNode.UseUTF8InHeader := true ;

需要注意SoapHTTPTrans.pas代码 header := 'text/xml charset=UTF-8'; 当中少了一个分号";",这造成服务器认为soap 客户程序POST上来的数据是iso8859-1编码。

在Delphi选择Project > Add to Project,加入Delphi6\Source\Soap\SoapHTTPTrans.pas。修改该行代码为:header := 'text/xml; charset=UTF-8';

微软SOAP Toolkit 2.0 with SP2的中文问题

微软SOAP Toolkit 2.0 with SP2的中文问题的原因和Delphi一样。

HttpConnector有一个在SOAP Toolkit 文档中没有标明的属性HTTPCharset,设置为utf-8即可。 Connector.Property("HTTPCharset") = "utf-8"

Web Service程序的调试

我们可以使用监控程序来监控Web Service的全过程,XML格式的HTTP访问请求和返回。

微软的SOAP Toolkit中带的SOAP Toolkit监视器MSSoapT、Apache soap toolkit中的工具、或者其他的网络监控工具(比如network sniffer)。

结束语:

Web Service为传统的GUI程序提供了使用远程过程调用(RPC)的一种很好的方式,但是各家厂商对于Web Service的实现还是稍有差异(正如EJB, CORBA),这就提出了不同实现的互相操作性的问题。此外,在我们国家的应用当中,还需要注意中文问题。

参考资料:

作者简介:

陈凯琪:对Java和Linux有浓厚的兴趣,精通J2EE应用服务器方面的技术,可以通过
chenkq@sina.com 与他联系
 

相关推荐