引言
Web 服务现在已成为 Java 2 企业版 (J2EE1.4) 平台的一个核心部分。最新的 J2EE 规范定义了如何利用现有的 J2EE 组件来开发具有互操作性的 Web 服务应用程序。
这样,开发人员不仅可以从一个提供最先进技术的成熟的平台中获益,而且可以使用它来创建具有互操作性的 [1] Web 服务应用程序,最重要的是 — 创建可适用于所有遵循 J2EE 1.4 规范厂商的应用程序。
J2EE Web 服务利用了 Java 规范请求 (JSR) 的保护来进行实施
- JSR 109 (实施企业 Web 服务)
- JSR153 (EJB2.1 企业 Java Bean)
- JSR 101 (为基于 XML 的远程过程调用提供的 JAX-RPC Java API)
- JSR 93 (为 XML Registries 1.0 提供的 JAXR Java API)
本文试图专注于 JSR109 规范,并通过一些简单的演示来说明它的细微差别。我们假定读者已经熟悉了 J2EE 打包的概念,并且对 JAX-RPC 规范和基本的 Web 服务概念(如 SOAP 和 WSDL)有一定的了解。
JSR109 试图解决以下 Web 服务应用程序实施的主要步骤:
- 开发 — Web 服务部署描述符和编程模型的标准化
- 部署 — 安排一个 J2EE 1.4 容器预期的最小部署时间操作集
- 服务发布 — 如何向客户端提供 Web 服务的 WSDL
- 服务消费 — 客户端部署描述符和个 JNDI 查找模型的标准化
与跨多个平台的松散连接、分布式应用程序(Web 服务的本质)的精神保持一致,J2EE 服务器端的 Web 服务应用程序可以被任意 Web 服务客户端调用,而 J2EE Web 服务客户端同样可以调用任意 Web 服务。
Web 服务服务器编程模型
J2EE1.4 允许开发人员使用以下两者来编写 Web 服务实施逻辑:
- 非会话状态 Java 类
- 非会话状态会话 Bean (SLSB)

