级别: 初级 Russell Butek (butek@us.ibm.com), 软件工程师, IBM Richard Scheuerle,Jr. (scheu@us.ibm.com), 软件工程师, IBM
2004 年 4 月 01 日 基于 XML 的远程过程调用的 Java API(Java APIs for XML-Based Remote Procedure Call,JAX-RPC)的 Java-to-WSDL/WSDL-to-Java 映射规则在往返过程中并不试图保留 Java 结构。很多结构被保留下来,但并不是全部。本技巧特别描述了为什么下面的 Java 编码惯例对于维持往返的能力非常重要。
这是三个关于往返和数据完整性的技巧中的第二个技巧。本技巧介绍了下面的 Java 编码惯例的重要性。想要获得往返背后的概念的介绍,请阅读
参考资料部分中列出的前一个技巧。
基于 XML 的远程过程调用的 Java API(Java APIs for XML-Based Remote Procedure Call,JAX-RPC)协议(请参阅
参考资料)是以 Java 为中心的(Java-centric),而不是以 WSDL 为中心的(WSDL-centric)。它包括一组相当详细的从 WSDL 名称到 Java 友好的(Java-friendly)名称(它们遵循 Java 编码的惯例)的映射规则。这是恰当的,因为 JAX-RPC 用户将是 Java 程序员。实际上,他们甚至可能都不关心您的 Web 服务
是一个 Web 服务。他们可能不知道或者不关心 WSDL 和 XML,以及所有他们摆到台面上的异常事件。他们可能关心的只是他们可以编写 Java 代码来调用您的服务,并且期望您的服务的 JAX-RPC API 看起来比较熟悉;换句话说,就是遵循 Java 编码的惯例。
然而,这种方法的缺点在于,您最初的 Java 服务必须在开始时就遵循 Java 编码惯例,以便避免往返问题。如果您开始的 Java 应用程序没有遵循 Java 编码的惯例,那么您的代码将不能往返,因为作为结果的代码
将遵循 Java 编码惯例。
详细的场景
让我们从一个简单的例子开始。这个产生往返问题的用况是发生在您将一个现有的应用程序作为一个 Web 服务公开的时候。假定现有的应用程序实现了在清单
1和
2中的接口和 Bean。您将注意到,这里有很多违反 Java 编码惯例的情况。(除了突出显示的 Java 编码惯例问题以外,本例中也显示了 java.util.Date,在第一个往返技巧中我们已经说明了它不能够往返。)
清单 1. 人口(Population)应用程序接口
package com.ibm.developerWorks.roundtrip;
public interface population {
public life GetALife();
}
|
清单 2. 生命(Life) bean
package com.ibm.developerWorks.roundtrip;
public class life {
public java.util.Date birthday;
public java.util.Date deathday;
}
|
为了将这个应用程序作为 Web 服务公开,您做的第一件事就是通过您喜欢的 JAX-RPC Java-to-WSDL 工具运行
population 接口。如果您指示那个工具生成 RPC/文字绑定,您应该以类似于
清单 3中的 WSDL 文件作为结束。
清单 3. 人口(Population)应用程序的 WSDL
<?xml version="1.0" encoding="UTF-8"?>
<definitions
targetNamespace="http://roundtrip.developerWorks.ibm.com"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:impl="http://roundtrip.developerWorks.ibm.com"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
<types>
<schema
targetNamespace="http://roundtrip.developerWorks.ibm.com"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<complexType name="life">
<sequence>
<element name="deathday" nillable="true" type="xsd:dateTime"/>
<element name="birthday" nillable="true" type="xsd:dateTime"/>
</sequence>
</complexType>
</schema>
</types>
<message name="GetALifeRequest"/>
<message name="GetALifeResponse">
<part name="GetALifeReturn" type="impl:life"/>
</message>
<portType name="population">
<operation name="GetALife">
<input message="impl:GetALifeRequest" name="GetALifeRequest"/>
<output message="impl:GetALifeResponse" name="GetALifeResponse"/>
</operation>
</portType>
<binding name="populationSoapBinding" type="impl:population">
...
</binding>
<service name="populationService">
...
</service>
</definitions>
|
一旦您有了 WSDL 文件,您将不得不生成构件。应用程序的 Web 服务 EAR 文件将需要这些构件。这些生成的构件是部署描述符和助手类。您使用自己喜欢的 WSDL-to-Java 工具来生成它们,实现从 Java 语言到 WSDL 然后再回到 Java 语言的往返。这些生成的构件将遵循 Java 编码惯例。
如果往返是完美的,您就能够简单地使用生成的构件来将您最初的应用程序代码打包成 EAR 文件,这样您就有了一个可爱的小 Web 服务。但是往返并不是完美的。生成的构件假定应用程序代码遵循 Java 编码惯例。换句话说,它们假定应用程序由
Population 和
Life 类组成,但是您最初的类是
population 和
life 。从清单
4和
5中您可以看到,由
清单 3中的 WSDL 生成的构件假定
Population 和
Life 类看起来相似。请注意下面突出显示的最初的 Java 代码和期望的 Java 代码之间的不一致。
清单 4. 由 WSDL 生成的 Population 服务端点接口
package com.ibm.developerWorks.roundtrip;
public interface
Population extends java.rmi.Remote {
public
Life getALife() throws java.rmi.RemoteException;
}
|
清单 5. 由 WSDL 生成的 Life Bean
package com.ibm.developerWorks.roundtrip;
public class
Life implements java.io.Serializable {
----
public java.util.Calendar getBirthday() {...}
----
public void setBirthday(java.util.Calendar birthday) {...}
----
public java.util.Calendar getDeathday() {...}
----
public void setDeathday(java.util.Calendar deathday) {...}
public boolean equals(java.lang.Object obj) {...}
public int hashCode() {...}
}
|
 |
