级别: 中级 Ivan Dubrov (dubrov@isg.axmor.com), 系统架构师, Axmor Artem Papkov (artem@us.ibm.com), 解决方案架构师, IBM James Smith (jamessmi@us.ibm.com), 经理和管理顾问, IBM
2005 年 12 月 05 日 在最近三年中,Web 服务浪潮席卷全世界。现在,无论您的企业应用程序是部署在小型家庭商店中,还是跨多个领域的企业基础设施的一部分,无论应用程序是否与他人协作,它都必须支持 Web 服务。在本文中,IBM Advanced Technology Solutions 团队将全面概述 Apache Geronimo 应用服务器的 Web 服务特性,并演示如何使用这些特性来创建支持 Web 服务的企业应用程序。然后使用一个虚拟银行例子来解释如何在 Geronimo 应用程序中实现和部署 Web 服务。
途径
现代企业往往是非常复杂的系统,每天要面对许多机会和挑战。许多机会涉及到与其他系统和企业进行集成或协作。因此,明智的企业应用程序开发人员要确保别的应用程序可以轻松地访问自己的应用程序。Web 服务就是提供这种可访问性的好方式。
Apache Geronimo 应用服务器支持 Web 服务以及最新 Java™ 2 Platform, Enterprise Edition(J2EE)规范的其他特性。为了演示 Geronimo 应用服务器中的 Web 服务支持,IBM Advanced Technology Solutions 团队已经决定改进一个软件模拟程序,这个程序模拟了简单的银行场景,是为以前的一篇 developerWorks 文章 “使用 Geronimo 构建安全的企业基础设施”(developerWorks,2005 年 7 月)开发的。
本文使用的业务场景实现一个资金转帐用例。零售银行客户(用户)可以访问这个应用程序,将资金从自己的帐户转出来,审计员可以监督所有银行事务。现有的资金转帐应用程序得到了改进,允许 Web 服务使用 Simple Object Access Protocol/Hypertext Transfer Protocol(SOAP/HTTP)和简单的 Web 服务客户机来提供后端功能。
什么是 Web 服务?
Web 服务是一段应用程序业务逻辑,可以使用普遍存在的 Web 协议和数据格式(比如 HTTP 和 SOAP)来执行。自从 J2EE 1.4 发布以来,Web 服务已经融入了 J2EE 中。在 J2EE 上下文中,Web 服务被认为是后端实现的外观(facade) —— 后端实现可能是 Enterprise JavaBean(EJB)或 servlet。下面的工件组成了 Web 服务:
- 一个 Web Service Definition Language(Web 服务定义语言,WSDL)文档,描述服务接口和端口(参见 参考资料 中的链接)。
- 一个服务端点接口,它由服务器部分实现,用于在客户机上调用服务方法。来自 WSDL 的绑定映射到这个端点接口,WSDL 绑定是 WSDL 的一部分,定义了 Web 服务的协议。
- Java API for XML-based RPC(JAX-RPC)描述符,包含从 XML 到 Java 技术的不同映射,比如将来自 WSDL 文档的 XML Schema Definition(XSD)类型映射到 Java 类型,以及将 XML 元素映射到端点接口方法参数。
- 服务实现 —— EJB 或 servlet。
- Web 服务部署描述符 webservices.xml。
以下几节开发这些工件,为 Geronimo 构建一个支持 Web 服务的应用程序。
创建 Web 服务
首先,开发 Web 服务本身。为此,必须在同一个文件夹中创建以下工件。(稍后给出每个步骤的细节。)
- 生成或开发 WSDL。在这个例子中,使用来自 Sun J2EE 参考实现的 wscompile 工具(参见 参考资料 中关于 wscompile 工具的更多信息)。
- 开发端点接口。它是基于 EJB 远程接口开发的。
- 用 JAX-RPC 映射将 WSDL 类型映射为 Java 类型。JAX-RPC 映射是使用 wscompile 工具生成的。
- 实现端点接口。
- 在部署描述符中配置 Web 服务引用。这包括在 web.xml 部署描述符中添加一节。
下面详细描述每个步骤。
第一个步骤是在 WSDL 文件中描述 Web 服务。在这个例子中,因为 Web 服务提供的功能与业务逻辑 EJB 相同,所以可以从 EJB 接口生成 WSDL,然后进行手工编辑。[注意,到编写本文时,Geronimo 对于构建 J2EE Web 服务还没有完善的工具支持(Axis 特定的 Web 服务例外,这种 Web 服务可以使用 Axis 工具生成]。
要从端点接口生成 WSDL,必须先开发一个接口。这个接口声明 Web 服务实现的方法。JAX-RPC 规范对这个接口有一些限制。简单地说,这个接口应该扩展 java.rmi.Remote —— 所有方法都声明为抛出 java.rmi.RemoteException。最后,参数和返回值是 JAX-RPC 兼容的(在我们的例子中,它们是具有默认构造函数的 JavaBean 和原始类型)。简单地说,在示例应用程序中,这个接口是 EJB 的远程接口,但是用 java.rmi.Remote 替换了父接口 javax.ejb.EJBObject,并删除了调用者名称参数。
在实现 Web 服务的端点接口之后,就可以生成 WSDL了。下一节描述这个过程。
从端点接口生成 WSDL
当前,Geronimo 应用服务器没有提供生成 WSDL 文档的标准工具集。但是,Sun 站点上的 J2EE 1.4 工具(参见 参考资料 中的链接)可以满足我们的需要。用于生成各种 Web 服务相关工件的工具称为 wscompile,位于 Sun J2EE 发行版的 bin 子目录中。
要生成 WSDL 文档,应该为 wscompile 创建一个配置文件。清单 1 显示了这个配置文件:
清单 1. wscompile 工具的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
<service name="BusinessLogic"
targetNamespace="http://ibm.com/samples/workdev/v1/wsdl"
typeNamespace="http://ibm.com/samples/workdev/v1/types"
packageName="com.ibm.workdev.v1.interfaces">
<interface name="com.ibm.workdev.v1.interfaces.BusinessLogicEndpoint"/>
</service>
</configuration>
|
可以看出这个文件的结构很简单。它包含名称空间、包和服务端点接口引用的声明。清单 2 显示了 wscompile 的调用字符串:
清单 2. 调用 wscompile
%SUN_J2EE_HOME%\bin\wscompile -classpath <APP_CLASSES>;%GERONIMO_HOME%\repository\geronimo-
spec\jars\geronimo-spec-j2ee-1.4-rc4.jar -gen:server -f:documentliteral config.xml
|
清单 2 中的 SUN_J2EE_HOME 是 Sun J2EE SDK 的主目录。APP_CLASSES 是指定应用程序类(包括端点接口类)的目录。GERONIMO_HOME 是 Geronimo 的主目录。注意,这个工具不只生成 WSDL,还生成几个支持类。
下一步是更新生成的 WSDL,使元素的名称在业务上下文中更容易理解。wscompile 没有保留方法参数名,而是将它们转换为 long_2(参数类型加参数位置)这样的名称,这将 for? 声明为方法的第二个参数,类型为 long。例如,performTransfer 操作的第二个参数可以重新命名为 amount。
接下来,确保所有消息都正好有一个部分。对于没有返回值的方法,wscompile 生成没有部分的消息。这个错误需要纠正。为了纠正这个错误,必须声明一个包含空序列的 XML 元素,并用这个元素将一个部分添加到消息中。清单 3 演示了如何声明这样的 XML 元素:
清单 3. XSD 响应类型
<element name="performTransferResponse">
<complexType>
<sequence/>
</complexType>
</element>
|
下面是来自消息声明的片段:
清单 4. WSDL 消息
<message name="BusinessLogicEndpoint_performTransferResponse">
<part name="result" element="ns2:performTransferResponse"/>
</message>
|
另外一种办法是从头开发 WSDL 文档。注意,Eclipse WTP 项目(参见 参考资料 中的链接)包含一个方便的 WSDL 文档编辑器。
创建 JAX-RPC 映射
在生成 WSDL 之后,需要为方法参数创建 JAX-RPC 映射文件和值类,然后再创建返回值。它们可以用前面提到的 wscompile 工具生成。
这种情况的 wscompile 配置见下面的 清单 5:
清单 5. wscompile 的配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
<wsdl
location="file:///BusinessLogic.wsdl"
packageName="com.ibm.workdev.v1.interfaces"/>
</configuration>
|
调用行如下:
清单 6. 调用 wscompile
%SUN_J2EE_HOME%\bin\wscompile -s src-gen -keep -mapping
mapping.xml -classpath <SAMECLASSPATH> -gen:client -f:documentliteral
config.xml
|
这个工具的类路径与生成 WSDL 时一样。src-gen 是应该放置 JAX-RPC bean 源类的目录。注意,需要在生成 WSDL 之前创建这个目录。最好将它与应用程序源目录分开,因为可能已经存在一些值类,wscompile 会覆盖它们。
并不是生成的所有文件都是必要的 —— 只需要缺失的 bean 的源代码。例如,在这个应用程序中,不需要 Account JavaBean 的源代码,因为它已经存在了。针对不同序列化器的其他源代码也是不需要的,因为它们是 Sun J2EE 实现所特有的。
wscompile 生成的 JAX-RPC 需要一些后期手工处理。首先,所有 wsdl-message-part-name 元素的值为 parameters。必须将它们重新命名以匹配来自部分元素序列的对应元素名(封装的文档/字面格式的每个部分有具有复杂类型的元素,等于元素序列)。
例如,清单 7 显示 XSD 类型:
清单 7. 显示 XSD 类型的 WSDL 片段
<complexType name="registerUser">
<sequence>
<element name="username" type="string" nillable="true"/>
<element name="password" type="string" nillable="true"/>
</sequence>
</complexType>
|
清单 8 显示消息部分中方法参数的映射:
清单 8. 方法参数的 JAX-RPC 映射
<method-param-parts-mapping>
<param-position>0</param-position>
<param-type>java.lang.String</param-type>
<wsdl-message-mapping>
<wsdl-message xmlns:wsdlMsgNS="http://ibm.com/samples/workdev/v1/wsdl">
wsdlMsgNS:BusinessLogicEndpoint_registerUser
</wsdl-message>
<wsdl-message-part-name>username</wsdl-message-part-name>
<parameter-mode>IN</parameter-mode>
</wsdl-message-mapping>
</method-param-parts-mapping>
|
注意,文档/包装样式中的 wsdl-message-part-name 提供了元素名,而不是消息部分本身的名称。
另一个问题是,wscompile 为返回类型是 void 的方法的返回值生成 JAX-RPC 映射(在 wsdl-return-value-mapping 元素中描述)。这些 wsdl-return-value-mapping 元素应该删除(否则在部署期间会抛出 NullPointerException)。正确的行为在 Web services for J2EE, Version 1.0 规范中还不明确(单向方式在这里不适合,因为调用可能失败,所以结果对于客户机很重要)。
部署 Web 服务
在下一步中,应该开发 webservices.xml 部署描述符。它将 WSDL、JAX-RPC 映射、处理程序和后端实现 bean 结合在一起。
为了演示 Web 服务处理程序的使用,我们实现了 WS-Security 的一部分功能。因为处理程序在方法级授权之后执行,所以处理程序不能直接用于身份验证(例如,这个业务逻辑 bean 无法公开为通过定制的处理程序执行身份验证的 Web 服务)。所以,在这个例子中,Web 服务代表外观,后端 bean 不要求授权。在执行时,这个 bean 获得安全处理程序捕获的凭证,执行手工身份验证,并以经过身份验证的客户机的身份调用 BusinessLogic EJB 方法。
服务器端处理程序在 webservices.xml 中指定。这个处理程序是一个实现 javax.xml.rpc.handler.Handler 接口的类。这个处理程序类的实例用于过滤所有到达的请求、发出的响应和错误。
客户端处理程序在 web.xml 部署描述符中的服务引用中指定。
服务器处理程序 WSSecurityServerHandler 被开发成示例应用程序的一部分,它从到达的 SOAP 请求中提取用户名和密码,并将它们放到线程本地变量中。这是目前将数据传输到 Web 服务后端 servlet 的惟一一种可靠的方式。Web 服务后端 servlet 使用这些凭证进行身份验证,并使用 Geronimo 特定的身份验证代码以经过身份验证的主体的身份调用 BusinessLogic EJB [因为只使用 Java Authentication and Authorization Service(JAAS)身份验证是不够的]。
清单 9 中的片段展示如何从安全处理程序接收主体的凭证,并以这个主体的身份调用 EJB 方法:
清单 9. 作为某个安全主体调用业务逻辑
// username and password are obtained from the security handler
LoginContext ctx = createLoginContext(username, password);
ctx.login();
try {
Set set = ctx.getSubject().getPrincipals(
org.apache.geronimo.security.IdentificationPrincipal.class);
IdentificationPrincipal idp = (IdentificationPrincipal) set.iterator().next();
Subject subject =
org.apache.geronimo.security.ContextManager.getRegisteredSubject(idp.getId());
org.apache.geronimo.security.ContextManager.setCurrentCaller(subject);
return (Order[])Subject.doAs(subject, new PrivilegedExceptionAction() {
public Object run() throws Exception {
BusinessLogic logic = BusinessLogicUtil.getHome().create();
return logic.getOrders();
}
});
} finally {
ctx.logout();
}
|
这个片段不能转移到处理程序代码中,因为业务方法(在这个例子中是 getOrders)应该以另一个主体的身份运行(doAs 调用),而处理程序在调用服务方法之前执行。
清单 10 展示了为这个应用程序开发的示例 Web 服务部署描述符:
清单 10. Web 服务部署描述符
<?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>BusinessLogic</webservice-description-name>
<wsdl-file>WEB-INF/BusinessLogic.wsdl</wsdl-file>
<jaxrpc-mapping-file>WEB-INF/jaxrpc-mapping.xml</jaxrpc-mapping-file>
<port-component>
<port-component-name>BusinessLogicEndpointPort</port-component-name>
<wsdl-port xmlns:ns="http://ibm.com/samples/workdev/v1/wsdl">
ns:BusinessLogicEndpointPort
</wsdl-port>
<service-endpoint-interface>
com.ibm.workdev.v1.interfaces.BusinessLogicEndpoint
</service-endpoint-interface>
<service-impl-bean>
<servlet-link>BusinessLogicServiceServlet</servlet-link>
</service-impl-bean>
<handler>
<handler-name>ServerSecurityHandler</handler-name>
<handler-class>
com.ibm.workdev.v1.web.wssec.WSSecurityServerHandler
</handler-class>
<soap-header xmlns:wssec="http://schemas.xmlsoap.org/ws/2003/06/secext">
wssec:Security
</soap-header>
</handler>
</port-component>
</webservice-description>
</webservices>
|

 |

|
部署 Web 服务客户机存根
部署 Web 服务客户机存根很简单明了。执行以下步骤来准备所需的部署描述符和代码:
- 通过 URL http://localhost:8080/workdev/service?WSDL 获得 WSDL。
- 开发端点接口。
- 用 JAX-RPC 映射将 WSDL 类型映射到 Java 类型。
- 在部署描述符中配置 Web 服务引用。
第 2 步和第 3 步已经在上面描述过了。它们几乎与开发 Web 服务本身时的过程一样。差异是在第 3 步中为 wscompile 工具设置 gen:client 选项,而不是 gen:server(这个选项让 wscompile 工具生成服务接口和其他工件)。实际上,JAX-RPC 映射和 bean 可以取自 Web 服务实现。只需要一个额外的工件,即服务接口(它扩展 javax.xml.rpc.Service)。
服务引用放在 web.xml 中(注意,web.xml 的规范版本应该是 2.4)。清单 11 所示的片段是取自 Funds Transfer 应用程序的例子:
清单 11. 在 web.xml 部署描述符中引用 Web 服务
<service-ref>
<service-ref-name>service/BusinessLogicService</service-ref-name>
<service-interface>com.ibm.workdev.v1.web.BusinessLogicService</service-interface>
<wsdl-file>WEB-INF/BusinessLogic.wsdl</wsdl-file>
<jaxrpc-mapping-file>WEB-INF/jaxrpc-mapping.xml</jaxrpc-mapping-file>
<service-qname xmlns:servicens="http://ibm.com/samples/workdev/v1/wsdl">
servicens:BusinessLogic
</service-qname>
<handler>
<handler-name>ClientSecurityHandler</handler-name>
<handler-class>com.ibm.workdev.v1.web.wssec.WSSecurityClientHandler</handler-class>
<soap-header xmlns:wssec="http://schemas.xmlsoap.org/ws/2003/06/secext">
wssec:Security
</soap-header>
</handler>
</service-ref>
|
现在已经开发了 Web 服务部署描述符,可以通过 Java Naming and Directory Interface(JNDI)获取 Web 服务实例了。清单 12 给出一个例子:
清单 12. 调用 Web 服务
InitialContext ctx = new InitialContext();
BusinessLogicService service =
(BusinessLogicService) ctx.lookup("java:comp/env/service/BusinessLogicService");
BusinessLogicEndpoint endpoint = service.getBusinessLogicEndpointPort();
// Invoke methods on the endpoint interface...
|
在 Funds Transfer 应用程序中,有一个调度器 servlet(WebServiceClientServlet),它与 JavaServer Pages™(JSP)一起提供一个调用 Web 服务方法的示例接口。
为什么不使用 Eclipse WTP?
到撰写本文时,Eclipse Foundation 已经发布了 WebTools Project 0.7,这个版本只为 Apache Geronimo 和 Web 服务提供了最低限度的支持。尽管这个工具很有发展潜力,但是它目前缺少一些重要的特性。
缺少的一种特性是将 Enterprise Application ARchive(EAR)文件部署到 Geronimo 的能力。WTP 0.7 只能部署模块,比如 Web、EJB 等等。不幸的是,本文描述的应用程序需要这种功能,因为 J2EE 应用程序部署描述符(EAR 描述符)包含重要的信息。部署所有其他模块需要这些信息,比如数据库配置、Java Messaging Services(JMS)配置和安全配置。因此,WTP 工具 Web 服务生成向导无法正确地生成服务,本文中这个应用程序的开发无法利用这种向导。
在 Geronimo 早期版本中遇到的另一个问题在 WTP 中也存在 —— 向导对于 Web 服务只支持 Axis 运行时。这导致创建与 J2EE 不兼容的 Web 服务,并在项目源代码中生成许多文件。另外,这些向导不支持为服务和 Web 服务客户机指定 SOAP 处理程序。这导致手工编辑处理程序,这很费事儿。
开发 Web 服务及其客户机时,更好的办法是更充分地支持 JSR 109 规范(参见 参考资料)。当前,WTP 还没有完整地支持这个规范,因此限制了 Web 服务、Web 服务客户机及其处理程序在 Project Explorer 树中的表现;所以只能查看这些信息,不能编辑。如果 WTP 提供丰富的编辑器和 Web 服务部署描述符(比如 webservices.xml、webservicesclient.xml 和 JAX RPC 映射描述符),也可能会有帮助。
结束语
本文描述了如何使用 Geronimo 应用服务器提供的 Web 服务功能来构建支持 Web 服务的应用程序。它说明了这种应用服务器依赖于 J2EE 1.4 规范,为构建 Web 服务及其客户机提供了许多功能。
值得注意的是,尽管到撰写本文时 Geronimo 应用服务器还没有提供正式发布的开发工具集,但是 Eclipse WTP 项目已经开始开发这些工具了。
总之,这种应用服务器已经展现出对 J2EE 标准健壮的支持,以后一定会在中小型企业 IT 项目中占有一席之地。
参考资料 学习
获得产品和技术
讨论
作者简介  | |  | Ivan Dubrov 是俄罗斯 Novosibirsk 的 IBM Advanced Technology Solutions(ATS)Lab 的软件工程师。他拥有 Novosibirsk 州立大学的计算机科学学士学位(优等生)。在最近一年中,他作为系统架构师和软件工程师参与了超过 5 个 ATS 项目。 |
 | |  | Artem Papkov 当前是 IBM Client Innovation Team 的解决方案架构师,帮助客户和合作伙伴掌握新出现的技术,比如 SOA 和 Web 服务。在 1998 年从 Belarusian 州立信息和无线电电子大学毕业,获得计算机科学硕士学位之后,他于 2000 年加入了 IBM 的北卡罗莱那 Research Triangle Park。他的经历包括使用新出现的技术进行多层解决方案的软件开发、体系结构设计以及与基于互联网的解决方案进行集成。在过去三年中,他主要帮助客户采用 Web 服务作为 IBM 的战略性集成技术,采用 SOA 作为集成方式。 |
 | |  | James Smith 在软件开发方面有超过 18 年的经验。他的职业生涯开始于加利福尼亚 Livermore 的 Sandia National Labs,他在那里使用大量现有的遗留代码设计高速数据获取系统和分布式计算系统。由于在 Java 技术和与客户交流方面具有丰富的经验,Jim 转到了 Emerging Internet Technologies 团队,主要关注帮助 IBM 客户采用 Java 解决方案。Jim 是 Advanced Technology Solutions(ATS)的创建人之一,这是一个全球软件服务和开发组织。它的使命是为 IBM、开发实验室、业务伙伴及客户开发、精化和推动高级技术和轻量级业务过程,帮助他们更快地采用和部署标准技术和 IBM 产品。当前 Jim 管理着这个组织。 |
对本文的评价
|