图 1: 显示服务器端 Web 服务的体系结构
与 J2EE 体系结构保持一致,一个服务实施的容器被用作访问 Web 服务实施(一个非会话状态会话 Bean 或一个 Java 类)的媒介。最终结果是 Web 服务客户端从不直接访问服务,而始终通过容器来进行访问。
这使 Web 服务应用程序能够从容器提供的额外功能(如安全性、管理、增强的日志功能和服务质量保证)中受益。换句话说,容器使开发人员不必再受创建企业应用程序所带来枯燥而困扰。
我们将浏览一些演示,从而对以上所讨论的概念获取一点感性认识。本文中讨论的演示假定已安装了 Oracle Application Server Containers for J2EE 10g (OC4J) 和编译工具 Ant。
在下载并配置了这些演示、以及 OC4J 和 Ant 之后,请按照 [demo root]/basic/statelessdemo 目录下的 Readme.html 中的指令进行操作,其中 [demo_root] 是解压缩这些演示的目录。这显示了一个作为 Web 服务的非会话状态 Java 类。
注:使用 Java 类实施的 Web 服务有时也被称为“Servlet 实施”,这只是因为这样的 Java 类实际运行在一个 Servlet 容器中。
从命令行使用以下命令执行一次清除、编译和汇编生成操作:
注:在运行 ant 脚本之前,确保已设置了以下环境变量:
JAVA_HOME 设为 JDK 的位置
ORACLE_HOME 设为解压缩 OC4J 的目录位置
J2EE_HOME 设为 ORACLE_HOME/j2ee/home
%ant compile-service-artifacts
%ant gen-service
这将编译 Web 服务并将其打包至一个 ear 文件中。在 [demo root]/basic/stateless/dist/hello.ear 中找到可部署的应用程序。从命令行使用 Java jar 命令解压缩这些 ear 和 war 文件:
%jar -xvf hello.ear
%jar -xvf hello-web.war
生成 hello-web.war.
让我们解开 hello-web.war 的包结构。 您将看到如图 2 所示的结构。
/WEB-INF /web.xml ----------------- servlet container info and service endpoint impl. /webservices.xml --------- describes the Web Service part of the application /oracle-webservices.xml--- information specific to the oracle’s Web Service container /wsdl/HelloService.wsdl--- wsdl is the Web Service contract published externally /HelloService-java-wsdl-mapping.xml --- map the wsdl to the Service Endpoint Interface /classes /Service Endpoint Interface[2] and the Service Implementation class[3] |
我们特别感兴趣的是图 3 所示的 webservices.xml 的结构。
<webservices> <webservice-description> < webservice-description-name>HelloService</webservice-description-name> < wsdl-file>WEB-INF/wsdl/HelloService.wsdl</wsdl-file> < jaxrpc-mapping-file>WEB-INF/HelloService-java-wsdl-mapping.xml </jaxrpc-mapping-file> < port-component> < port-component-name>HelloInterfacePort </port-component-name> < wsdl-port xmlns:wsa0="http://hello.demo.oracle/">wsa0:HelloInterfacePort</wsdl-port> < service-endpoint-interface> ;oracle.demo.hello.HelloInterface < /service-endpoint-interface> < service-impl-bean> < servlet-link> HelloInterfacePort</servlet-link> < /service-impl-bean> </port-component> </webservice-description> < /webservices> |
您可以看到,webservices.xml 收集了容器部署 Web 服务应用程序所需的全部信息。它提供了 WSDL <wsdl-file> 的位置、映射文件 [3] <jaxrpc-mapping-file>、对应 WSDL 中端口的 <port-component>、Java 服务终端接口 (SEI) <service-endpoint-interface>、WSDL 的 Java 表示、以及作为其 SOAP HTTP 监听器的 servlet 的名称 <servlet-link>。
现在让我们看一下图 4 所示的 /WEB-INF/web.xml,并对其进行分析。
<web-app> ..... < servlet> < servlet-name> HelloInterfacePort</servlet-name> <display-name>HelloInterfacePort</display-name> <description>JAX-RPC endpoint - HelloInterfacePort</description> <servlet-class>oracle.demo.hello.HelloImpl</servlet-class> <load-on-startup>1</load-on-startup> < /servlet> < servlet-mapping> <servlet-name>HelloInterfacePort</servlet-name> <url-pattern>/HelloService</url-pattern> < /servlet-mapping> < /web-app> |
请注意 webservices.xml 中的 <servlet-link> 引用了 web.xml 中给出的 <servlet-name>。这使得 Web 服务实施受到 servlet 的影响,从而受到 HTTP [HTTP 上的 SOAP] 的影响。
注: web.xml 中的 <servlet-class> 实际引用的是实施 Web 服务 SEI (webservices.xml 中的 <service-endpoint-interface >)的 Java 类(在这种情况下为 oracle.demo.hello.HelloImpl),它实际上不是一个 Servlet。
这种通用的方法在一定程度上实现了应用程序包跨 J2EE 容器实施 JAX-RPC 和 JSR 109 的可移植性(如 J2EE 1.4 中所定义的那样)。该应用程序现在可以部署到任意适应 JSR 109 的容器上,并且可以使用任意 Web 服务客户端来调用它。
现在让我们看看如何使用一个非会话状态会话 Bean (SLSB) 来实施 Web 服务。
看一下 [demo root]./ejb,它显示了一个作为 Web 服务的 EJB 2.1 非会话状态会话 Bean。从命令行使用以下命令为其执行一次清除和汇编操作(类似为非会话状态 Java 类所做的操作):
%ant clean
%ant gen-service (在这种情况下,编译作为 gen-service 任务的一部分来完成)
在 ./dist/helloService-ejb.ear ÖлñÈ¡¿É²¿ÊðµÄÓ¦ÓóÌÐò
使用以下命令解压缩 helloService-ejb.jar:
jar xvf helloService-ejb.jar
观察它的包结构。您将看到如图 5 所示的目录结构。
/META-INF/ /webservices.xml wsdl/HelloServiceEJB.wsdl /ejb-jar.xml /HelloServiceEJB-java-wsdl-mapping.xml /oracle-webservices.xml /Service Endpoint Interface class + EJB classes |
然后看看它的 [unjar dir]/META-INF/webservices.xml(如图 6 所示)。
<webservices> <webservice-description> <webservice-description-name>HelloServiceEJB </webservice-description-name> <wsdl-file>META-INF/wsdl/HelloServiceEJB.wsdl</wsdl-file> <jaxrpc-mapping-file>META-INF/HelloServiceEJB-java-wsdl-mapping.xml</jaxrpc-mapping-file> <port-component> <port-component-name>HelloServiceInfPort </port-component-name> <wsdl-port xmlns:wsa0="http://oracle.j2ee.ws/ejb/Hello" >wsa0:HelloServiceInfPort</wsdl-port> <service-endpoint-interface>oracle.demo.ejb.HelloServiceInf</service-endpoint-interface> <service-impl-bean> <ejb-link>HelloServiceEJB</ejb-link> </service-impl-bean> </port-component> </webservice-description> </webservices> |
您可以注意到这个 webservices.xml 大体上类似于一个 Java 类的 webservices.xml,除了 <service-impl-bean> 中有一个 <ejb-link> 元素,这表明该实施是一个 EJB。
让我们看看它和图 7 中的 /META-INF/ejb-jar.xml 有什么样的关系。
<ejb-jar> <display-name>HelloServiceEjb</display-name> <enterprise-beans> <session> <display-name>HelloServiceEJB</display-name> <ejb-name>HelloServiceEJB</ejb-name> <service-endpoint>oracle.demo.ejb.HelloServiceInf</service-endpoint> <ejb-class>oracle.demo.ejb.HelloServiceBean</ejb-class> <session-type>Stateless</session-type> </session> </enterprise-beans> </ejb-jar> |
请注意 webservices.xml 中的 <ejb-link> 被用来与 ejb-jar.xml 中的 <ejb-name> 相关联。此外,ejb-jar.xml 有一个新的项目 <service-endpoint> 作为 J2EE 1.4 中的 EJB 2.1 的一部份,它拥有完全合格的服务终端接口名称。ejb <ejb-class> 应当具有在服务终端接口类中定义的方法的实施。
注:服务实施类不需要(通过一个 Java 实施关键字)显式地实施服务终端接口。这考虑到了服务终端接口实施的灵活性,因为我们可以使用一个现有的类,并通过创建一个服务终端接口类来影响我们希望影响的商务方法。除那些由服务终端接口定义的方法之外,服务实施类可以实施其它的方法,但只有服务终端接口方法可以影响到客户端。
非会话状态会话 Bean 和 Java 类实施之间的共同特性如下:
- 两种服务实施类型都使用了一个服务终端接口 [3] 来定义 Java Web 服务的方法签名
- 打包的 WSDL 文件(在 webservices.xml 中由 <wsdl-file> 引用)由容器发布为被实施服务的 WSDL
- 服务实施没有假定用什么类型的客户端来调用服务,只要客户端能够理解 WSDL 并能够使用 SOAP 进行通信即可
- 使用了一个映射文件 [4] 将服务终端接口映射到 WSDL — 这些规则在 Java — WSDL 映射规则中定义
- 两种实施都支持通过 HTTP 传输的 SOAP 为默认的通信协议
开发人员将遇到的一个明显问题是:什么时候应该在 J2EE1.4 中用非会话状态会话 Bean 来实施 Web 服务,而不是使用 Java 类?
如果您遇到以下情况,请使用非会话状态会话 Bean 来影响 Web 服务:
- 需要使先前已存在的非会话状态会话 Bean 成为 Web 服务
- 需要声明性的事务管理
- 需要 EJB 容器提供的线程管理
- 需要基于角色的安全性
如果您遇到以下情况,请使用 Java 类来影响您的 Web 服务
- 需要使先前已存在的 Java 类成为 Web 服务
- 想要一个轻型系统,而不太关心 EJB 容器提供的事务处理能力
Web 服务客户端编程模型
这个客户端编程模型提供了在 J2EE 环境中 Web 服务的一个客户端视图。它使开发人员避免了运行时的细节(如 SOAP 和 Java 之间的协议绑定、传输和编组/反编组)。

