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 与他联系 |