J2EE[TM] 应用程序可能需要访问 Web 服务。在这些情况下,J2EE 组件(如 Servlet、JavaServer[TM] (JSP[TM]) 页、消息驱动 Bean 或 Enterprise Bean)充当 Web 服务的客户端。基于 XML 的远程过程调用的 Java[TM] API (JAX-RPC) 技术提供了访问 Web 服务的标准方法,并且有三种访问 Web 服务的模式:桩模块、动态代理和动态调用接口 (DII)。本文档将重点介绍客户端了解要使用的服务或服务集的用例,因此不涉及动态发现及访问服务的内容。我们将介绍使用不同的策略及其通过它们来访问 Web 服务的影响。
下面是一些注意事项:
首先让我们看一下调用 Web 服务的 J2EE 组件的简要概述。如果您已经熟悉了 J2EE Web 服务客户端编程模型,就可以跳过概述部分。了解概述后,再考虑一下访问 Web 服务的 J2EE 组件涉及的其他问题。
PurchaseOrderServiceSEI.java
接口以及一个 PurchaseOrder.java
类,该类是服务的 submitPO
方法所需的参数类型。
请注意上面的代码片段,调用 Web 服务类似于调用 J2EE 中的其他管理对象。请使用 JNDI 查找并获得对服务对象的引用,然后获得一个特定的端口对象,并在端口对象上调用方法。J2EE 组件在容器内运行,因此可以访问容器服务以通过 JNDI 查找资源。
部署描述符需要一个服务引用元素以用于它所访问的 Web 服务。例如,在 Servlet 或 JSP 组件访问服务所需的 web.xml 文件中,每个被访问的服务都需要有一个服务引用。下面是 web.xml 文件中的代码片段,其中显示了服务引用元素。
在特定于应用服务器的部署描述符中,还需要在组装和部署时将该服务引用映射到部署环境中的值。例如,在 J2EE SDK 中,请使用 sun-web.xml 文件。下面是一个 sun-web.xml 部署描述符的示例。
我们已经了解了访问 Web 服务的 J2EE 组件的基本编程模型,接着让我们考虑一些其他问题。
设计和实现客户端的过程是先从了解客户端准备访问的每个服务的 WSDL 文件开始的。WSDL 文件为客户端提供了访问服务所需的接口、方法、参数和异常。此外,WSDL 文件还可能包含一些诸如服务地址之类的部署信息。请注意,WSDL 文件并非总是提供完整的服务描述。有时需要添加信息才能了解访问服务的要求。例如,WSDL 文件中没有指定服务需要基本认证或相互认证的互操作方法。所以客户端开发者必须从 WSDL 文件之外的来源获得这些服务要求。
另一个需要考虑的问题是客户端从何处获得 WSDL 并在何处保存它。由于我们在这里将不讨论动态发现,因此假定了客户端开发者已经知道获得 WSDL 文件的位置并且可以访问它。服务 WSDL 文件可以在 URL 中部署和找到,但是客户端代码的开发者需要复制该 WSDL 文件,并使其成为开发工作区的一部分。由于客户端以 WSDL 开始,因此以编程方式访问服务所需的客户端工件通常使用 WSDL 和 Java 开发样式。客户端开发工具将基于 WSDL 文件,生成一些表示 WSDL 文件中所述服务的 Java 类。调用该目标服务的 J2EE 组件将使用这些生成的类。除了在生成时使用 WSDL 文件外,客户端应用程序还需要将 WSDL 文件捆绑成可移植打包的一部分。
另外需要考虑的是,如果客户端代码的开发者使用 WSDL 文件生成某些客户端类来表示目标服务接口,则与服务 WSDL 文件中已有类型对应的 Java 类型可能与所需的类型有所不同。所需的可能是 Date 类,而得到的却是 Java Calendar 类。对于 JAX-RPC 的工具和版本而言,WSDL 类型到 Java 类型的映射则可能稍有不同。
与所有应用程序的设计一样,Web 服务客户端的设计也可以从某种结构中获益。客户端代码最好能够将 Web 服务交互代码与其余的客户端代码进行分离。建议您应用要创建的业务委托代码来封装 Web 服务访问,使其与其余的代码分离。业务委托封装了对 Web 服务的访问,并帮助强制将 Web 服务交互代码与其余的客户端代码相分离。使用 ServiceLocator 模式也能帮助清除代码,并通过封装通用查找代码使应用程序更易于管理。有关更多详细信息,请参见 Java BluePrints Solutions catalog 其他项中的使用用于 Web 服务的服务定位器。
对于访问 Web 服务的 J2EE 组件,最好使用通过 JNDI 查找获得的桩模块。但是,可以编写访问代码,使其不依赖于生成的桩模块类。这种操作很重要,因为桩模块类无法在应用服务器之间移植。下面的代码示例 4 显示了 J2EE 应用程序如何使用桩模块访问服务。应用程序使用 JNDI InitialContext.lookup
调用定位服务。JNDI 调用将返回一个 OpcOrderTrackingService
对象,该对象是一个桩模块。
Context
ic = new InitialContext();
StringPurchaseOrderService_Impl
svc = (StringPurchaseOrderService_Impl)
ic.lookup("java:comp/env/service/StringPurchaseOrderService");
StringPurchaseOrderServiceSEI poservice =
svc.getStringPurchaseOrderServiceSEIPort();
String purchaseOrder = ... // This is the string
representation of a purchase order in XML
String result = poservice.submitPO(purchaseOrder);
代码示例 4:在 J2EE 环境中访问桩模块(不建议使用的方法)
因为它依赖于生成的桩模块类,因此以上客户端代码不是使用桩模块的建议策略。尽管该示例可以正常运行,但是 JAX-RPC 为您提供了一种中性方法来访问服务,并可获得相同的结果。通过使用 JAX-RPC javax.xml.rpc.Service
接口方法 getPort
,能够以相同的方式访问 Web 服务,而无需考虑使用的是桩模块还是动态代理。getPort
方法将返回一个生成的桩模块实现类的实例或动态代理,然后,客户端可以使用这个返回的实例在服务端点上调用操作。代码示例 5 说明了如何删除对生成的桩模块类的依赖性。
Context
ic = new InitialContext();
Service svc = (Service)
ic.lookup("java:comp/env/service/StringPurchaseOrderService");
StringPurchaseOrderServiceSEI poservice =
(StringPurchaseOrderServiceSEI)
svc.getPort(StringPurchaseOrderServiceSEI.class);
String purchaseOrder = ... // This is the string
representation of a purchase order in XML
String result = poservice.submitPO(purchaseOrder);
代码示例 5:在 J2EE 环境中访问桩模块(建议的方法)
javax.xml.rpc.Service
接口,而不是指定给服务实现类。通过调用 getPort
方法可获得服务端点接口的客户端表示。获得端口之后,客户端可在该端口上执行应用程序所需的任何调用。调用上述代码之后,JAX-RPC 运行环境会选择与端口进行通信的端口和协议绑定,然后配置表示服务端点接口的返回的桩模块。并且,因为 J2EE 平台允许部署描述符为一项服务指定多个端口,所以容器可以基于其配置为服务调用选择最适用的协议绑定和端口。
访问 Web 服务的客户端应用程序会出现两种异常:系统异常和特定于服务的异常,这两种异常均由服务端点抛出。客户端应用程序必须能处理这两种类型的异常。
在下列情况下将抛出系统异常:调用服务方法时传递了不正确的参数,服务不可访问,网络发生错误或发生了其他一些应用程序无法控制的错误。通常,除了重试调用服务以外,客户端无法对系统异常执行任何操作。Web 服务的客户端通常会使用一种工具,使 JAX-RPC 生成一些映射到目标服务 WSDL 文件的客户端接口。这些要在客户端代码中使用的已生成接口将为系统异常抛出远程异常。使用此接口调用服务的代码需要捕捉这些远程异常。
遇到特定于服务的错误时会抛出服务异常(从故障映射)。服务异常由目标 Web 服务定义并公开为 WSDL 文件的一部分。其中一个特定于服务的异常的示例是:提交数据的验证显示错误并且抛出一个无效数据的异常。当开发者对应于目标服务 WSDL 文件生成客户端接口和类时,服务异常也会作为服务异常的客户端表示而生成。多数情况下,客户端代码可以执行更正操作并使用更改后的数据重新提交请求,所以这些特定于服务的异常是可恢复的。值得注意的是,当设计访问服务的客户端时,需要说明这些在目标服务 WSDL 文件中指定的不同的异常类型,并设计处理它们的客户端代码。
充当 Web 服务客户端的 J2EE 组件也会像其他 J2EE 应用程序一样被打包。值得注意的是,这些带有访问 Web 服务的 J2EE 组件的应用程序是可移植的。可以直接打包。如果客户端是一个 Web 组件,则将它和典型的 Web 工件(例如,web .xml 文件和其他一些特定于 Web 服务访问代码的工件)打包在一个 war 文件中。同样,将 EJB 组件和典型的 EJB 工件(例如,ejb-jar.xml 文件和其他一些特定于 Web 服务访问代码的工件)打包在一个 ejb-jar 文件中。
在 J2EE 环境中运行的 Web 服务客户端需要这些附加的工件,其中包括:
service-ref
元素。目标 Web 服务可能具有安全性要求。WSDL 文件有时会指定安全性要求,有时则不会,尽管服务端点可能要求指定。在任何情况下,如果目标服务具有某些安全性要求,则客户端必须对其进行处理。一些安全性要求示例包括:目标端点在 SSL 上可用,目标服务指定基本的认证,因此客户端需要提供用户名和口令,或者目标服务希望执行相互认证,因此客户端还需要提供数字证书。此外,目标服务还可以使用一些消息级安全性。处理上述情况(提供用户名和口令,为 SSL 处理目标服务的证书,向目标服务提供客户端的数字证书以便进行相互认证等)的客户端配置全部是特定于应用服务器的,因此这里不再进行详细描述。调用 Web 服务时,可以参阅应用服务器配置安全环境的文档。
尽管客户端代码可以通过在其中提供必要的安全工件来处理目标服务的安全性要求,但应该避免在客户端代码中处理安全性。而应该是客户端使用应用服务器配置工具来配置服务引用,以便处理目标服务的安全性要求。这样,J2EE 容器便可以管理配置的服务引用的安全性,并在调用目标服务时提供运行时必需的工件。其中一个处理目标服务安全性要求的代码示例是:服务具有指定的基本认证,并且客户端代码为桩模块属性设置用户名和口令。建议的备选方法是将应用服务器配置为管理客户端应用程序的服务引用的基本认证。这样,提供的用户名和口令将由应用服务器而不是客户端代码进行处理。此方法可使代码更简洁。