图 8:客户端 Web 服务体系结构
存在四种类型的 J2EE 客户端:
- EJB 2.1
- Servlet 2.4
- JSP
- 应用程序客户端
通过在以下文件中提供一组简单的部署描述符 (DD),上述 J2EE 组件的任意一个都可用来调用远程 Web 服务:
- META-INF/ejb-jar.xml -----------------ejb
- WEB-INF/web.xml ---------------------- servlet 和 jsp
- MET-INF/application-client.xml ------- 应用程序客户端
这里引用的 DD是 '<service-ref>'。点击链接 [6] 来获取同样的 service-ref 完整模式。
查看 [demo root]/demo/web-client/,它显示了一个使用 Web 服务的 Servlet,从命令行执行以下命令来配置客户端:
%ant clean
%ant assemble
已汇编的应用程序放在 ./dist 中。解压缩 consumerWeb-web.war,并特别查看其内部结构:
jar xvf consumerWeb-web.war
consumerWeb-web.war 的包结构类似于图 9 。
WEB-INF/ /wsdl/HelloService.wsdl /mapping.xml /web.xml /classes/ /SEI 和 servlet 类 |
我们特别感兴趣的是 web.xml。图 10 提供了 web.xml 的一个摘录,它显示了 service-ref 部分,和我们感兴趣的一些项目。
<web-app> |
图 11 所示为执行 Web 服务查找的 Servlet (或 JSP)代码。
public String consumeService (String name) { ...... Context ic = new InitialContext(); Service service = (Service)ic.lookup("java:comp/env/service/MyHelloServiceRef"); // declare the qualified name of the port, as specified in the wsdl QName portQName= new QName("http://hello.demo.oracle/","HelloInterfacePort"); //get a handle on that port : Service.getPort(portQName,SEI class) HelloInterface helloPort = (HelloInterface) service.getPort(portQName,oracle.demo.hello.HelloInterface.class); //invoke the operation : sayHello() resultFromService = helloPort.sayHello(name); ..... } |
上面的代码段摘自 [demo root]/web-client/src/web/oracle/ServiceConsumerServlet.java
客户端(在这种情况下为 Servlet)利用了 JNDI 查找来获取 javax.xml.rpc.Service 的一个实例。它利用了 ‘Service' 对象来获取 Web 服务 “端口”的句柄,这些是在 WSDL 中定义的代表服务的端口。
获得的端口可以转换成一个本地打包的服务终端接口 (oracle.demo.hello.HelloInterface),以及在其上调用的可用方法。本地打包的服务终端接口可以利用一个工具从 WSDL 中生成,或者可以是已经提供的,并利用一个 JAX-RPC 映射文件将其映射至 WSDL。
客户端也可以使用这个 'Service' 对象来获得 'Call' 对象的一个句柄,并进行一次 DII Web 服务调用。所有这些 API 都在公共 API 的 J2EE1.4 集合 (http://java.sun.com/j2ee/1.4/docs/api) 中定义,并且都来自 JAX-RPC 规范。
这里要注意的重要的一点是,客户端始终通过 JNDI 查找 — 也就是,通过容器 — 来间接访问服务实施,而从不将直接引用传递给 Web 服务实施。这样,容器可以介入并为客户端提供服务(日志、安全性、管理)。
此外,J2EE Web 服务客户端不关心端口的工作方式,只关心端口定义的方法。
查看 [demo-root]/ejb-client 和 [demo-root]/app-client 演示.
注:ejb-jar.xml 或 application-client.xml 中类似的 <service-ref> 标记将允许一个 EJB 或应用程序客户端查找或调用远程 Web 服务。
注意您使用了一些简单的部署描述符项目来转换一个 EJB、JSP 或 Servlet,从而能够调用远程 Web 服务。
Web 服务处理器
J2EE1.4 还对 JAX-RPC 处理器的支持进行了标准化。处理器类似于 servlet 过滤器。JAX-RPC 处理器为 Web 服务终端(客户端和服务器)提供了额外的消息处理工具。典型地,我们可以使用处理器来访问原始的 SOAP 消息,从而管理加密/解密,执行日志记录、消息审计和其它的管理操作。
处理器适合于服务器编程模型和客户端编程模型(如图 11 所示)。

图 11:Web 服务处理器图示
目前的 Oracle JAX-RPC 运行时系统仅定义了 SOAP 消息处理器。将来,它将被扩展来处理除 SOAP 之外的其它协议。
要了解如何利用处理器来处理 SOAP 标题,请查看位于 [demo_root]/demo/header 下的演示,其中使用了客户端和服务器端处理器。
查看 [demo-root]/header/dist/creditService.jar,了解如何利用 SOAP 标题在 META-INF/webservices.xml 中设置单个服务器端处理器来执行基本的验证(如图 12 所示)。
<webservices> <webservice-description> .......... <port-component> ......... <handler> <handler-name>SHandler1</handler-name> <handler-class>oracle.demo.header.AuthenticateHandler< /handler-class> <init-param> <param-name>password</param-name> <param-value>TIGER</param-value> </init-param> <init-param> <param-name>id</param-name> <param-value>SCOTT</param-value> </init-param> <soap-header xmlns:wsa1="http://oracle.j2ee.ws/ejb/Credit">wsa1:authHeader1</soap-header> </handler> </port-component> </webservice-description> </webservices> |
处理器通过 <handler> 元素来定义,<handler-class> 元素指定完全合格的处理器类名称。此外,在服务器编程模型中,可以利用初始化参数 < init-param >、SOAP 标题 < soap-header > 和 SOAP 角色来配置处理器。
查看(由 header/src/service/oracle/demo/header/AuthenticateHandler.java 中的 <handler-class> 指定的)处理器的源代码,以了解如何在处理器实施中检索初始化参数。服务器端处理器结构在 webservices.xml 模式 [5] 中定义。
可以安排多个处理器以指定的顺序运行,例如,一系列的处理器或“处理器链”。当我们想在安全策略 ‘B’ 之前运行安全策略 ‘A’ (诸如此类)时,这非常有用。
图 13 提供了一个在 webservices.xml 中定义一个“服务器端”处理器链的示例。您可以在 [demo-root]/handlerchain/ 中找到它。
<webservices> <webservice-description> ..... <port-component> ...... <handler> <handler-name>Account Logger</handler-name> <handler-class>oracle.demo.xx.AccountTransactionHandler </handler-class> </handler> <handler> <handler-name>New Account Handler</handler-name> <handler-class>oracle.demo.xx.NewAccountHandler </handler-class> </handler> <handler> <handler-name>Withdrawal Handler</handler-name> <handler-class>oracle.demo.xx.AccountWithdrawalHandler< /handler-class> </handler> </port-component> </webservice-description> </webservices> |
注意定义了三个处理器以图 14 所示的顺序运行。
图 14:处理器链示例的处理器运行顺序
此外,请注意服务器端处理器链通过 <port-component> 标记与 Web 服务的一个特定的“端口”联系在一起。
可以类似地在 J2EE1.4 客户端(例如 web.xml、ejb-jar.xml 或 application-client.xml)的 <service-ref> 标记中将客户端处理器进行安排。
在图 15 中,摘录了带有客户端处理器链的一个 <service-ref>。
<service-ref> <service-ref-name>service/MyHelloServiceRef</service-ref-name> <service-interface>javax.xml.rpc.Service</service-interface> .... <port-component-ref> .... </port-component-ref> <handler> <handler-name>First Handler</handler-name> <handler-class >oracle.xx.AccountTransactionHandler</handler-class> <port-name>portA</port-name> </handler> <handler> <handler-name>Second Handler </handler-name> <handler-class>oracle.xx.NewAccountHandler< /handler-class> <port-name>portB</port-name> </handler> </service-ref> |
注:与服务器端处理器不同,客户端处理器与 <service-ref> (服务引用)而不是与 <port-component> (端口组件引用)联系在一起。不过,它们有一个可配置的参数,<port-name>,处理器可以利用它与被调用的服务端口联系在一起。利用端口名称,您可以限定哪个处理器在服务终端(WSDL 端口)被调用时运行。
在上述情况下,处理器 'First Handler' 只在 'PortA' 被调用时运行,而 'Second Handler' 只在 'PortB' 被调用时运行。
这些处理器实施规则是 JAX-RPC 规范的一部分。
结论
本文试图使您轻松地了解 JSR-109 如何使 J2EE 环境中的 Web 服务和 Web 服务客户端的部署标准化。Oracle Application Server 10g 在 Oracle JDeveloper 中提供了一组工具(包括命令行和 GUI ),有助于对遵循 J2EE1.4 规范的 Web 服务应用程序进行汇编。J2EE 容器利用诸如消息日志和已部署应用程序管理之类的工具提供了一个稳定的运行时。
资源-
具有互操作性的 Web 服务
Web 服务互操作性是通过遵循如 WS-I www.ws-i.org、Soap Builders 之类的标准来实现的。 -
服务终端接口 (SEI)
指的是 JAX-RPC 服务终端接口。它是 WSDL 的一种 Java 表示,它必须遵循的一系列规则包括:- 它必须扩展 java.rmi.Remote 接口
- 它的所有方法必须产生一个 java.rmi.RemoteException
- 方法参数和返回类型必须是 JAX-RPC 支持的类型
- 它必须不包含任何常量(最终的静态声明)
- 服务终端实施
JSR 109 要求服务终端实施类可以显式或不显示地实施服务终端接口,但它必须定义在服务终端接口中声明的所有方法。
- 映射文件
映射文件使 Java-WSDL 的映射标准化。部署描述符没有标准的文件名。它的名称由 webservices.xml 中和客户端的 <service-ref> 标记内的 jaxrpc-mapping-file 元素确定。在大多数情况下,它必须是一个简单的服务终端接口 package-name 到 WSDL namespace-uri 的映射。只有在不满足默认的映射规则(JSP 109 的一部分)时,我们才必须提供一个完整的映射。
http://www-124.ibm.com/developerworks/opensource/jsr109/xsd/j2ee_jaxrpc_mapping_1_1.xsd
- webservices.xml
J2EE1.4 中引入的新的部署描述符,它被用来获取所有与 Web 服务实施相关的信息。这种新的标记位于 ejb-jar.xml、web.xml 或 application-client.xml 中,并向容器说明 J2EE 代码希望通过 JNDI 查找来获取这些资源。webservices.xml 的模式可以在此处找到:
http://java.sun.com/xml/ns/j2ee/j2ee_web_services_1_1.xsd
- service ref
J2EE1.4 中引入的新的部署描述符标记,它被用来获取所有与 Web 服务客户端相关的信息。service ref 的模式在此处提供:
http://java.sun.com/xml/ns/j2ee/j2ee_web_services_1_1.xsd
- JAX-RPC 处理器
处理器实施规则在 JAX-RPC 规范中定义。简而言之,一个处理器类必须实施 'javax.xml.rpc.handler.Handler' 接口,或者扩展 'javax.xml.rpc.handler.GenericHandler'。了解在本文引用的处理器演示中的处理器的使用。
- DII —动态调用 API
它是 JAX-RPC 规范的一部分,其中客户端部分通过一个动态代理(在运行时创建的一个类)调用一个远程过程。关于更多详情,请仔细查看 JAX-RPC 规范。
- 汇编
工具 wsa.jar 用来执行汇编操作。这个工具接收一个文件 service-config.xml,这个文件为它提供了汇编一个 Web 服务应用程序所需的全部信息。这种类型的汇编 — 从 Java 类开始,并将它们影响为 Web 服务 — 被称为从下至上的汇编,与之对应的是从上至下的汇编,其中实施纲要是从 WSDL 中生成的。
- WSDL
http://www.w3.org/TR/wsdl
- SOAP
http://www.w3.org/TR/soap12-part1/
- 本文中引用的所有演示都可以以 Zip 文件的形式从此处下载:
http://otn.oracle.com/tech/java/oc4j/1003/how_to/webservices_demos.zip
作者: Anirban Chatterjee
anirban.chatterjee@oracle.com
版权所有 2003,Oracle Corporation。保留所有权利。