引言
在过去数年里,随着万维网联盟(World Wide Web Consortium,W3C)更新了核心规范,并引入了弥补 Web 服务最初缺陷的新规范,Web 服务发生了大量的变化。W3C 的 Web Services Activity 小组所维护的规范以独立于供应商的方式将 Web 服务作为一组 XML 规范进行处理。
同时,Java™ Community Process (JCP) 也在维护自己的规范集,以将 W3C 的建议合并到 Java 语言中。Java APIs for XML(JAX-RPC、JAXB、JAXP、JAXR 和 SAAJ)是一组使用 Java 语言实现 Web 服务规范的接口。
W3C 所维护的当前 Web 服务规范和 JCP 维护的 Java Web 服务 API 处理“网络上”的 Web 服务,以确保平台独立性和语言独立性。遵循 XML 规范或使用 Java API 的开发人员将确保应用程序能够通过任何通信协议在任何平台上与采用任何语言编写的 Web 服务进行通信。Web 服务可扩展任何应用程序的访问范围,是经过验证的对目前基于 Web 的应用程序有价值的集成技术。
但当基于 Web 的应用程序需要跨多个 Web 应用程序容器(如 IBM® WebSphere® Application Server、BEA WebLogic 和 Tomcat 等,这里仅指出三个)部署时,跨网络兼容性不够。对于 Java Web 服务,没有跨多个 Web 应用程序容器实现的标准部署的“web.xml”可用。
如果您希望应用程序支持多个 Web 应用程序容器提供的 Web 服务实现,则 Java Web 服务应用程序的部署可能会成为一项挑战。可以在 Web 应用程序中使用单个 Web 服务实现,如来自 Apache Web 服务项目的 Axis。对于 Web 服务客户机,这个策略通常能跨多个 Web 容器工作,因为客户机代码并不依赖于任何 Web 服务部署描述符。对于 Web 服务提供者(服务器),如果将 Web 服务实现嵌入 Web 应用程序存档(Web Application Archive,war)文件中,可能会导致意外加载器冲突,因此使用供应商的 Web 服务实现是最理想的部署选择。
本文剩下的部分将讨论 Java Web 服务的部署问题,向您展示各种部署描述符实现,并讨论 Java 社区如何开始处理这个问题。
开发跨多个容器部署的单个 Web 服务
对于 Web 应用程序部署,我们希望进行开放性的选择。如果您的客户在 WebSphere 或 WebLogic 等商业 J2EE 实现进行了投资,他们将希望利用其投资的平台。另一方面,如果您的客户希望降低初期投入成本,则可能希望采用 JBoss 或 Apache Tomcat 等开放源代码解决方案。在这两种情况下,如果您希望尽可能提高开发工作的可重用性,则可能无法依赖于可用的供应商特定 IDE。使用 J2EE 应用程序供应商提供的 IDE 进行开发工作可能会限制处理 Java Web 服务时的灵活性,隐藏部署 Web 服务的很多细节。
本文中的示例使用开放源代码社区提供的免费标准的开发工具集来为每个目标 Web 应用程序容器构建 Web 服务部署描述符。所有这些工具均在开发人员中得到了广泛应用,且支持各种开放标准技术。
我们的目标是,获得能够生成可使用 Axis 跨目标 Web 应用程序容器(WebSphere、WebLogic、JBoss 和 Tomcat)部署的 Web 服务的单个项目。相应的 war 文件应该能够在只需很少修改而绝对不需要重新编译源代码的情况下部署到我们的目标 Web 应用程序容器上。
本文并不打算作为有关 Web 服务或 Web 服务部署的教程,而旨在说明 Java Web 服务的一个问题,并阐述将来可以如何处理这个问题。如果您仅使用一个 Web 应用程序容器,而没有打算更改 Web 应用程序容器,则可以跳过有关 Web Services Metadata (JSR-181) 的部分(此 JSR 可能会影响您将来的开发工作)。
Web 服务的描述
为了提供有关我们的部署示例的足够信息,我创建了一个需要为接口使用映射文件的 Web 服务。传统的 Hello World 或 Stock Ticker Web 服务将无法提供足够的信息来说明我们的 Web 服务部署需求。
我们的 Web 服务端点将具有多个方法,这些方法可用于说明部署期间所需的各种文件。我们的示例 Web 服务将返回有关远程应用程序性能的信息。当然,我们的示例实现并不会返回任何实际的信息,它只不过是一个简单的示例,用于说明更为复杂的 Web 服务接口的要求。下面是用于创建我们的 Web 服务端点的接口文件。
清单 1. Web 服务端点接口
public interface StatsService extends java.rmi.Remote { public StatsContainer[] getAllStatistics() throws java.rmi.RemoteException; public StatsContainer[] getStatistics(String category) throws java.rmi.RemoteException; public void resetAllStatistics() throws java.rmi.RemoteException; public void resetStatistics(String category) throws java.rmi.RemoteException; public void clearStatistics() throws java.rmi.RemoteException; public String[] getCategories() throws java.rmi.RemoteException; } |
此接口具有两个不同的返回类型,需要进行映射。第一个类型是 StatsContainer 对象数组,而另一个类型则是 string 对象数组。StatsContainer 是简单容器对象,该对象具有若干基元类型和两个字符串。我们的目标是,从此接口和实现文件入手,使用我们的开放工具集中提供的工具构建所需的部署描述符, 以便将 Web 服务部署到目标平台上。我们将描述此过程中的每个步骤以及生成的各个文件。
构建过程的描述
我们的 Web 服务的构建过程中将使用各种自动化工具,这些工具由可利用 Java 自检构建 Web 服务构件的 Web 服务实现提供。对于我们的部署,将使用两种不同的构建工具,因为部署描述符分属两个不同的组:支持 J2EE 1.4 的部署描述符和自定义 Web 服务部署描述符。
J2EE 1.4 Web 服务
为了构建标准 J2EE 1.4 Web 服务所需的构件,我们使用了 Java Web Service Developers Pack (JWSDP v1.5) 所提供的 wscompile 命令。wscompile 命令会创建 Web 服务描述语言(Web Service Description Language,WSDL)文件、Web 服务映射文件和实现文件,以便在 Web 服务和调用的应用程序之间进行封送处理。
为了运行 wscompile 命令,您首先需要编写一个 XML 配置文件,在其中描述您希望 wscompile 执行的操作。在此示例中,我们希望处理我们的服务端点并创建实现所需的 XML 构件和序列化代码。下面是 wscompile 命令所需的配置文件的示例:
清单 2. 示例配置文件
<?xml version="1.0" encoding="UTF-8"?> <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config"> <service name="StatsWS" targetNamespace="http://services.symmetrysolutions.com/stats" typeNamespace="http://types.symmetrysolutions.com/stats" packageName="com.symmetrysolutions.statsws"> <interface name="com.symmetrysolutions.statsws.StatsService" servantName="com.symmetrysolutions.statsws.StatsServiceImpl"/> </service> </configuration> |
在此配置文件中,我们使用 service 元素描述我们的 Web 服务。此元素告知 wscompile 命令以下内容:将与接口关联的命名空间以及与该接口关联的类型。除了命名空间外,service 元素还告知 wscompile 命令为生成的任何源代码使用的包名称以及其将自检的接口的名称。
运行自动化工具并不是部署符合 J2EE 1.4 的 Web 服务的最后一步。根据所使用的 Web 应用程序容器的不同,最后的步骤将有所变化。无论在何种情况下,必须包含的最后一个 Web 服务部署构件都是 webservices.xml 文件。此文件告知 J2EE 1.4 应用程序容器在何处查找 Web 服务描述,以及将什么接口和实现类用于 Web 服务。
Axis Web 服务
Axis Web 服务的构建过程与 J2EE 1.4 Web 服务不同,因为 Axis 具有自己的部署描述符。Axis Web 服务运行时需要 deploy.wsdd 文件提供的信息,以确定服务端点的名称和将其作为 Web 服务发布的方式。deploy.wsdd 文件将发送到 Axis 服务器,Axis 服务器将利用此信息对 Web 服务进行自检,并创建 Web 服务运行时所需的信息。注意:上述过程并不符合 J2EE 1.4 (部署构件),但符合 SOAP 1.1,因此 Axis Web 服务将能够与任何 Web 服务客户机进行互操作。
要构建 Axis Web 服务部署描述符,可以手动进行,也可以使用 Axis WSDL2Java Ant 任务来处理 Web 服务的 WSDL。在我们的示例中,由于我们决定使用 Web 服务端点的接口,因此没有 WSDL 文件。幸运的是,Axis 还提供了一个 Ant 任务来处理接口并输出 WSDL 文件。因此,Axis 构建过程包含两个步骤,如下所述:
- 使用 Java2WSDL 任务从接口创建 WSDL 文件。
- 从 WSDL 创建 Web 服务框架(本文中将不使用)和部署描述符(将在本文中使用)。
构建了 Axis Web 服务后,必须告知 Web 应用程序容器要部署的服务以及如何进行部署,以便进行部署。这是通过将 deploy.wsdd 文件传递给部署 Web 服务的 Axis 管理员任务来完成的。这意味着,在 Web 容器启动后,Axis Web 服务需要进行部署 Web 服务的步骤。有关详细信息,请参见 Tomcat 上的 Axis 部署部分。
部署过程的描述
创建了所需的全部 Web 服务部署描述符后,最后一步是在每个目标平台上部署应用程序。我们将说明为了部署 Web 服务而需要在每个目标 Web 应用程序平台上进行的最后步骤。
IBM WebSphere 和 JBoss 4.0.4
IBM WebSphere 和 JBoss 4.0 均符合 J2EE 1.4,可以使用 Java Web Services Developer Pack (JWSDP v1.5) 或类似工具来生成 JAX-RPC Web 服务构件。唯一还没有为部署生成的 Web 服务构件是 webservices.xml 文件,该文件描述如何将所有组件组合到一起。
在 JBoss 4.0.4 上部署
要在 JBoss 上部署 Web 服务,必须进行以下步骤:(请注意,我们必须使用 JBoss 4.0.4,因为在 JBoss 的早期版本上部署数组类型会出现问题。)
- 创建 webservices.xml 文件来描述我们的 J2EE 1.4 Web 服务部署,如下所示:
清单 3. 示例 webservices.xml 文件
<?xml version="1.0" encoding="UTF-8"?> <webservices xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://www.ibm.com/webservices/xsd/j2ee_web_services_1_1.xsd" version="1.1"> <webservice-description> <webservice-description-name>StatsWS</webservice-description-name> <wsdl-file>WEB-INF/wsdl/StatsWS.wsdl</wsdl-file> <jaxrpc-mapping-file>WEB-INF/mapping.xml</jaxrpc-mapping-file> <port-component> <port-component-name>StatsWS</port-component-name> <wsdl-port>StatsServicePort</wsdl-port> <service-endpoint-interface> com.symmetrysolutions.statsws.StatsService </service-endpoint-interface> <service-impl-bean> <!—This servlet is declared in our web.xml file --> <servlet-link>StatsWS</servlet-link> </service-impl-bean> </port-component> </webservice-description> </webservices> |
- 修改 web.xml 文件,以将 Web 服务端点声明为 Servlet,如下所示。请注意,正是在此处将 StatsWS 绑定到实现类。以下声明位于 web.xml 文件中,用于将实现绑定到 Servlet。
清单 4. web.xml 中的 Web 服务 Servlet 引用
<!-- This name is declared in the webservices.xml file --> <servlet> <servlet-name>StatsWS</servlet-name> <servlet-class> com.symmetrysolutions.statsws.StatsServiceImpl </servlet-class> </servlet> |
在 IBM WebSphere 上部署
IBM WebSphere Web 服务的构建过程与 J2EE 1.4 Web 服务类似,唯一不同的是使用了 WebSphere 特定的工具来生成所需的部署描述符(J2EE 1.4 标准 + WebSphere 特定)。要构建 WebSphere Web 服务部署描述符,您可以手动进行,也可以使用 WSDL2Java 任务来处理 Web 服务的 WSDL。在我们的示例中,由于我们决定使用 Web 服务端点的接口,因此没有 WSDL 文件。因此,WebSphere 构建过程包含两个步骤,如下所述:
- 使用 Java2WSDL 任务从接口创建 WSDL 文件。
- 从 WSDL 创建 Web 服务部署描述符。
通过完成上述任务构建了 WebSphere Web 服务后,必须将所有生成的构件(序列化类和部署描述符)打包到 war 文件中,以便能在 WebSphere 服务器上部署。
Tomcat 上的 Axis 部署
部署 Axis Web 服务需要在 Web 容器内执行 Axis 特定的命令,以告知 Axis 引擎部署 Web 服务。这在生产应用程序中可能比较困难,因为要依赖手动步骤重新启动来部署 Web 服务。为了处理此问题,可以为 Web 应用程序发布的 Web 服务发出所有部署命令,然后将生成的 service-config.wsdd 文件嵌入到 war 文件中。Axis 引擎启动时(根据 web.xml 文件中的配置设置),它会查找 service-config.wsdd 文件,并自动重新部署 Web 服务。
要部署 Axis Web 服务,需要执行以下步骤:
- 修改 web.xml 文件,以包含 Axis 引擎的声明(Servlet 声明),如以下的清单 5 中所示。
- 从 WSDL 创建 Web 服务部署描述符。
清单 5. web.xml 中的 Axis 引擎的 Servlet 声明
<!-- AXIS servlet definition --> <servlet-mapping> <servlet-mapping> |
- 打包并部署 Web 应用程序。
- 运行 Axis AdminService,以部署为 Web 服务生成的部署描述符。如果生成的配置未包含在 war 文件中,则必须在每次启动服务器时执行此步骤。
- 或者,可以将 service-config.wsdd 重新打包回 war 文件中,以供将来进行部署。这样将避免在每次启动 Axis 引擎时执行前述步骤,但是,如果打包和部署所使用的 Axis 引擎版本不同,Axis 引擎可能无法正常工作。这只应该在部署时进行,以便在 Axis 引擎发生变化的情况下能生成新的 service-config.wsdd 部署描述符。
BEA WebLogic 商业 Web 服务容器在 8.1 及更早版本中使用自定义部署描述符。它还使用标准 JAX-RPC 描述符(可以通过使用符合 JSR-181 的注释生成)。BEA 将 JSR-181 引入了 Java Community Process。根据 WebLogic 9.x 开发者指南,WebLogic 平台的建议部署流程采用 Web Services MetaData(下面描述的 JSR-181)来标记用于实现 Web 服务的 Java 文件、使用 JDK 5.0(用于提供注释支持)编译带注释的文件,以及使用 WebLogic JSR-181 处理器来处理最后所得到的类文件。这将产生符合 J2EE 1.4 规范的其他 Web 服务构件。WebLogic 提供了称为 jwsc 的 Ant 任务来执行最后一个操作。运行了 jwsc 任务后,就可以将 Web 服务部署描述符打包成 war 文件,以进行部署。
JSR-181 简化 Web 服务开发和部署
前面讨论的观点具有双重意思:
- 说明跨多个 Web 应用程序容器部署 Web 服务所需的步骤
- 说明开发人员尝试在 Java 平台上部署 Web 服务时将遇到的困难
在说明 JSR-181 在简化 Web 服务部署方面的好处时,我们将重点讨论这两个目标中的后者。
JSR-181 是由 BEA Systems, Inc. 引入 Java Community Process (JCP) 的,用于简化使用 Java 平台开发和部署 Web 服务的过程。JSR-181 中描述的规范依赖于 J2SE 5.0 的功能来对描述 Web 服务实现的 Web 服务元数据进行注释。通过在源代码中使用一些简单的 Web 服务注释,Web 容器将能够在无需满足任何其他开发要求的情况下发布 Web 服务。
如规范中所述,JSR-181 所涉及的范围如下:
- 定义用于进行 Web 服务应用程序编程的带注释 Java 语法
- 提供可促进和加速开发的简化 Web 服务开发模型
- 提供可通过工具进行操作的语法
- 定义构建和部署 Web 服务的标准,而无需了解通用 API 和部署描述符的知识和使用相关实现
此规范的总体目标是使 Java Web 服务简单且易于部署,以提供最常见的 Web 服务功能。此规范并未定义 Web 容器必须执行何种操作来部署 Web 服务,从而使得所得到的 WSDL(Web 服务契约)跨 Web 容器保持一致且符合开发人员的设想。
JSR-181 编程模型JSR-181 引入的编程模型建立在 J2EE 1.4 服务器模型和 JAX-RPC 的基础之上,简化了开发人员需要维护的 Web 服务构件数量。根据服务实现的起点不同,JSR-181 可以大幅度减少为了实现 Java Web 服务所必须维护的构件的数量。该规范描述了开始 Web 服务开发的若干不同方法以及 Web 服务注释功能如何帮助进行开发工作。Web 服务入门编程模型如下所述。
从 Java 入手
从 Java 入手将可能是 Java Web 服务创建者最常采用的方法,同时也是该规范唯一要求 的编程模型。此编程模型允许开发人员创建实现类,并为所需 Web 服务功能添加注释。其他 Web 服务构件(WSDL、模式和部署描述符)将自动由 JSR-181 处理器从带注释的 Java 类生成。WSDL 的缺省生成将遵循 JAX-RPC 1.1 所定义的 Java 到 XML/WSDL 的映射,但开发人员可以通过使用 Web 服务注释来自定义 WSDL。
从 WSDL 入手
从 WSDL 入手的编程模型用于生成服务端点接口以及表示模式定义的类和 WSDL 内定义的各个消息部分。 在此模型中,JSR-181 注释直接在实现文件中使用——而实现文件必须由开发人员创建,以定义 WSDL 服务契约未确定的细节(如绑定或服务位置信息)。
从 WSDL 和 Java 同时入手
从 WSDL 和 Java 同时入手的编程模型用于将实现映射到 WSDL 中定义的接口契约。支持此编程模型时,JSR-181 处理器必须 在实现文件中描述的注释与 WSDL 中定义的契约不匹配时提供反馈信息。
JSR-181 处理器
JSR-181 规范对 Web 服务容器内的 JSR-181 处理器的实现细节的规定仍然十分开放,唯一的要求就是处理器能产生可运行的 Web 服务。这个开放性的实际结果就是,JSR 实现开始在市场上出现。开发人员将来可以使用多种不同类型的处理器来实现从 Java 入手的编程模型。例如,一个模型可以就是产生符合 J2EE 1.4 的 Web 服务构件的预处理器。在这种情况下,预处理器可能产生一个配置文件和 webservices.xml 部署文件,并调用 Java2WSDL 编译步骤。另一个实现可以提供拖放 Web 服务部署,会在运行时处理注释,以直接从 Web 应用程序中包含的类直接发布 Web 服务。
JSR-181 中的 Web 服务注释
如果继续构建之前的示例 Web 服务,我们可以使用 JSR-181 注释实现它,以帮助生成 J2EE 1.4 Web 服务构件。通过使用 JSR-181 处理器,我们前面的示例将不需要在开发时包含任何部署构件,可以从实现文件中的注释生成可行的 Web 服务。下面是 JSR-181 提供的一些注释的简单描述。有关 Web 服务注释的完整讨论,请参见“参考资料”部分提供的规范。
清单 6. WebService 注释
@WebService( name = "StatsWS", targetNamespace = "http://services.symmetrysolutions.com/statsws", serviceName = "StatsWS" ) |
WebService 注释(必需)位于 Java 文件中的类或接口声明之前。当 WebService 注释位于类声明前时,它将类标记为实现 Web 服务,除非使用 WebMethod 注释显式声明,或声明了 endpointInterface,否则所有公共方法都将成为 Web 服务接口的一部分。当 WebService 注释位于接口声明前时,它将接口标识为 Web 服务接口,接口内的所有方法都被视为 Web 服务端点的一部分,而不会考虑接口内的 WebMethod 注释。
清单 7. SOAPBinding 注释
@SOAPBinding(style = SOAPBinding.Style.RPC, use = SOAPBinding.Use.ENCODED) |
SOAPBinding 注释(可选)位于 Java 文件内的类或接口声明前。SOAPBinding 注释允许开发人员控制 Web 服务在 SOAP 消息协议上的映射。
清单 8. WebMethod 注释
@WebMethod(operationName = "getAllStatistics") |
WebMethod 注释(可选)在方法级别声明,用于自定义作为 Web 服务操作公开的方法。在实现类中使用时,将允许开发人员限制将哪些方法作为 Web 服务公开、与操作关联的名称以及 SOAPAction 绑定。在接口文件中使用时,仅用于控制与操作关联的名称以及 SOAPAction 绑定。
清单 9. WebParam 注释
@WebParam(name = "category", mode="IN") |
WebParam 注释(可选)在方法内声明,用于自定义 Web 服务操作内的参数。WebParam 最常与 RPC 样式绑定一起使用,但也可以用于将元素名称的参数与采用 DOCUMENT 样式绑定的命名空间关联。
清单 10. WebResult 注释
@WebResult(name = "categoryList") |
WebResult 注释(可选)在方法级别声明,用于自定义 Web 服务操作的返回值。
从上面提到的列表中可以看出,使用 JSR-181 处理器部署 Web 服务可以大幅度减少基于 Java 的 Web 服务的开发和部署的工作量。此注释列表并不完整,但却代表了可能在典型部署中使用的最常见注释。我建议您阅读完整的 JSR-181 规范,以全面了解 Web 服务注释的所有功能。
使用 Web 服务注释
如果搜索支持 JSR-181 的 Web 应用程序容器,所得到的结果将非常有限。撰写本文时,只要极少数产品支持 JSR-181,包括 JBoss 4.0.4、BEA WebLogic 9.x 和 JSR-181 参考实现。为了测试带注释的 Java Web 服务,我建议执行以下步骤:
- 确保安装了 J2SE 1.5 (5.0),且它是您的缺省 Java 编译器。当尝试编译注释时,可以立即知道它是否是缺省 Java 编译器。
- 下载 JSR-181 实现。我决定使用 JBoss 4.0.4,因为它是唯一具有全面 JAX-RPC 支持的 JSR-181 开放源代码实现。
- 安装 JBoss 4.0.4 并确保选择了 EJB3 选项(可能不一定需要进行此步骤)。到撰写本文时,JBoss 4.0.4 的 GA 版或更高版本应该可以获得。如果不能获得此版本,请确保下载并安装 JBossWS 的 GA 版,因为此版本提供了对 JSR-181 的全面支持。
- 将注释添加到 Web 服务实现或实现和接口中。
- 编译并打包 Web 应用程序。
- 将 war 文件拖放到 JBoss 的部署目录,并启动。现在 JBossWS JSR-181 处理器应对您的 Web 服务进行部署。
从前面的步骤可以看出,在 Web 容器内实现 JSR-181 处理器时,Java Web 服务部署可能简单到执行一次拖放操作即可完成。
正如前面所提到的,JSR-181 规范对 Web 容器中运行的处理器并没有任何特殊的要求。唯一的要求是,JSR-181 处理器完成工作后,将获得具有一致的 WSDL 契约的可部署 Web 服务。这个要求将很可能采用多种方式实现。第一个方法通过 JBoss 4.0.4 服务器进行了说明,即支持拖放部署。此方法并不会产生开发人员可用的任何部署描述符构件,但却是最简单的部署方法。在需要对部署描述符进行某些自定义的情 况下,此方法可能行不通。在此情况下,继续采用前面所述的标准 J2EE 1.4 部署可能是最佳选择。
另一个部署方法就是采用集成开发环境(Integrated Development Environment,IDE)的外接程序组件或可以在构建过程中运行的 Ant 任务。(请参见上面有关部署 BEA WebLogic 的描述。)处理器外接程序可以在将产品打包为 war 文件前正确运行,并且将基于注释为 Web 服务生成服务器特定的部署描述符。Apache Beehive 项目好像正在开发这样的组件,在不久的将来可能会提供一个通用 JSR-181 实现。(请参见“参考资料”部分中有关 Apache Beehive 的信息。)通过使用这种实现方法,处理器将生成可允许开发人员进行进一步自定义的部署描述符。
Web Services MetaData (JSR-181) 无疑将使得构建和部署 Java Web 服务的工作比目前简单得多。开发人员将不必了解很多不同的部署描述符,还可能不必在部署时对 Web 应用程序进行修改。Web 服务注释是 Java 语言将如何发展的一个例子,它提供了应用程序体系结构设计和应用程序部署之间一个重要的联系。架构师可以描述接口的用途,而运行时平台能够将此转换为可部 署的解决方案。