其他的不一致
请注意,在清单
4和
5中有很多的差别我们没有强调:
Population 现在扩展成
Remote ;
getALife 现在抛出
RemoteException ;
Life 现在扩展成
可序列化(Serializeable) ;
Life 现在具有显式的等于(equal)和
hashCode 方法。虽然这些差别可能会引起问题,但它们极少能阻止您将现有的应用程序转变成 Web 服务。
|
|
尝试使用您最初的代码和生成的构件来构建 EAR 文件。在最好的情况下,您将碰到构建时(build-time)或者安装时(install-time)错误。在最坏的情况下,将成功地构建和安装 EAR 文件,但是您将碰到某个模糊的运行时(runtime)错误,这将使您花费数天的时间来调试。所以,从中您可以看出,为什么理解您最初的代码与生成的构件对您的代码的假定之间不一致是很重要的。
表 1总结了所有由于最初的 Java 代码没有遵循 Java 编码惯例而引起的不一致。
表 1 - 不能往返的 Java 结构
|
问题
|
Java-to-WSDL-to-Java
|
来自示例
|
Date 变成
Calendar
|
java.util.Date
xsd:datetime
java.util.Calendar
|
Life 的“birthday”和“deathday”特性
| | bean 名称改变的情况 |
class bean {
}
<complexType name="bean">
</complexType>
class Bean {
}
| 类名称
life 与
Life
| | 接口名称和方法名称改变的情况 |
interface y {
--void A();
}
<portType name="y">
--<operation name="A">
----...
--</operation>
</portType>
interface Y {
--void a();
}
| 接口名称
population 与
Population
以及方法名称
GetALife 与
getALife
| | 公共实例变量(Public instance variable)与访问器(accessor) |
class Bean {
--public int a;
}
<complexType name="Bean">
--<sequence>
----<element name="a" type="int"/>
--</sequence>
</complexType>
class Bean {
--public void setA(int a);
--public int getA();
}
|
Life 的“birthday”和“deathday”特性
|
如何避免编码惯例中的往返问题
由于往返并不完美,所以除了在您的 Web 服务 EAR 文件中使用最初的代码以外,还需要做一些额外的工作。
仅仅说“不”
克服这些问题最容易的方法就是简单地避开它们。始终遵循 Java 编码惯例。
编写包装器(wrapper)
或许您不能够改变最初的代码。另外一个可供选择的方法是编写最初的应用程序的包装器(wrapper)。包装器(wrapper)代码将是 Web 服务,并且它将所有的调用委派给真正的实现。
清单 6给出了本例的一个包装器(wrapper)。使用本技巧打包的 EAR 文件实现了这个包装器(wrapper)。(请注意,对于这个包装器(wrapper),我们将
Life 和
Population 生成到不同的包中,而不是最初的
life 和
population 类。我们在 Windows 上开发的这个例子,Windows 对于仅仅大小写有区别的文件名不是特别的友好。)
清单 6. 最初的 population 应用程序的 Web 服务包装器(wrapper)
package com.ibm.developerWorks.roundtrip;
import java.rmi.RemoteException;
import java.util.Calendar;
import com.ibm.developerWorks.roundtrip.generated.Life;
import com.ibm.developerWorks.roundtrip.generated.Population;
public class WrapperImpl implements Population {
private population p = new populationImpl();
public Life getALife() throws RemoteException {
life l = p.GetALife();
Life L = new Life();
Calendar birthday = Calendar.getInstance();
birthday.setTime(l.birthday);
Calendar deathday = Calendar.getInstance();
deathday.setTime(l.deathday);
L.setBirthday(birthday);
L.setDeathday(deathday);
return L;
}
}
|
修改映射元数据文件
针对 J2EE 的 Web 服务(一般指 JSR-109--参阅
参考资料)定义了映射元数据文件,使您可以回避某些 JAX-RPC 映射规则。随着将来的技巧进行调整,在这些技巧中将给出一些关于修改这个映射元数据文件以便克服往返问题的线索。
总结
当往返 Web 服务时,遵循 Java 编码惯例是很重要的。如果不这样做,就将危害到往返。
有许多克服这些往返问题的方法:
- 您可以通过简单地遵循惯例来完全地避免它们
- 您可以编写最初实现的 Web 服务包装器(wrapper)
- 您可以修改映射元数据文件(本技巧没有详述映射元数据文件的细节)。
参考资料
作者简介  | |  | Russell Butek IBM WebSphere Web 服务引擎的开发人员之一。他也是 JAX-RPC Java Specification Request (JSR) 专家组的 IBM 代表。他从事 Apache 的 AXIS SOAP 引擎的实现方面的研究,推动了 AXIS 1.0 遵循 JAX-RPC 1.0。以前,他是 IBM CORBA ORB 的开发人员和许多 OMG 特别工作组的 IBM 代表:包括可移植拦截器特别工作组(他是这个特别工作组的主席)、核心特别工作组以及互操作性特别工作组。 |
 | |  | Richard Scheuerle 是 IBM WebSphere Web 服务引擎的开发人员之一。他从事 Apache 的 AXIS SOAP 引擎的实现方面的研究。Richard 有 10 年的编译器/语言设计的开发经验。现在他侧重于 Web 服务引擎性能和 API 设计。Richard 也从事 CORBA 工具和芯片确认软件的开发。 |
对本文的评价
|