级别: 初级
肖菁 (jing.xiao@chinacreator.com), 软件工程师, 湖南省长沙铁道学院科创计算机系统集成有限公司软件中心
2004 年 1 月 01 日
本文将重点描述如何使用WSIF提供的WSDL扩展将本地java类、EJB用WSDL文档描述出来并且使用WSIF提供的统一方法进行调用,并且详细的介绍了WSIF中针对本地java类、EJB提供的WSDL扩展功能。
WSIF 提供的API允许使用统一的方法调用可以用WSDL描述的服务,而不需要了解该服务的实现机制和调用方法。WSIF中提供的WSDL扩展允许编程者将本地java类、EJB、JMS的消息队列、可以使用java连接器机制访问的其他应用虚拟成web服务,然后使用统一的调用方法访问这些服务。本文中作者将重点描述如何使用WSIF提供的WSDL扩展将本地java类、EJB用WSDL文档描述出来并且使用WSIF提供的统一方法进行调用,并且详细的介绍了WSIF中针对本地java类、EJB提供的WSDL扩展功能。
1 WSIF简介
WSIF是apache的web服务项目的一个子项目,目前版本是2.0,实际上是WSIF被提交给ASF后的第一次发布版本,命名为2.0是和以前非Apache发布的1.x版本相区别。
WSIF提供了一组简单的API来调用web服务而不需要了解该web服务的实现方式,更深入的说,WSIF是一组基于WSDL文件的API,他调用可以用WSDL文件描述的任何服务。
WSIF中提供的API允许编程者通过WSDL描述内容和web服务调用的抽象层打交道,而不是直接使用SOAP来调用web服务。编程者使用WSIF后就可以使用统一的编程模型来调用web服务而不需要了解该web服务是如何实现和被访问的。
WSIF 2.0中里面提供了下列内容的支持: SOAP(可以使用apache SOAP或者axis实现)、本地java类、EJBs、JMS services和其它可以通过java connector访问的应用。WSIF规定了特别的WSDL扩展使这些资源可以被当成WSDL描述的服务访问。
WSIF允许通过运行时分析web服务描述的元数据来实现无stub或者动态的调用一个web服务。他允许在运行时将更新的绑定实现插入到WSIF中,他允许调用的服务在运行时之前选择自己的绑定实现。
WSIF具有以下几个主要特征:
- 以WSDL为中心和服务的抽象定义(portType)打交道,隐藏实现细节(协议绑定和服务位置)
- 可插入式允许增加心得提供者使应用可以通过修改WSDL就可以应用新的web服务而不需要修改应用的代码
- 可扩展很容易使用新的WSDL扩展进行试验
- 灵活性很容易定制很容易和JNDI结合使用是服务被提供的位置更加透明
2 本地java绑定的WSDL扩展
WSIF中本地java绑定的WSDL扩展允许将WSDL中的抽象功能直接映射到本地java类的实际功能实现上,这种扩展意味着我们可以使用WSDL来描述一个本地Java类,然后使用WSIF提供的基于WSDL文件的服务调用方式来调用这个Java类。
在WSIF中,描述一个本地java类的WSDL的要素如下:
<definitions .... >
<!-- Java binding -->
<binding ... >
<java:binding/>
<format:typeMapping style="uri" encoding="..."/>?
<format:typeMap typeName="qname"|elementName="qname" formatType="nmtoken"/>*
</format:typeMapping>
<operation>*
<java:operation
methodName="nmtoken"
parameterOrder="nmtoken"?
returnPart="nmtoken"?
methodType="instance|static|constructor"? />?
<input name="nmtoken"? />?
<output name="nmtoken"? />?
<fault name="nmtoken"? />?
</operation>
</binding>
<service ... >
<port>*
<java:address
className="nmtoken"
classPath="nmtoken"?
classLoader="nmtoken"? />
</port>
</service>
</definitions>
|
下面的四个小节将详细介绍这些扩展的具体含义.
2.1 java:binding元素
使用该元素表示该绑定是一个java绑定
2.2 format:typemapping
format:typemapping元素允许定义WSDL消息中的抽象类型(在抽象服务描述中)和java类型的映射,他们表示同样的信息。元素中的Style属性用来规定目标类型系统(比如:使用本地类型系统表示抽象信息);在java类型系统中,这个属性的值必须是"java";这个属性的使用允许这种扩展被其他类型的绑定重用。Encoding属性必须是一个URI,这个URI用来指示本地类型和抽象类型的协调方式。WSIF中的历了一种特殊的encoding--"java" encoding-它告诉我们如何在WSDL扩展中的Java绑定中建立java类和一个抽象schema类型之间相对关系。Java encoding的详细情况下面有介绍。使用encoding属性允许我们建立自己的encoding来实现抽象类型和java类型之间的映射。
2.2.1 Java encoding
WSIF中的Java encoding是没有详细说明的,不需要详细说明是encoding信息只有在java对象中包含的信息通过某种方式变换时才有--比如序列化到SOAP消息中或者转化为其他类型系统中的某种表示。如果我们使用的WSIF消息只包含java类型系统中的类型,而且调用相应的java服务,那么我们只需要确保每个消息的部分使用java对象的正确类型表示就可以了(就像java绑定扩展中的typemapping元素定义的那样)。
当然,这些特殊的需要也会存在,不过WSIF中目前还没有提供,相信在WSIF的后续版本中会解决这个问题。
2.3 format:typemap
每个typemap元素将一种WSDL抽象类映射到一些方便的类型系统的某个类;在java绑定中,这种类型系统就是java类型系统。Typename属性是被映射抽象类型的类型标识符(必须是WSDL中已经被预定义的schema类型或者是本WSDL中定义的某种类型)。Elementname属性用来规定一个元素来代替一个类型(因为WSDL消息的内容可以被他们二者的任意一种描述)。Formtype属性是对应该抽象类型或者元素的java类型。它的值必须是某种原始java类型(char、byte、short、int、long、float、double)或者是某个java类的全名(包括包和类名).
2.4 java:operation
java:operation定义了抽象的WSDL操作和java方法之间的映射。Methodname属性规定和抽象操作对应的java方法的名字。Parameterorder属性和抽象操作中的parameterorder规定类似而且重载了这个规定。它定义了服务调用时输入参数的顺序;在java绑定中,它定义了被调用方法的签名。使用parameterorder属性允许我们映射一个抽象操作和一个java方法,即使他们签名对应的参数顺序不一致。Returnpart属性和java方法的返回类型对应的抽象输出消息。Methodtype属性规定被映射到的java方法是一个构造器、一个静态方法还是一个实例方法。
2.5 java:address
java:address元素是WSDL port元素的扩展,它允许通过java绑定将一个java对象定以成服务的endpoint。这种方式定义的port只能是java绑定形式的。Classname属性定了服务调用中要用到的java类的全名(包括包和类名,如:service.HellowWorld),可选择的classpath属性定义了调用之前需要设置的classpath,可选择的classloader属性定义了装载服务类的类装载器。如果是调用一个实例方法,服务使用者可以装载和实例化一个服务类,这告诉服务提供者要保证每个服务类有一个public的无参数的构造器可用。其他的被映射的java方法和构造器也必须在服务类中是定以成public的。
WSDL中的其他元素来自于WSDL中预定义的元素,大家可以参考WSDL规范的相关内容和说明。
好了,理论的东西讲了这么多,现在给大家演示一个例子,看看如何在应用中将本地的java类使用一个WSDL文件描述出来,并且使用WSIF的统一调用方法进行调用。
3 EJB绑定的WSDL扩展
WSIF中EJB绑定的WSDL扩展允许将WSDL中的抽象功能直接映射到EJB的实际功能实现上,这种扩展意味着我们可以使用WSDL来描述一个EJB,然后使用WSIF提供的基于WSDL文件的服务调用方式来调用这个EJB。
在WSIF中,描述一个EJB的WSDL的要素如下:
<definitions .... >
<!-- EJB binding -->
<binding ... >
<ejb:binding/>
<format:typeMapping style="uri" encoding="..."/>?
<format:typeMap typeName="qname"|elementName="qname" formatType="nmtoken"/>*
</format:typeMapping>
<operation>*
<ejb:operation
methodName="nmtoken"
parameterOrder="nmtoken"?
returnPart="nmtoken"?
interface="home|remote"? />?
<input name="nmtoken"? />?
<output name="nmtoken"? />?
<fault name="nmtoken"? />?
</operation>
</binding>
<service ... >
<port>*
<ejb:address
className="nmtoken"
jndiName="nmtoken"?
initialContextFactory="nmtoken"?
jndiProviderURL="url"?
archive="nmtoken"? />
</port>
</service>
</definitions>
|
分析它和本地java绑定的WSDL扩展,可以看到他们的不同在于ejb:binding、ejb:operation、ejb:address三个元素,下面的三个小节将介绍这三个元素的定义。
3.1 ejb:binding元素
使用该元素表示该绑定是一个ejb绑定
3.2 ejb:operation
ejb:operation定义了抽象的WSDL操作和ejb接口中提供的方法之间的映射。Methodname属性规定和抽象操作对应的java方法的名字。Parameterorder属性和抽象操作中的parameterorder规定类似而且重载了这个规定。它定义了服务调用时输入参数的顺序;在ejb绑定中,它定义了被调用方法的签名。使用parameterorder属性允许我们映射一个抽象操作和一个java方法,即使他们签名对应的参数顺序不一致。Returnpart属性和java方法的返回类型对应的抽象输出消息。EjbInterface属性用于规定被映射的方法由EJB的远程(Remote)还是本地(home)接口提供。系统默认被映射的方法由EJB的远程(Remote)接口提供。
3.3 ejb:address
java:address元素是WSDL port元素的扩展,它允许通过ejb绑定将一个ejb对象定义成服务的endpoint。这种方式定义的port只能是ejb绑定形式的。Classname属性定了服务调用中要用到的EJB的本地接口类的全名(包括包和类名,如:org.vivianj.wsif.HelloWorldHome),可选择的aechive属性定义了调用之前需要设置的classpath,可选择的classloader属性定义了装载服务类的类装载器。所有被映射的方法必须在服务类中是定义成public的。InitialContextFactory(初始化上下文的工厂类)和jndiProviderURL(JNDI提供者的URL)属性用于指定完成EJB的jndi查询时需要设置的相关参数。
4 HelloWorld实例
在我们的例子中,我们提供了两种实现,一种是本地类HelloWorld.java,一种是一个Session EJB,他们实现同样的功能。
本地类一个方法(getHelloString),它根据传入的参数返回一个字符串say Hello To + %输入的参数%,该类的实现代码如下:
4.1 HelloWorld.java实例
//HelloWorld.java
package service;
public class HelloWorld
{
public String getHelloString(String param){
return "Say hello to : " + param;
}
}
|
Session EJB的实现请查看文章后面的工程(wsif-ejb.jar)中的实现,下面是该ejb实现类的代码。
4.2 EJB实现类的代码
package org.vivianj.wsif;
import java.rmi.RemoteException;
public class HelloWorldBean implements javax.ejb.SessionBean {
private javax.ejb.SessionContext mySessionCtx;
public javax.ejb.SessionContext getSessionContext() { return mySessionCtx;}
public void setSessionContext(javax.ejb.SessionContext ctx) { mySessionCtx = ctx; }
public void ejbCreate() throws javax.ejb.CreateException {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void ejbRemove() {}
public String getHelloString(String param){
String result = "Say hello to : " + param;
return result;
};
}
|
4.3 如何编写WSDL文档来描述本地类
现在我们将根据WSIF中java绑定的WSDL扩展来使用WSDL文档描述这个java类,下面是作者根据WSIF中的localjava例子中的WSDL文档编写的一个WSDL文档(HelloWorld.wsdl),它描述了我们编写的HelloWorld.java类,供大家参考:
<?xml version="1.0" ?>
<definitions targetNamespace="http://wsifservice.helloworld/"
xmlns:tns="http://wsifservice.helloworld/"
xmlns:typens="http://wsiftypes.helloworld.ient.localjava/"
xmlns:xsd="http://www./1999/XMLSchema"
xmlns:soap="http://schemas./wsdl/soap/"
xmlns:format="http://schemas./wsdl/formatbinding/"
xmlns:java="http://schemas./wsdl/java/"
xmlns="http://schemas./wsdl/">
<!-下面这一段定义了服务调用中使用的消息-->
<message name="getHelloStringRequestMessage">
<part name="param" type="xsd:string"/>
</message>
<message name="getHelloStringResponseMessage">
<part name="resp" type="xsd:string"/>
</message>
<!-- port type declns -->
<portType name="HelloWorld">
<operation name="getHelloString">
<input name="getHelloStringRequest" message="tns:getHelloStringRequestMessage"/>
<output name="getHelloStringResponse" message="tns:getHelloStringResponseMessage"/>
</operation>
</portType>
<!-- binding declns -->
<binding name="JavaBinding" type="tns:HelloWorld">
<java:binding/>
<format:typeMapping encoding="Java" style="Java">
<format:typeMap typeName="xsd:string" formatType="java.lang.String" />
</format:typeMapping>
<operation name="getHelloString">
<java:operation
methodName="getHelloString"
parameterOrder="param"
methodType="instance" />
<input name="getHelloStringRequest"/>
<output name="getHelloStringResponse"/>
</operation>
</binding>
<!-- service decln -->
<service name="HelloWorldService">
<port name="JavaPort" binding="tns:JavaBinding">
<java:address className="service.HelloWorld"/>
</port>
</service>
</definitions>
|
其中的binding元素是我们重点关注的:
- 它的第一个子元素就是java:binding,表示这是一个java绑定类型
- format:typeMapping的元素和含义请参考上面关于java绑定的WSDL扩展的相关内容
- format:typeMap定义了java.lang.String和xsd:String之间的类型映射
- java:operation定义了WSDL中的抽象方法getHelloString和java方法中的getHelloString方法的映射。它的parameterorder属性规定了输入参数的顺序,methodtype属性表示这是一个实例方法调用。它的子元素input和output定了该方法调用用到的输入输出参数等
service元素的java:address子元素表示这是一个绑定到本地java类的port.
其他的元素都是WSDL规范中关于web服务的相关预定义元素,请大家参考WSDL规范中的相关定义。
4.4 如何编写WSDL文档来描述EJB
现在我们将根据WSIF中ejb绑定的WSDL扩展来使用WSDL文档描述这个EJB,下面是作者针对HelloWorld实例编写的一个WSDL文档(EJBWsdl.wsdl),它描述了我们编写的HelloWorld实例EJB,供大家参考:
<?xml version="1.0" ?>
<definitions targetNamespace="http://wsifservice.helloworld/"
xmlns:tns="http://wsifservice.helloworld/"
xmlns:typens="http://wsiftypes.rvice.ejb/"
xmlns:xsd="http://www./1999/XMLSchema"
xmlns:soap="http://schemas./wsdl/soap/"
xmlns:format="http://schemas./wsdl/formatbinding/"
xmlns:ejb="http://schemas./wsdl/ejb/"
xmlns="http://schemas./wsdl/">
<!-- message declns -->
<message name="GetHelloStringRequestMessage">
<part name="param" type="xsd:string"/>
</message>
<message name="GetHelloStringResponseMessage">
<part name="resp" type="xsd:string"/>
</message>
<!-- port type declns -->
<portType name="HelloWorld">
<operation name="getHelloString">
<input name="GetHelloStringRequest" message="tns:GetHelloStringRequestMessage"/>
<output name="GetHelloStringResponse" message="tns:GetHelloStringResponseMessage"/>
</operation>
</portType>
<!-- binding declns -->
<binding name="EJBBinding" type="tns:HelloWorld">
<ejb:binding/>
<format:typeMapping encoding="Java" style="Java">
<format:typeMap typeName="xsd:string" formatType="java.lang.String" />
</format:typeMapping>
<operation name="getHelloString">
<ejb:operation
methodName="getHelloString"
parameterOrder="param"
interface="remote"
returnPart="resp" />
<input name="GetHelloStringRequest"/>
<output name="GetHelloStringResponse"/>
</operation>
</binding>
<!-- service decln -->
<service name="HelloStringService">
<port name="EJBPort" binding="tns:EJBBinding">
<ejb:address className="org.vivianj.wsif.HelloWorldHome"
jndiName="ejb/HelloWorld"
initialContextFactory="weblogic.jndi.WLInitialContextFactory"
jndiProviderURL="t3://202.197.40.36:7001"
archive="e:/wsifTest/ejbbinding/wsifejb.jar;D:/bea/weblogic81/server/lib/weblogic.jar" />
</port>
</service>
</definitions>
|
其中的binding元素是我们重点关注的:
- 它的第一个子元素就是ejb:binding,表示这是一个ejb绑定类型
- format:typeMapping的元素和含义请参考上面关于ejb绑定的WSDL扩展的相关内容
- format:typeMap定义了java.lang.String和xsd:String之间的类型映射
- ejb:operation定义了WSDL中的抽象方法getHelloString和EJB中的getHelloString方法的映射。它的parameterorder属性规定了输入参数的顺序,interface属性表示这个方法由EJB的远程方法提供。它的子元素input和output定了该方法调用用到的输入输出参数等。
service元素的ejb:address子元素表示这是一个绑定到ejb的port. InitialContextFactory和jndiProviderURL定义了初始化工厂和提供者的URL。
4.5 使用WSIF提供的API调用
好了,现在我们已经将自己编写的本地类、EJB用WSDL描述出来了,接下来的工作将是演示如何使用WSIF提供的API通过使用我们编写的WSDL文档来调用这个服务。
下面这段代码是作者编写的调用这两个服务的Run.java, 供大家参考。它需要我们的WSDL文件名作为参数,处理结果是将服务调用的返回结果打印在控制台上。
//Run.java
package dynamic;
import javax.xml.namespace.QName;
import org.apache.wsif.WSIFMessage;
import org.apache.wsif.WSIFOperation;
import org.apache.wsif.WSIFPort;
import org.apache.wsif.WSIFService;
import org.apache.wsif.WSIFServiceFactory;
public class Run {
public static void main(String[] args) throws Exception {
// 第一个参数是 WSDL文件的位置和名字
// 创建服务工厂(service factory)
WSIFServiceFactory factory = WSIFServiceFactory.newInstance();
WSIFService service =
factory.getService(
args[0],
null,
null,
"http://wsifservice.helloworld/",
"HelloWorld");
// 参数http://wsifservice.helloworld/对应于我们WSDL文档中的targetNamespace
//参数HelloWorld对应于WSDL文档中的porttype
// 参数类型映射
service.mapType(
new QName("http://wsifservice.helloworld", "param"),
Class.forName(
"java.lang.String"));
// 获得port
WSIFPort port = service.getPort();
// 创建操作(operation)
WSIFOperation operation = port.createOperation("getHelloString");
// 创建和该操作调用相关的输入/输出/错误信息
WSIFMessage input = operation.createInputMessage();
WSIFMessage output = operation.createOutputMessage();
WSIFMessage fault = operation.createFaultMessage();
// 组装输入信息
input.setObjectPart("param", "xiaojing");
// 执行调用
if (operation.executeRequestResponseOperation(input, output, fault)) {
// 调用成功,从返回信息中获取我们需要的信息
// message
String zipInfo =
(String) output.getObjectPart("resp");
// resp就是我们的WSDL文档中的output对应的消息(message元素)中的参数(part元素)的名字
System.out.println(zipInfo);
} else {
System.out.println("Invocation failed");
// 调用失败,处理错误信息
}
}
}
|
[注] 调用这两个服务的代码实际上是一模一样的,除了输入的WSDL文件名不同。这也是WSIF的重要特性:
- 通过WSDL文件调用服务,不管他是如何实现和被访问的。
- 尽可能的将修改限制在WSDL文件中而不需要修改应用代码
4.6 编译运行
4.6.1 设置环境变量
由于编译和运行这个程序需要将大量的.jar文件设置到classpath中,所以请各位参考下载的WSIF包中的classpath.bat和lcp.bat编写自己的脚本来实现环境变量的设置。后面的下载的包里面有作者编写的环境变量设置脚本,供大家参考。
4.6.2 运行
设置好环境变量后,编译程序,然后就可以使用下面的命令来执行这个程序了:
java dynamic.Run HelloWorld.wsdl
|
它执行后的显示结果如下图:
4.7 可改进的地方
如果各位有心的话,应该可以发现,其实我们编写的Run.java中的有些参数仍然来自于我们的WSDL文档、和客户应用基本没有关联(比如:factory.getService方法调用的第四个参数和第五个参数,他们来源于WSDL中的一些元素,而且和客户应用基本没有关联),所以如果你可以做的话,你可以自己编写一个基于SAX的XML文档解析类,将这些和WSDL关联而和应用代码不很密切的内容从WSDL中直接取值,而不是依赖于编程者的输入。
5 复杂类型
如果被输入的参数或者返回的参数不是java的原始类型,那么我们的类型映射就没有这么简单了。大家可以参考WSIF中提供的localjava的例子,它的返回类型都用到了 一个address的类型,它是该实例中用到的一个自定义的类型,对应于一个java类,WSDL文档的编写也和一般的不同,请参考WSIF中提供的AddressBook.wsdl实例。不过一点可以给大家提示的是,编写好了wsdl文档后,其中复杂类型对应的java类不需要自己编写,可以使用Axis提供的wsdl2java功能来生成,命令的使用类似于:java org.apache.axis.wsdl.WSDL2Java (WSDL-file-URL),详细的帮助请大家参考Axis项目的用户指导(User Guide)
6 相关工具
研究到这一步的时候,作者想起来另外两个提供了类似功能的工具:AXIS和WSAD(Websphere studion application develop).
- Axis中的Java2wsdl功能可以为一个本地类生成将它发布为web服务的WSDL文件,只需要设置一些参数而已,这个作者经过研究发现原来WSIF中已经包含了Axis的jar文件,应该是WSIF直接继承了这项功能,所以应该是使用了同样的原理,只是使用范围不同而已。
- WSAD中提供的向导功能支持将java类、EJB等内容通过向导发布成web服务,相信也是采用了和这个相同的原理
7 总结
WSIF允许使用者用WSDL文件描述本地java类、EJB,也就是将本地java类、EJB虚拟成一个Web服务,然后使用WSIF提供的API调用这个服务,达到类似于通过服务调用本地类、EJB的效果。
本文中作者详细的介绍了WSIF中提供的对java、EJB绑定的WSDL的支持,如何编写WSDL文档来描述和映射一个本地的java类、EJB,给出了编写的WSDL文档的实例和简单的说明,同时给出了如何通过WSIF提供的统一的调用方式通过WSDL文档调用本地类、EJB的例子。通过实例大家可以看到我们实现了服务调用和服务实现的分离,任何服务实现和相关的映射基本上只会影响到服务的实现和WSDL的修改,不会影响到服务调用段代码的修改。
|