Apache Axis2 支持各种数据绑定技术,包括官方 Java™ 标准 JAXB 2.x。Axis2 还支持针对 Web 服务配置的 Java 标准 JAX-WS 2.x,作为其自有的配置技术的替代选择。Dennis Sosnoski 将继续他的 Java Web 服务 系列,向您演示如何将这两种 Java 标准用于 Axis2,并讨论 Axis2 对这些标准的当前支持存在哪些限制。
早期的 Apache Axis 建立在第一个面向 Web 服务的 Java 标准 JAX-RPC 的基础之上。事实证明,这并不是一个很好的方法,因为 JAX-RPC 限制了 Axis 代码的内部设计,而且造成了性能问题并缺乏灵活性。JAX-RPC 还对 Web 服务开发的方向作出了假设,而这在后来被证明是错误的。
在开始 Axis2 开发工作时,已经着手研究 JAX-RPC 的替代选择,因此,Axis2 在设计时已经考虑到了足够的灵活性,使其能够在基础框架之上实现对替代 Web 服务标准的支持。最新的 Axis2 版本同时实现了对 JAXB 2.x Java XML 数据绑定标准和替代了 JAX-RP 的 JAX-WS 2.x Java Web 服务标准的支持。本文将展示如何将 JAXB 和 JAX-WS 用于 Axis2 并找出 Axis2 对这些标准的当前支持中存在的一些限制。
Axis2 中的 JAXB
Axis2 实现了对 JAXB 2.x 的支持,将它作为数据绑定替代选择的其中之一,您可以在使用 WSDL2Java 从 Web Services Description Language (WSDL) 服务定义中生成代码时进行选择。(参见 “Java Web Services: Axis2 Data Binding” 获得有关其他主要替代选择的讨论)。和大多数其他替代选择一样,使用 JAXB 2.x 从 WSDL 中生成的代码创建了一组链接(linkage)类和一组数据模型类。这些链接类,包括一个客户端 stub 和一个服务器端消息接收器,充当应用程序代码和 Axis2 之间的接口。数据模型类表示实际的消息数据。
JAXB 2.x 使用数据模型类中的注释来控制数据与 XML 之间的转换方式。注释方法允许您在无需修改源代码或重新编译类的情况下在运行时使用不同的 JAXB 实现。由 JAXB 实现负责从数据模型类访问注释信息并在执行 XML 转换时应用这些注释。
代码下载(参见 下载)提供了一个演示在 Axis2 中使用 JAXB 的示例应用程序,位于 jaxb 目录中。这个应用程序是本系列前一篇文章中的简单的库管理服务的另一个版本(包括 “Axis2 Data Binding” 中的数据绑定比较)。WSDL 服务定义定义了四个操作:
getBook 用来检索由 International Standard Book Number (ISBN) 标识的某本特定图书的细节信息
getBooksByType 用来检索某一特定类型的所有图书的细节信息
getTypes 用于查找可用的图书类型
addBook 用于将新书添加到库中
清单 1 显示的是经过大量编辑的 WSDL,只包含了与 getBook 操作有关的部分:
清单 1. 库服务 WSDL
Axis2 的 JAXB 支持应该进行扩展,以生成未封装的操作方法(为了方便编程,封装到消息中的值被转换为方法参数 — 再一次建议您参考 “Java Web 服务:Axis2 Data Binding”,获得有关封装接口和未封装接口的讨论。但是使用 WSDL2Java 工具的未封装支持,不管在当前的 Axis2 代码中,还是最近几个发行版中,都不适合这个例子。至少在目前,封装操作方法是惟一可以将 JAXB 用于 Axis2 代码生成的方法(但请立刻参考一下 JAX-WS 讨论 了解另一种替代方法)。对于封装操作接口,每个服务方法都对操作使用一个与输入消息匹配的单一对象参数,并为操作返回一个与输出消息匹配的对象。
附带的代码提供了服务和测试客户机的实际实现,开始处理由运行的 WSDL2Java 生成的类。与本系列早期文章的样例代码相同,下载部分包括用于通过 Apache Ant 构建样例的 build.properties 和 build.xml 文件(位于 jaxb 目录)。您首先需要编辑 build.properties 文件,以将路径设置为您的 Axis2 安装(并修改其他设置,如果系统需要的话)。随后可以在一个打开到 jaxb 目录的控制台中输入 ant 以运行 WSDL2Java,编译附带的和生成的代码,并为服务器部署构建 AAR 文件。要进行尝试,首先将生成的 AAR 文件部署到您的 Axis2 服务器安装并在控制台中输入 ant run。
客户端 JAXB 使用
测试客户机使用作为命令行参数传递进来的服务端点参数创建了一个服务 stub 实例,然后依次执行 5 个服务调用:
获得有关某本书的细节。
获得库中的图书类型。
向库添加一本新书(如果该书已经存在的话,此操作将失败,当客户机在未重启服务器的情况下运行超过一次时,也会出现同样的错误)。
如果上一步骤成功,那么尝试使用相同 ISBN 添加另一本书(这个操作应当永远都是失败的)。
获得有关某一特定类型的所有图书的信息。
清单 2 展示了完整的测试客户机代码。可以看到用于每个操作的封装器对象中的服务接口的封装特性,比如调用 getTypes 操作所需的 GetTypes 对象(即使没有为该操作提供输入数据)和由调用返回的 GetTypesResponse 对象。
清单 2. JAXB 测试客户机代码
如果将 清单 2 与 “Java Web Services: Axis2 Data Binding” 中的客户机代码示例加以比较,会发现它非常类似于 JiBX 和 Axis Data Binding (ADB) 封装例子,主要区别在于 JAXB 封装器类使用 Java 5 类型列表(typed lists)而不是数组(JiBX 数据绑定支持的另一个替换选择,但是不受 ADB 支持)。
服务器端使用
库服务的服务器端代码包含两个类,其中一个实际实现库处理,另一个可适应 Axis2 所期望的服务接口。实际的实现代码对于不同的数据绑定几乎都是相同的,只需要根据生成的数据模型表示做一些微小的修改。清单 3 展示了更加有趣的服务接口类。和在客户端一样,封装的接口要求应用程序代码从收到的封装器对象中提取数据,并构造将要发送的封装器对象。
清单 3. JAXB 服务器代码
“Java Web Services: Axis2 Data Binding” 并没有展示针对不同数据绑定的服务器接口代码,但是如果您比较 清单 3 和从上文下载的代码,就会发现清单 3 非常接近于 ADB 和 JiBX 封装器例子,同样,惟一的区别在于使用了 Java 5 的类型类别而没有使用数组。
JAXB 数据模型类
清单 4 展示了通过运行 WSDL2Java 生成的 JAXB 数据模型类(生成的大多数注释已被删除,只留下少量注释作为例子)。生成的数据模型类对于客户机和服务器都是相同的,即使是由项目构建单独创建的。显示的类用于 清单 2 的客户机代码和 清单 3 的服务器代码中的 getBook 调用。每个类定义上的注释(用粗体显示)和大部分字段定义提供了配置信息,供 JAXB 用于控制对象与 XML 的转换。
清单 4. JAXB 数据模型类
@XmlAccessorType 注释在包或类级别使用,用于控制从类中访问值的方式 — 包括访问所有字段、访问所有具有 get/set 访问方法的属性、只访问公开字段和属性,或仅根据某个注释的指定进行访问。@XmlType 注释在匹配某个模式类型的类或 enum 定义中使用,以向 JAXB 提供模式类型的名称和名称空间(如果有的话)、类型表示中的值的顺序,以及如何使用一个工厂方法构建类的实例(可选)。@XmlRootElement 注释在匹配全局元素定义的类或 enum 定义中使用,用于提供全局元素的名称和名称空间。@XmlElement 和 @XmlAttribute 注释在值中使用(以字段或 JavaBean 属性方法的形式),提供元素或属性名和其他特征)。
JAXB 使用的所有注释都位于 javax.xml.bind.annotation 包中,除了这个简单示例生成的代码中使用的注释外,还包括许多其他注释。JAXB 支持从模式中生成代码(本例就属于此例),也支持从代码中生成。某些注释和选项(比如那些处理对象工厂和串行器/并行器方法的注释和选项)只能用于从代码中生成的情况。
Axis2 中的 JAXB 问题
WSDL2Java 调用 JAXB 参考实现中附带的 XJC 绑定编译器来生成数据模型代码,因此在大多数方面数据模型代码的生成是独立于 Axis2 的。如果直接在 Web 服务使用的模式上面运行 JAXB XJC 绑定编译器,您将生成相同的数据模型。不幸的是,WSDL2Java 和 XJC 之间的阻抗匹配并不总是正确的,这就会导致一些问题。
其中一个问题与在 WSDL 文档中构建模式的方式有关。库服务的初始 WSDL 使用单个文档,该文档合并了两个独立的模式,一个用于 WSDL 消息元素,另一个用于应用程序数据(图书和类型信息)。如 WSDL 允许的那样,消息元素模式通过名称空间引用导入了应用程序数据模式。这个包含嵌入模式的 WSDL 可以很好地与使用 ADB 或 JiBX 数据绑定的 WSDL2Java 工作,但是对于 JAXB,它将在模式处理期间引发一个异常抛出。将应用程序数据模式分离到一个单独的文件并在模式导入时指定文件名称,这将允许 WSDL2Java 使用 JAXB 绑定正确地处理模式。
另一个问题是,XJC 提供了大量代码生成选项以及许多定制来控制针对某一特定模式组件的代码生成细节 — 但是 WSDL2Java 没有提供任何方法来将这些选项或定制传递给 XJC,因此代码生成将始终按照默认设置运行。如果需要使用任何代码生成选项或定制,可能需要单独运行 XJC 和 WSDL2Java。不幸的是,无法在 WSDL2Java 代码生成中使用单独生成的 JAXB 数据模型。如果需要使用定制的 JAXB 数据模型,最佳办法可能就是运行 WSDL2Java 以生成其自己的 JAXB 代码模型,然后再换入您单独生成的数据模型类,并根据需要手动修改代码来将所有内容联合到一起。或者,可以像下一小节介绍的那样使用 JAX-WS,这将使您完全跳过 WSDL2Java,但是存在一些明显的限制。
在 Axis2 中使用 JAX-WS
虽然可以将 JAXB 作为 Axis2 中的另一种数据绑定替代选择,但是它与 JAX-WS 之间的区别更加显著。JAX-WS 是一种截然不同的 Web 服务定义方法,它全面取代了标准的 Axis2 服务器端和客户端配置。您使用 JAX-WS 参考实现(必须从 Axis2 中单独下载;参见 参考资料)中附带的 WsImport 工具从 WSDL 中生成 JAX-WS 代码,而不是使用 WSDL2Java。甚至部署机制也不同于 Axis2 中通常使用的 AAR 文件方法。
代码 下载 部分只提供了早先使用的同一样例应用程序的不同版本,这个版本进行了修改,用于演示 JAX-WS 在 Axis2 中的使用。代码位于下载中的 jaxws 目录中,并且它带有自己的 WSDL、build.properties 和 build.xml。这个 JAX-WS 版本的 WSDL 基本上与用于 JAXB 的 WSDL 相同,如 清单 1 所示。该 WSDL 的主要区别在于它对应用程序代码使用了内嵌的模式,而这对于使用 JAXB 数据绑定的 WSDL2Java 来说是不可行的。
当使用 JAX-WS 的 WsImport 工具从 WSDL 创建代码时,将获得与使用 WSDL2Java 实现 JAXB 代码生成时相同的 JAXB 数据模型和封装器类。不同之处在于链接(linkage)代码,对于 JAX-WS,链接代码包含一个生成的服务接口和一个客户端服务构建器类。接口类,如 清单 5(稍微进行了重新格式化,并且只保留了一个方法注释)所示,定义了与 WSDL 中的操作匹配的方法。客户机代码和服务器代码都使用这个接口。接口中的大量注释提供了所有必需配置信息,帮助 JAX-WS 将服务接口与该服务的操作的接口方法关联起来。
清单 5. JAX-WS 生成的服务接口
WsImport 工具将附带的 WSDL 识别为匹配 “封装的” 约定,并自动生成一个未封装的服务接口。您可以从 清单 5 中看到效果,在此过程中,方法将单独的值作为输入参数,并直接返回任何合适的类型,而不是使用一个封装器对象层(但是仍然将生成封装器对象,然后供 JAX-WS 运行时在幕后使用)。
附带的代码再一次给出了服务和测试客户机的实际实现。要亲自尝试,您需要对附带的 build.properties 文件进行编辑,以设置到 Axis2 安装和 JAX-WS 参考实现安装的路径(参见 参考资料)。完成编辑后,在打开到 jaxws 目录的控制台中输入 ant 以从 WSDL 运行 JAX-WS 代码生成,编译附带的代码,并为服务器部署构建一个 JAR 文件。要运行测试客户机,将生成的 JAR 文件复制到 Axis2 服务器安装的 WEB-INF/servicejars 目录中,然后在控制台中输入 ant run。
客户端 JAX-WS 使用
清单 6 展示了完整的测试客户机代码。如果将其与 清单 2 比较,您将观察到未封装接口和已封装接口之间的不同,其中未封装接口具有更好的编程友好性。
清单 6. JAX-WS 测试客户机代码
JAX-WS 客户端处理通常要求在运行时访问服务 WSDL,并且使用 WSDL 来初始化服务器链接。如果您已经获知目标服务的 WSDL 始终可以在运行时直接从服务器获得,并且该服务器将始终位于同一地址,那么您可以将 WSDL URL 提供给 WsImport,并让它将 URL 硬编码到生成的代码中。对于大多数严肃的工作,最好使用 WSDL 的本地副本,然后,如果目标服务地址不同于 WSDL 中的地址,那么在运行时重写该地址。附带的构建文件采用了这种方法,而 清单 6 中粗体所示的代码部分演示了如何在不修改 WSDL 的情况下在运行时修改服务地址。
服务器端 JAX-WS 使用
服务器端代码的 JAX-WS 版本如 清单 7 所示。实现类的 @WebService 注释(粗体显示)将实现代码与某个特定 Web 服务接口关联起来。实现类的这个注释允许您从生成的服务接口(清单 5)的相应注释中重写设置。在本例中,注释将设置服务和端口名,并给出 WSDL 服务定义的位置(显然 Axis2 希望该位置与类路径的根相关,或是一个绝对 URL)。
清单 7. JAX-WS 服务器代码
清单 7 中的其余代码仅仅是 清单 3 所示的(封装)JAXB 示例的未封装版本。
Axis2 中的 JAX-WS 问题
Axis2 在基础 JAX-WS 处理方面表现得非常好,但它的确也存在一些限制。最严重的一点就是当您在 Axis2 中使用 JAX-WS 时,缺乏对 WS-Security 或其他 Web 服务扩展技术的支持(但是围绕 Axis2 构建的应用服务器可能使用自己的方式实现配置和使用 WS-Security)。这对于企业应用程序来说是一个服务器限制,并且,除了一个稍微简单些的配置方法外,不存在任何起到补偿作用的优势,似乎没有足够的理由说服人们马上在 Axis2 中使用 JAX-WS。
结束语
在本文中,您了解了将 JAXB 2.x 和 JAX-WS 2.x Java 标准用于 Axis2 的基础知识。Axis2 中的 JAXB 支持存在某些限制,但是,至少对那些不需要定制的简单模式来说,它为 Axis2 中支持的其他数据绑定方法提供了一种有用的替代选择。JAX-WS 支持的限制更多,目前只对那些不需要 WS-Security 或其他任何增值功能的简单服务有用。
本系列目前为止一直关注 Apache Axis2 框架。有关 JAXB 和 JAX-WS 的介绍为研究其他一些同样支持这些标准的开源 Java Web 服务框架提供了一个良好的起点。在下个月中,本专栏将研究由 Sun 开发的 Metro Web Services 框架以及 JAXB 和 JAX-WS 参考实现。我们将更深入地研究 JAX-WS 的使用和特性。
早期的 Apache Axis 建立在第一个面向 Web 服务的 Java 标准 JAX-RPC 的基础之上。事实证明,这并不是一个很好的方法,因为 JAX-RPC 限制了 Axis 代码的内部设计,而且造成了性能问题并缺乏灵活性。JAX-RPC 还对 Web 服务开发的方向作出了假设,而这在后来被证明是错误的。
在开始 Axis2 开发工作时,已经着手研究 JAX-RPC 的替代选择,因此,Axis2 在设计时已经考虑到了足够的灵活性,使其能够在基础框架之上实现对替代 Web 服务标准的支持。最新的 Axis2 版本同时实现了对 JAXB 2.x Java XML 数据绑定标准和替代了 JAX-RP 的 JAX-WS 2.x Java Web 服务标准的支持。本文将展示如何将 JAXB 和 JAX-WS 用于 Axis2 并找出 Axis2 对这些标准的当前支持中存在的一些限制。
Axis2 中的 JAXB
Axis2 实现了对 JAXB 2.x 的支持,将它作为数据绑定替代选择的其中之一,您可以在使用 WSDL2Java 从 Web Services Description Language (WSDL) 服务定义中生成代码时进行选择。(参见 “Java Web Services: Axis2 Data Binding” 获得有关其他主要替代选择的讨论)。和大多数其他替代选择一样,使用 JAXB 2.x 从 WSDL 中生成的代码创建了一组链接(linkage)类和一组数据模型类。这些链接类,包括一个客户端 stub 和一个服务器端消息接收器,充当应用程序代码和 Axis2 之间的接口。数据模型类表示实际的消息数据。
JAXB 2.x 使用数据模型类中的注释来控制数据与 XML 之间的转换方式。注释方法允许您在无需修改源代码或重新编译类的情况下在运行时使用不同的 JAXB 实现。由 JAXB 实现负责从数据模型类访问注释信息并在执行 XML 转换时应用这些注释。
代码下载(参见 下载)提供了一个演示在 Axis2 中使用 JAXB 的示例应用程序,位于 jaxb 目录中。这个应用程序是本系列前一篇文章中的简单的库管理服务的另一个版本(包括 “Axis2 Data Binding” 中的数据绑定比较)。WSDL 服务定义定义了四个操作:
getBook 用来检索由 International Standard Book Number (ISBN) 标识的某本特定图书的细节信息
getBooksByType 用来检索某一特定类型的所有图书的细节信息
getTypes 用于查找可用的图书类型
addBook 用于将新书添加到库中
清单 1 显示的是经过大量编辑的 WSDL,只包含了与 getBook 操作有关的部分:
清单 1. 库服务 WSDL
<wsdl:definitions targetNamespace="http://ws.sosnoski.com/library/wsdl"
xmlns:wns="http://ws.sosnoski.com/library/wsdl"
xmlns:tns="http://ws.sosnoski.com/library/types"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
<wsdl:types>
<schema elementFormDefault="qualified"
targetNamespace="http://ws.sosnoski.com/library/wsdl"
xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://ws.sosnoski.com/library/types"
schemaLocation="types.xsd"/>
<element name="getBook">
<complexType>
<sequence>
<element name="isbn" type="string"/>
</sequence>
</complexType>
</element>
<element name="getBookResponse">
<complexType>
<sequence>
<element name="getBookReturn" minOccurs="0" type="tns:BookInformation"/>
</sequence>
</complexType>
</element>
...
</schema>
</wsdl:types>
<wsdl:message name="getBookRequest">
<wsdl:part element="wns:getBook" name="parameters"/>
</wsdl:message>
<wsdl:message name="getBookResponse">
<wsdl:part element="wns:getBookResponse" name="parameters"/>
</wsdl:message>
...
<wsdl:portType name="Library">
<wsdl:operation name="getBook">
<wsdl:input message="wns:getBookRequest" name="getBookRequest"/>
<wsdl:output message="wns:getBookResponse" name="getBookResponse"/>
</wsdl:operation>
...
</wsdl:portType>
<wsdl:binding name="LibrarySoapBinding" type="wns:Library">
<wsdlsoap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getBook">
<wsdlsoap:operation soapAction="urn:getBook"/>
<wsdl:input name="getBookRequest">
<wsdlsoap:body use="literal"/>
</wsdl:input>
<wsdl:output name="getBookResponse">
<wsdlsoap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
...
</wsdl:binding>
<wsdl:service name="jaxb-library">
<wsdl:port binding="wns:LibrarySoapBinding" name="library">
<wsdlsoap:address location="http://localhost:8080/axis2/services/jaxb-library"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Axis2 的 JAXB 支持应该进行扩展,以生成未封装的操作方法(为了方便编程,封装到消息中的值被转换为方法参数 — 再一次建议您参考 “Java Web 服务:Axis2 Data Binding”,获得有关封装接口和未封装接口的讨论。但是使用 WSDL2Java 工具的未封装支持,不管在当前的 Axis2 代码中,还是最近几个发行版中,都不适合这个例子。至少在目前,封装操作方法是惟一可以将 JAXB 用于 Axis2 代码生成的方法(但请立刻参考一下 JAX-WS 讨论 了解另一种替代方法)。对于封装操作接口,每个服务方法都对操作使用一个与输入消息匹配的单一对象参数,并为操作返回一个与输出消息匹配的对象。
附带的代码提供了服务和测试客户机的实际实现,开始处理由运行的 WSDL2Java 生成的类。与本系列早期文章的样例代码相同,下载部分包括用于通过 Apache Ant 构建样例的 build.properties 和 build.xml 文件(位于 jaxb 目录)。您首先需要编辑 build.properties 文件,以将路径设置为您的 Axis2 安装(并修改其他设置,如果系统需要的话)。随后可以在一个打开到 jaxb 目录的控制台中输入 ant 以运行 WSDL2Java,编译附带的和生成的代码,并为服务器部署构建 AAR 文件。要进行尝试,首先将生成的 AAR 文件部署到您的 Axis2 服务器安装并在控制台中输入 ant run。
客户端 JAXB 使用
测试客户机使用作为命令行参数传递进来的服务端点参数创建了一个服务 stub 实例,然后依次执行 5 个服务调用:
获得有关某本书的细节。
获得库中的图书类型。
向库添加一本新书(如果该书已经存在的话,此操作将失败,当客户机在未重启服务器的情况下运行超过一次时,也会出现同样的错误)。
如果上一步骤成功,那么尝试使用相同 ISBN 添加另一本书(这个操作应当永远都是失败的)。
获得有关某一特定类型的所有图书的信息。
清单 2 展示了完整的测试客户机代码。可以看到用于每个操作的封装器对象中的服务接口的封装特性,比如调用 getTypes 操作所需的 GetTypes 对象(即使没有为该操作提供输入数据)和由调用返回的 GetTypesResponse 对象。
清单 2. JAXB 测试客户机代码
public class WebServiceClient
{
public static void main(String[] args) throws Exception {
// check for required command line parameters
if (args.length < 3) {
System.out.println("Usage:\n java " +
"com.sosnoski.ws.library.jaxb.WebServiceClient host port path");
System.exit(1);
}
// create the client stub
String target = "http://" + args[0] + ":" + args[1] + args[2];
System.out.println("Connecting to " + target);
JaxbLibraryStub stub = new JaxbLibraryStub(target);
// retrieve a book directly
String isbn = "0061020052";
GetBook gb = new GetBook();
gb.setIsbn(isbn);
GetBookResponse gbr = stub.getBook(gb);
BookInformation book = gbr.getGetBookReturn();
if (book == null) {
System.out.println("No book found with ISBN '" + isbn + '\'');
} else {
System.out.println("Retrieved '" + book.getTitle() + '\'');
}
// retrieve the list of types defined
GetTypesResponse gtr = stub.getTypes(new GetTypes());
List<TypeInformation> types = gtr.getGetTypesReturn();
System.out.println("Retrieved " + types.size() + " types:");
for (int i = 0; i < types.size(); i++) {
TypeInformation type = types.get(i);
System.out.println(" '" + type.getName() + "' with " +
type.getCount() + " books");
}
// add a new book
String title = "The Dragon Never Sleeps";
isbn = "0445203498";
try {
AddBook ab = new AddBook();
ab.setType("scifi");
ab.setIsbn(isbn);
ab.getAuthor().add("Cook, Glen");
ab.setTitle(title);
stub.addBook(ab);
System.out.println("Added '" + title + '\'');
title = "This Should Not Work";
ab.setTitle(title);
stub.addBook(ab);
System.out.println("Added duplicate book - should not happen!");
} catch (AddDuplicateFault e) {
System.out.println("Failed adding '" + title +
"' with ISBN '" + isbn + "' - matches existing title '" +
e.getFaultMessage().getBook().getTitle() + '\'');
}
// get all books of a type
GetBooksByType gbbt = new GetBooksByType();
gbbt.setType("scifi");
GetBooksByTypeResponse gbbtr = stub.getBooksByType(gbbt);
List<BookInformation> books = gbbtr.getGetBooksByTypeReturn();
System.out.println("Retrieved " + books.size() + " books of type 'scifi':");
for (int i = 0; i < books.size(); i++) {
System.out.println(" '" + books.get(i).getTitle() + '\'');
}
}
}
如果将 清单 2 与 “Java Web Services: Axis2 Data Binding” 中的客户机代码示例加以比较,会发现它非常类似于 JiBX 和 Axis Data Binding (ADB) 封装例子,主要区别在于 JAXB 封装器类使用 Java 5 类型列表(typed lists)而不是数组(JiBX 数据绑定支持的另一个替换选择,但是不受 ADB 支持)。
服务器端使用
库服务的服务器端代码包含两个类,其中一个实际实现库处理,另一个可适应 Axis2 所期望的服务接口。实际的实现代码对于不同的数据绑定几乎都是相同的,只需要根据生成的数据模型表示做一些微小的修改。清单 3 展示了更加有趣的服务接口类。和在客户端一样,封装的接口要求应用程序代码从收到的封装器对象中提取数据,并构造将要发送的封装器对象。
清单 3. JAXB 服务器代码
public class JaxbLibraryImpl extends JaxbLibrarySkeleton
{
private final BookServer m_server;
public JaxbLibraryImpl() {
m_server = new BookServer();
}
public AddBookResponse addBook(AddBook req) throws AddDuplicateFault {
BookInformation prior = m_server.getBook(req.getIsbn());
if (prior == null) {
BookInformation book = new BookInformation();
book.getAuthor().addAll(req.getAuthor());
book.setIsbn(req.getIsbn());
book.setTitle(req.getTitle());
book.setType(req.getType());
AddBookResponse rsp = new AddBookResponse();
rsp.setAddBookReturn(m_server.addBook(book));
return rsp;
} else {
AddDuplicateFault e =
new AddDuplicateFault("Book already present with matching ISBN");
AddDuplicate ad = new AddDuplicate();
ad.setBook(prior);
e.setFaultMessage(ad);
throw e;
}
}
public GetBookResponse getBook(GetBook req) {
BookInformation book = m_server.getBook(req.getIsbn());
GetBookResponse rsp = new GetBookResponse();
rsp.setGetBookReturn(book);
return rsp;
}
public GetBooksByTypeResponse getBooksByType(GetBooksByType req) {
GetBooksByTypeResponse rsp = new GetBooksByTypeResponse();
rsp.getGetBooksByTypeReturn().addAll(m_server.getBooksByType(req.getType()));
return rsp;
}
public GetTypesResponse getTypes(GetTypes req) {
GetTypesResponse rsp = new GetTypesResponse();
rsp.getGetTypesReturn().addAll(m_server.getTypes());
return rsp;
}
}
“Java Web Services: Axis2 Data Binding” 并没有展示针对不同数据绑定的服务器接口代码,但是如果您比较 清单 3 和从上文下载的代码,就会发现清单 3 非常接近于 ADB 和 JiBX 封装器例子,同样,惟一的区别在于使用了 Java 5 的类型类别而没有使用数组。
JAXB 数据模型类
清单 4 展示了通过运行 WSDL2Java 生成的 JAXB 数据模型类(生成的大多数注释已被删除,只留下少量注释作为例子)。生成的数据模型类对于客户机和服务器都是相同的,即使是由项目构建单独创建的。显示的类用于 清单 2 的客户机代码和 清单 3 的服务器代码中的 getBook 调用。每个类定义上的注释(用粗体显示)和大部分字段定义提供了配置信息,供 JAXB 用于控制对象与 XML 的转换。
清单 4. JAXB 数据模型类
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"isbn"
})
@XmlRootElement(name = "getBook")
public class GetBook {
@XmlElement(required = true)
protected String isbn;
/**
* Gets the value of the isbn property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getIsbn() {
return isbn;
}
/**
* Sets the value of the isbn property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setIsbn(String value) {
this.isbn = value;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "BookInformation", propOrder = {
"author",
"title"
})
public class BookInformation {
protected List<String> author;
@XmlElement(required = true)
protected String title;
@XmlAttribute(required = true)
protected String type;
@XmlAttribute(required = true)
protected String isbn;
public List<String> getAuthor() {
if (author == null) {
author = new ArrayList<String>();
}
return this.author;
}
public String getTitle() {
return title;
}
public void setTitle(String value) {
this.title = value;
}
public String getType() {
return type;
}
public void setType(String value) {
this.type = value;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String value) {
this.isbn = value;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"getBookReturn"
})
@XmlRootElement(name = "getBookResponse")
public class GetBookResponse {
protected BookInformation getBookReturn;
public BookInformation getGetBookReturn() {
return getBookReturn;
}
public void setGetBookReturn(BookInformation value) {
this.getBookReturn = value;
}
}
@XmlAccessorType 注释在包或类级别使用,用于控制从类中访问值的方式 — 包括访问所有字段、访问所有具有 get/set 访问方法的属性、只访问公开字段和属性,或仅根据某个注释的指定进行访问。@XmlType 注释在匹配某个模式类型的类或 enum 定义中使用,以向 JAXB 提供模式类型的名称和名称空间(如果有的话)、类型表示中的值的顺序,以及如何使用一个工厂方法构建类的实例(可选)。@XmlRootElement 注释在匹配全局元素定义的类或 enum 定义中使用,用于提供全局元素的名称和名称空间。@XmlElement 和 @XmlAttribute 注释在值中使用(以字段或 JavaBean 属性方法的形式),提供元素或属性名和其他特征)。
JAXB 使用的所有注释都位于 javax.xml.bind.annotation 包中,除了这个简单示例生成的代码中使用的注释外,还包括许多其他注释。JAXB 支持从模式中生成代码(本例就属于此例),也支持从代码中生成。某些注释和选项(比如那些处理对象工厂和串行器/并行器方法的注释和选项)只能用于从代码中生成的情况。
Axis2 中的 JAXB 问题
WSDL2Java 调用 JAXB 参考实现中附带的 XJC 绑定编译器来生成数据模型代码,因此在大多数方面数据模型代码的生成是独立于 Axis2 的。如果直接在 Web 服务使用的模式上面运行 JAXB XJC 绑定编译器,您将生成相同的数据模型。不幸的是,WSDL2Java 和 XJC 之间的阻抗匹配并不总是正确的,这就会导致一些问题。
其中一个问题与在 WSDL 文档中构建模式的方式有关。库服务的初始 WSDL 使用单个文档,该文档合并了两个独立的模式,一个用于 WSDL 消息元素,另一个用于应用程序数据(图书和类型信息)。如 WSDL 允许的那样,消息元素模式通过名称空间引用导入了应用程序数据模式。这个包含嵌入模式的 WSDL 可以很好地与使用 ADB 或 JiBX 数据绑定的 WSDL2Java 工作,但是对于 JAXB,它将在模式处理期间引发一个异常抛出。将应用程序数据模式分离到一个单独的文件并在模式导入时指定文件名称,这将允许 WSDL2Java 使用 JAXB 绑定正确地处理模式。
另一个问题是,XJC 提供了大量代码生成选项以及许多定制来控制针对某一特定模式组件的代码生成细节 — 但是 WSDL2Java 没有提供任何方法来将这些选项或定制传递给 XJC,因此代码生成将始终按照默认设置运行。如果需要使用任何代码生成选项或定制,可能需要单独运行 XJC 和 WSDL2Java。不幸的是,无法在 WSDL2Java 代码生成中使用单独生成的 JAXB 数据模型。如果需要使用定制的 JAXB 数据模型,最佳办法可能就是运行 WSDL2Java 以生成其自己的 JAXB 代码模型,然后再换入您单独生成的数据模型类,并根据需要手动修改代码来将所有内容联合到一起。或者,可以像下一小节介绍的那样使用 JAX-WS,这将使您完全跳过 WSDL2Java,但是存在一些明显的限制。
在 Axis2 中使用 JAX-WS
虽然可以将 JAXB 作为 Axis2 中的另一种数据绑定替代选择,但是它与 JAX-WS 之间的区别更加显著。JAX-WS 是一种截然不同的 Web 服务定义方法,它全面取代了标准的 Axis2 服务器端和客户端配置。您使用 JAX-WS 参考实现(必须从 Axis2 中单独下载;参见 参考资料)中附带的 WsImport 工具从 WSDL 中生成 JAX-WS 代码,而不是使用 WSDL2Java。甚至部署机制也不同于 Axis2 中通常使用的 AAR 文件方法。
代码 下载 部分只提供了早先使用的同一样例应用程序的不同版本,这个版本进行了修改,用于演示 JAX-WS 在 Axis2 中的使用。代码位于下载中的 jaxws 目录中,并且它带有自己的 WSDL、build.properties 和 build.xml。这个 JAX-WS 版本的 WSDL 基本上与用于 JAXB 的 WSDL 相同,如 清单 1 所示。该 WSDL 的主要区别在于它对应用程序代码使用了内嵌的模式,而这对于使用 JAXB 数据绑定的 WSDL2Java 来说是不可行的。
当使用 JAX-WS 的 WsImport 工具从 WSDL 创建代码时,将获得与使用 WSDL2Java 实现 JAXB 代码生成时相同的 JAXB 数据模型和封装器类。不同之处在于链接(linkage)代码,对于 JAX-WS,链接代码包含一个生成的服务接口和一个客户端服务构建器类。接口类,如 清单 5(稍微进行了重新格式化,并且只保留了一个方法注释)所示,定义了与 WSDL 中的操作匹配的方法。客户机代码和服务器代码都使用这个接口。接口中的大量注释提供了所有必需配置信息,帮助 JAX-WS 将服务接口与该服务的操作的接口方法关联起来。
清单 5. JAX-WS 生成的服务接口
@WebService(name = "Library", targetNamespace = "http://ws.sosnoski.com/library/wsdl")
@XmlSeeAlso({
ObjectFactory.class
})
public interface Library
{
/**
*
* @param isbn
* @return
* returns com.sosnoski.ws.library.jaxws.BookInformation
*/
@WebMethod(action = "urn:getBook")
@WebResult(name = "getBookReturn",
targetNamespace = "http://ws.sosnoski.com/library/wsdl")
@RequestWrapper(localName = "getBook",
targetNamespace = "http://ws.sosnoski.com/library/wsdl",
className = "com.sosnoski.ws.library.jaxws.GetBook")
@ResponseWrapper(localName = "getBookResponse",
targetNamespace = "http://ws.sosnoski.com/library/wsdl",
className = "com.sosnoski.ws.library.jaxws.GetBookResponse")
public BookInformation getBook(
@WebParam(name = "isbn", targetNamespace = "http://ws.sosnoski.com/library/wsdl")
String isbn);
@WebMethod(action = "urn:getBooksByType")
@WebResult(name = "getBooksByTypeReturn",
targetNamespace = "http://ws.sosnoski.com/library/wsdl")
@RequestWrapper(localName = "getBooksByType",
targetNamespace = "http://ws.sosnoski.com/library/wsdl",
className = "com.sosnoski.ws.library.jaxws.GetBooksByType")
@ResponseWrapper(localName = "getBooksByTypeResponse",
targetNamespace = "http://ws.sosnoski.com/library/wsdl",
className = "com.sosnoski.ws.library.jaxws.GetBooksByTypeResponse")
public List<BookInformation> getBooksByType(
@WebParam(name = "type", targetNamespace = "http://ws.sosnoski.com/library/wsdl")
String type);
@WebMethod(action = "urn:getTypes")
@WebResult(name = "getTypesReturn",
targetNamespace = "http://ws.sosnoski.com/library/wsdl")
@RequestWrapper(localName = "getTypes",
targetNamespace = "http://ws.sosnoski.com/library/wsdl",
className = "com.sosnoski.ws.library.jaxws.GetTypes")
@ResponseWrapper(localName = "getTypesResponse",
targetNamespace = "http://ws.sosnoski.com/library/wsdl",
className = "com.sosnoski.ws.library.jaxws.GetTypesResponse")
public List<TypeInformation> getTypes();
@WebMethod(action = "urn:addBook")
@WebResult(name = "addBookReturn",
targetNamespace = "http://ws.sosnoski.com/library/wsdl")
@RequestWrapper(localName = "addBook",
targetNamespace = "http://ws.sosnoski.com/library/wsdl",
className = "com.sosnoski.ws.library.jaxws.AddBook")
@ResponseWrapper(localName = "addBookResponse",
targetNamespace = "http://ws.sosnoski.com/library/wsdl",
className = "com.sosnoski.ws.library.jaxws.AddBookResponse")
public boolean addBook(
@WebParam(name = "type", targetNamespace = "http://ws.sosnoski.com/library/wsdl")
String type,
@WebParam(name = "isbn", targetNamespace = "http://ws.sosnoski.com/library/wsdl")
String isbn,
@WebParam(name = "author",
targetNamespace = "http://ws.sosnoski.com/library/wsdl")
List<String> author,
@WebParam(name = "title", targetNamespace = "http://ws.sosnoski.com/library/wsdl")
String title)
throws AddDuplicateFault
;
}
WsImport 工具将附带的 WSDL 识别为匹配 “封装的” 约定,并自动生成一个未封装的服务接口。您可以从 清单 5 中看到效果,在此过程中,方法将单独的值作为输入参数,并直接返回任何合适的类型,而不是使用一个封装器对象层(但是仍然将生成封装器对象,然后供 JAX-WS 运行时在幕后使用)。
附带的代码再一次给出了服务和测试客户机的实际实现。要亲自尝试,您需要对附带的 build.properties 文件进行编辑,以设置到 Axis2 安装和 JAX-WS 参考实现安装的路径(参见 参考资料)。完成编辑后,在打开到 jaxws 目录的控制台中输入 ant 以从 WSDL 运行 JAX-WS 代码生成,编译附带的代码,并为服务器部署构建一个 JAR 文件。要运行测试客户机,将生成的 JAR 文件复制到 Axis2 服务器安装的 WEB-INF/servicejars 目录中,然后在控制台中输入 ant run。
客户端 JAX-WS 使用
清单 6 展示了完整的测试客户机代码。如果将其与 清单 2 比较,您将观察到未封装接口和已封装接口之间的不同,其中未封装接口具有更好的编程友好性。
清单 6. JAX-WS 测试客户机代码
public class WebServiceClient
{
public static void main(String[] args) throws Exception {
// check for required command line parameters
if (args.length < 3) {
System.out.println("Usage:\n java " +
"com.sosnoski.ws.library.jaxws.WebServiceClient host port path");
System.exit(1);
}
// create the client stub
JaxwsLibrary service = new JaxwsLibrary();
Library stub = service.getLibrary();
// set the actual endpoint address
String target = "http://" + args[0] + ":" + args[1] + args[2];
System.out.println("Connecting to " + target);
BindingProvider provider = (BindingProvider)stub;
provider.getRequestContext().
put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, target);
// retrieve a book directly
String isbn = "0061020052";
BookInformation book = stub.getBook(isbn);
if (book == null) {
System.out.println("No book found with ISBN '" + isbn + '\'');
} else {
System.out.println("Retrieved '" + book.getTitle() + '\'');
}
// retrieve the list of types defined
List<TypeInformation> types = stub.getTypes();
System.out.println("Retrieved " + types.size() + " types:");
for (int i = 0; i < types.size(); i++) {
TypeInformation type = types.get(i);
System.out.println(" '" + type.getName() + "' with " +
type.getCount() + " books");
}
// add a new book
String title = "The Dragon Never Sleeps";
isbn = "0445203498";
try {
List<String> authors = new ArrayList<String>();
authors.add("Cook, Glen");
stub.addBook("scifi", isbn, authors, title);
System.out.println("Added '" + title + '\'');
title = "This Should Not Work";
stub.addBook("scifi", isbn, authors, title);
System.out.println("Added duplicate book - should not happen!");
} catch (AddDuplicateFault e) {
System.out.println("Failed adding '" + title +
"' with ISBN '" + isbn + "' - matches existing title '" +
e.getFaultInfo().getBook().getTitle() + '\'');
}
// get all books of a type
List<BookInformation> books = stub.getBooksByType("scifi");
System.out.println("Retrieved " + books.size() + " books of type 'scifi':");
for (int i = 0; i < books.size(); i++) {
System.out.println(" '" + books.get(i).getTitle() + '\'');
}
}
}
JAX-WS 客户端处理通常要求在运行时访问服务 WSDL,并且使用 WSDL 来初始化服务器链接。如果您已经获知目标服务的 WSDL 始终可以在运行时直接从服务器获得,并且该服务器将始终位于同一地址,那么您可以将 WSDL URL 提供给 WsImport,并让它将 URL 硬编码到生成的代码中。对于大多数严肃的工作,最好使用 WSDL 的本地副本,然后,如果目标服务地址不同于 WSDL 中的地址,那么在运行时重写该地址。附带的构建文件采用了这种方法,而 清单 6 中粗体所示的代码部分演示了如何在不修改 WSDL 的情况下在运行时修改服务地址。
服务器端 JAX-WS 使用
服务器端代码的 JAX-WS 版本如 清单 7 所示。实现类的 @WebService 注释(粗体显示)将实现代码与某个特定 Web 服务接口关联起来。实现类的这个注释允许您从生成的服务接口(清单 5)的相应注释中重写设置。在本例中,注释将设置服务和端口名,并给出 WSDL 服务定义的位置(显然 Axis2 希望该位置与类路径的根相关,或是一个绝对 URL)。
清单 7. JAX-WS 服务器代码
@javax.jws.WebService(endpointInterface="com.sosnoski.ws.library.jaxws.Library",
portName="library", targetNamespace="http://ws.sosnoski.com/library/wsdl",
wsdlLocation="com/sosnoski/ws/library/jaxws/library.wsdl", serviceName="JaxwsLibrary")
public class JaxwsLibraryImpl implements Library
{
private final BookServer m_server;
public JaxwsLibraryImpl() {
m_server = new BookServer();
}
public boolean addBook(String type, String isbn, List<String> author, String title)
throws AddDuplicateFault {
BookInformation prior = m_server.getBook(isbn);
if (prior == null) {
BookInformation book = new BookInformation();
book.getAuthor().addAll(author);
book.setIsbn(isbn);
book.setTitle(title);
book.setType(type);
return m_server.addBook(book);
} else {
AddDuplicate ad = new AddDuplicate();
ad.setBook(prior);
AddDuplicateFault e =
new AddDuplicateFault("Book already present with matching ISBN", ad);
throw e;
}
}
public BookInformation getBook(String isbn) {
return m_server.getBook(isbn);
}
public List<BookInformation> getBooksByType(String type) {
return m_server.getBooksByType(type);
}
public List<TypeInformation> getTypes() {
return m_server.getTypes();
}
}
清单 7 中的其余代码仅仅是 清单 3 所示的(封装)JAXB 示例的未封装版本。
Axis2 中的 JAX-WS 问题
Axis2 在基础 JAX-WS 处理方面表现得非常好,但它的确也存在一些限制。最严重的一点就是当您在 Axis2 中使用 JAX-WS 时,缺乏对 WS-Security 或其他 Web 服务扩展技术的支持(但是围绕 Axis2 构建的应用服务器可能使用自己的方式实现配置和使用 WS-Security)。这对于企业应用程序来说是一个服务器限制,并且,除了一个稍微简单些的配置方法外,不存在任何起到补偿作用的优势,似乎没有足够的理由说服人们马上在 Axis2 中使用 JAX-WS。
结束语
在本文中,您了解了将 JAXB 2.x 和 JAX-WS 2.x Java 标准用于 Axis2 的基础知识。Axis2 中的 JAXB 支持存在某些限制,但是,至少对那些不需要定制的简单模式来说,它为 Axis2 中支持的其他数据绑定方法提供了一种有用的替代选择。JAX-WS 支持的限制更多,目前只对那些不需要 WS-Security 或其他任何增值功能的简单服务有用。
本系列目前为止一直关注 Apache Axis2 框架。有关 JAXB 和 JAX-WS 的介绍为研究其他一些同样支持这些标准的开源 Java Web 服务框架提供了一个良好的起点。在下个月中,本专栏将研究由 Sun 开发的 Metro Web Services 框架以及 JAXB 和 JAX-WS 参考实现。我们将更深入地研究 JAX-WS 的使用和特性。