IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  SOA and Web services  >

使用 Rational Application Developer V6 创建协同 Web 服务

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

样例代码


级别: 初级

David Schmidt (schmidtd@us.ibm.com), 软件工程师兼顾问, IBM

2005 年 5 月 01 日

开发相互调用的 Web 服务与开发一个独立的应用来调用 Web 服务是类似的。但是,它们在开发工具中有很大的不同,例如采用 IBM® WebSphere®系列中的 Rational® Application Developer v6.0 版本软件。本文将讨论设计、开发调用其它 Web 服务的 Web 服务。我们将依次创建、部署、调用这些 Web 服务。同时,我们将使用 IBM alphaWorks 提供的 Web Services Navigator 来可视化它们在 WebSphere 环境中的调用流程。

调用所有 Web 服务

从任何 Java ™ 程序中调用 Web 服务都是相同的,不论是在 Java bean 中,还是一个独立的 Java 应用程序,或者是一个其它的 Web 服务。 你与 Web 服务绑定需要使用同一个办法:创建一个静态的客户机/存根或是通过 UDDI 绑定一个动态的客户机。 为了简化讨论与最终的实现,我们将讲解静态存根的生成方式。当处于接口一旦定义就不被再次修改的环境下,或是处于控制 Web 服务交互过程中,使用静态存根是非常合适的。 针对客户机程序的选择,更进一步的讨论请参考 WebSphere 6 版本 Web 服务开发与部署手册 中的第 11 章。同时在本文 参考资料 中也有说明。

本文讨论的 Web 服务开发与部署适用于应用开发环境中,这其中也包括最新的 IBM WebSphere 应用开发环境。





回页首


设计要素

为了使用 alphaWorks 的 Web Services Navigator 来浏览 Web 服务间相互调用的关系,你需要确认 Web 服务遵守 JSR 109 规定。 此外,因为要采用生成客户机存根的方式来静态绑定 Web 服务,我们同时也需要遵守一些额外的惯例。

我们来创建三个可以相互调用的 Web 服务。比如我们称它们为 Web1、Web2 与 Web3。 这样意味着它们在应用开发工具中的主文件为 Web1.java、Web2.java、Web3.java。应用开发工具将生成它们的存根并与之自动关联。

实现这个是非常简单的。Web 服务将有一个方法签名: doWork() ,它用一个整数作为输入并返回一个字符串作为输出。完整的 Web1.java 看起来如下:(不包括 doWork() 方法的真正实现):


清单1. 一个非常简单的 Web 服务
				
				package com.mycompany.webservices.web1;
				public class Web1 { public Web1() { }
				public String doWork(int work) { return "Web1 called
				with work: "+work; } }
			

你可以通过相同的框架结构来生成所有这三个 Web 服务。每个 Web 服务可以采用相同的输入与输出来执行 doWork() 方法。一旦所有这三个 Web 服务和一些支撑基础类被创建后,区分它们的输出是很困难的。

因为你想通过遵循 JSR 109 规范来实现 Web 服务的相互调用,因此你就不能将它们放在同一个项目中并把它们部署在同一个 EAR 包中。 这也是基于使用静态客户机设计的考虑。继续刚才所说的,应用开发工具将生成客户机存根类。 当 Web1.java 的客户机存根类生成后,应用开发工具同时将创建一个也叫 Web1.java 的接口文件。如果你试图在一个单独的项目或 EAR 包中生成这个存根文件,原有的 Web1.java 文件将被覆盖。 因此,我们将创建三个相互间独立的项目用来存储三个 Web 服务。





回页首


使用应用开发工具开发 Web 服务

允许在应用开发工具中创建 Web 服务

为了开发 Web 服务,应用开发工具需要将“Web Service Developer”工作角色置为许可状态。通过选择 Help>Welcome 可以检查它实际的选项。 检查屏幕底部的“Earth-with-bell”图标。如果它没有显示,点击右下脚的小人的图标,将显示一个弹出菜单。通过在该弹出菜单中点击“Earth-with-bell”图标启用该选项。如图一所示。但启用了“Web Service Developer”角色后你可以关闭欢迎页,以后在进行 Web 服务开发时将不需要再次执行此操作。(除非你创建了新的工作空间)

在开始之前,请确认应用开发工具已经启用“Web Service Developer”工作角色。 从应用开发工具菜单点击 Help>Welcome,并参考图一所示确认已经有“Earth-with-bell”图标。 如果它没有显示,请参考“Enabling Web service creation in Application Developer”。


图 1. 已经启用“Web Service Developer”角色的应用开发工具欢迎页面
wsenable

现在已经有了 Web 服务的骨架和接口,我们将准备三个 Web 服务。参阅 参考资料 中的“Project Interchange”zip文件,其中包含所有已经创建的源文件及支撑文件。 使用 RAD File>Import... Project Interchange 功能把完整的项目直接导入到你的应用开发环境中。

步骤1 : 创建一个新的“Dynamic Web Project”。从应用开发工具的 File 菜单依次选择 File>New>Dynamic Web Project 。 命名为 Web1 后再按下 Finish 按钮。通过相同的方式来创建动态 Web 项目 Web2 与 Web3。 注意 : 如果你从当前透视图更换为 Web 透视图时,应用开发工具可能会询问你。因为我们的例子是基于 J2EE(默认)透视图的,最好不要切换到 Web 透视图。 选择 Remember my decision 选项,并按下 No 按钮。如图2所示。


图 2. 应用开发工具透视图切换 -- 选择“No”
perspectiveno

步骤 2 : 创建 Java 类来保存每个 Web 服务。 打开项目导航器里的树图: Dynamic Web Projects>Web1>Java Resources>JavaSource。鼠标右键点击JavaSource,并选择 New>Class。填写包名为 com.mycompany.webservices.web1 ,类名为Web1,并按下 Finish按钮。


图 3. 创建一个新的 Java 类
newclass

对 Web2 和 Web3 项目执行相同的操作,但是所填写的包名和类名要同项目名称一致。在包名中使用小写的“w”而在类名中要使用大写的“W”。

步骤 3 :将 Web 服务代码拷贝到这些新创建的 Java 类中并选择保存。


图 4. 从清单 1 将 Web 服务代码拷贝到 Web1.java 文件中
copycode

确认代码中的包名、类名以及类构造函数名都已改为与项目名一致。 如果发生了不一致的现象,应用开发工具将标识错误并提醒你更正。下面就是 Web2.java 没有替换前的结果:


图 5. 应用开发工具中的错误自动标识
bugs

注意在包名中使用小写的“w”而在类名中要使用大写的“W”。

现在,你已经创建好了这三个简单的 Java 类。这些将成为我们的 Web 服务。 它们不带有显著的 J2EE 特点,甚至它们更不具有 Bean 组件特点。它们仅仅是简单的 Java 类,但是即使是最简单的 Java 类页可以作为 Web 服务。





回页首


生成静态客户机

现在是使用应用开发工具创建静态客户机的时候了。鼠标右键从项目浏览器点击 Web1.java 文件,确认你可以从弹出菜单看到菜单项 Web services>Create Web Service 。 (如果弹出菜单中没有 Web services 项,请参考“Enabling Web service creation in Application Developer”。) Create Web Service 菜单项实际上会执行一系列的操作。 而其中最为重要的就是在其它项目中生成静态客户机类,使它们可以调用这个 Web 服务。 例如,我们想使 Web2 能调用 Web1。这意味着我们需要在 Web2 项目中生成 Web1 的静态客户机类。

为了生成一个完全可以互连接的 Web 服务, 我们就需要在 Web2 与 Web3 中生成 Web1 的客户机,在 Web1 与 Web2 中生成 Web3 的客户机,在 Web1 与 Web3 中生成 Web2 的客户机。 你会发现这种方法在开发更多 Web 服务时是很吃力的。 全部相互连接的 Web 服务需要 n*(n-1) 个客户机。如果这样的话,在多于 4 个或 5 个 Web 服务后,你将会把所有时间放在客户机生成上。 而实际上,你只需填写一些相关的自动控制的表单就可以自动生成。因为我们这个例子中只有 3 个 Web 服务,接下来会继续采用人工方式来生成客户机。

注意:为使客户机生成步骤生效,你的 Application Developer 中的 WebSphere Application Server V6.0 版本测试环境必须启动。 如果它没有启动,你将在过程中得到这个非常普通的错误信息:


图 6. 如果 WebSphere 应用服务器没有启动后的错误对话框
error

如果你发现弹出这个错误消息对话框,忽略它并在启动服务器后重新执行上面的步骤就可以了。

现在我们已经准备好开始了,在项目浏览器中用鼠标右键再次点击 Web1.java 文件,选择 Web services>Create Web Service 菜单项。在Web服务页开启后,确认 Generate a proxy 选项被选中,接下来按下 Next 按钮。


图 7. 生成 Web 服务和代理
generate

在对象选择页按下 Next 按钮。


图 8. 验证“Bean”(Web 服务类)位置
generate2

在服务部署配置页特别需要注意在客户端环境选择中的 Client project 选项。 这就是你设立的相互连接的 Web 服务的地方。因为要启动 Web1 的 Web 服务,我们生成的第一个客户机将被放入 Web2 客户机项目中。从下拉列表中选择它并按下 Finish 按钮。


图 9. 为生成客户机代理选择目标项目
generate3

应用开发工具可能会弹出消息对话框提醒自动覆盖文件:


图 10. 运行自动的文件覆盖
generate4

接下来,通过选择 Yes to all 忽略该消息。 我们对 3 个 Web 服务采用相同的步骤来生成客户机,即对任意一个 Web 服务在其它 2 个项目中生成客户机。 尽管菜单项名字只是叫“Create Web Service”,但它实际上意味着创建或生成与 Web 服务相关的构件。不要担心重复执行该操作。

表 1. 交叉通信的 Web 服务

源自项目: 生成到该客户机项目:
Web1Web2
Web1Web3
Web2Web1
Web2Web3
Web3Web1
Web3Web2

每个项目应该包含下列包:com.mycompany.webservices.web1、 com.mycompany.webservices.web2、 com.mycompany.webservices.web3:


图 11. 可以访问 Web1 项目的 Web2 与 Web3 Web 服务
generate5

“本地”项目将只包含 2 个 Java 文件,另外两个“远程”项目将包含 6 个 Java 文件。





回页首


创建 Web 服务连接

针对我们的这些 Web 服务,我们将加入 JSR 109 Web 服务调用代码,同时为了使之遵循加入一些根本的逻辑。Web 服务调用的组件如下所示:


清单 2. 满足 JSR 109 规范的 Web 服务业务逻辑
				
				InitialContext ctx; Web1 web1 = null; ctx = new
				InitialContext(); Web1Service serviceLookup1 =
				(Web1Service)
				ctx.lookup("java:comp/env/service/Web1Service"); web1 =
				serviceLookup1.getWeb1(); result = web1.doWork(work);
			

我们初始化上下文,寻找并实例化存根,进而调用 Web 服务相关方法。当然,这个在 Web 服务本身的操作是完全合法的。

正如下面展示的,Web1.java 完全可以调用其它 Web 服务了:


清单 3. 一个调用其它 Web 服务的 Web 服务
				
				package com.mycompany.webservices.web1;
				import java.rmi.RemoteException; import
				javax.naming.InitialContext; import
				javax.naming.NamingException; import
				javax.xml.rpc.ServiceException; import
				com.mycompany.webservices.web2.*; import
				com.mycompany.webservices.web3.*;
				public class Web1 { public Web1() { }
				public String doWork(int work) { InitialContext ctx;
				Web2 web2 = null; Web3 web3 = null; String result = "";
				try { ctx = new InitialContext(); Web2Service
				serviceLookup2 = (Web2Service)
				ctx.lookup("java:comp/env/service/Web2Service"); web2 =
				serviceLookup2.getWeb2(); Web3Service serviceLookup3 =
				(Web3Service)
				ctx.lookup("java:comp/env/service/Web3Service"); web3 =
				serviceLookup3.getWeb3();
				if (1 == work) { result = web2.doWork(work); result =
				web3.doWork(work); } } catch (NamingException e) {
				e.printStackTrace(); } catch (ServiceException e) {
				e.printStackTrace(); } catch (RemoteException e) {
				e.printStackTrace(); } return "Web1 called with work: "
				+ work; } }
			

你会发现一些 J2EE 头被加进来了,同时对我们静态客户机的引用 com.mycompany.webservices.web2* 及 com.mycompany.webservices.web3* 也加了进来。Web2.java 在调用 Web3 时修改了 doWork() 方法:


清单 4. Web2 doWork()方法的实现
				
				public String doWork(int work) { InitialContext ctx;
				Web1 web1 = null; Web3 web3 = null; String result = "";
				if (1 == work) { try { ctx = new InitialContext();
				Web3Service serviceLookup3 = (Web3Service)
				ctx.lookup("java:comp/env/service/Web3Service"); web3 =
				serviceLookup3.getWeb3(); result = web3.doWork(work); }
				catch (NamingException e) { e.printStackTrace(); } catch
				(ServiceException e) { e.printStackTrace(); } catch
				(RemoteException e) { e.printStackTrace(); } } return
				"Web2 called with work: " + work; }
			

Web3.java 的 doWork() 的方法将依赖传递的参数回调 Web2。进而,当它调用 Web2 时改变了传递的参数(为了避免调用陷入死循环)。


清单 5. Web3 doWork()方法的实现
				
				public String doWork(int work) { InitialContext ctx;
				Web2 web2 = null; String result = ""; try { ctx = new
				InitialContext(); Web2Service serviceLookup2 =
				(Web2Service)
				ctx.lookup("java:comp/env/service/Web2Service"); web2 =
				serviceLookup2.getWeb2(); if (1 == work) { result =
				web2.doWork(3); } } catch (NamingException e) {
				e.printStackTrace(); } catch (ServiceException e) {
				e.printStackTrace(); } catch (RemoteException e) {
				e.printStackTrace(); } return "Web3 called with work: "
				+ work; }
			





回页首


测试 Web 服务间的调用

一旦我们在应用开发工具中开发完成,它们将被自动的部署在测试服务器上准备被调用。从项目浏览器中测试 Web 服务是简单且恰当的。 扩展 Web Services>Services


图 12. 选择调用 Web1 服务
testservices

右键点击 Web1Service 并选择 Test with Web Services Explorer 菜单项。 这样 Web Services Explorer 将被启动。我们可以通过点击来调用 Web1 的 doWork() 方法。填写数字“1”后按下 Go 按钮,我们就会得到结果。


图 13. Web Services Explorer 准备调用 Web1 的 doWork 方法
invoker

你可以看到调用 Web1 的 doWork() 方法执行后的结果:

				
				
					doWorkReturn (string)
					:Web1 通过传递的“1”被调用。
				
			

除了 WebSphere Application Server 的 System.out 日志文件的 trace 语句,Web 服务其余的行为是不可知的。 我们将决定通过 alphaWorks 技术来改变这种状况。





回页首


使用 IBM Web Services Navigator 观察 Web 服务

做为 alphaWorks 的技术,IBM Web Services Navigator 与其数据收集器一起工作用来收集、显示 WebSphere 环境中的 Web 服务的活动。 当它们安装和部署流程结束后,一个简单 Web1 的 doWork() 方法产生如下事务流程图:


图 14. Web 服务事务流程显示
viewtrans

这个事务流程追溯了不同 Web 服务的调用顺序:从 Web1 到 Web2,从 Web2 到 Web3,再从 Web3 到 Web2,最终都返回 Web1 等等。

服务拓扑结构图向我们展示了调用者图表:


图 15. Web 服务拓扑/调用关系图
topology

IBM Web Services Navigator 有许多特点,你可以在 alphaWorks 网站阅读更多这方面的资料(参考 参考资料 )。现在你的 Web 服务可以相互间进行操作了。





回页首


结束语

使一个 Web 服务调用其它 Web 服务与用单独的 Java 代码调用 Web 服务相比不再那么困难。 但是首先必须对应用开发工具进行一些初始化设置,使之能够满足我们开发 Web 服务的要求。 你需要允许 Web 服务间所必须的通信。我们通过本文描述了使用 RAD6 开发互操作 Web 服务的例子。 最后,通过 IBM alphaWorks 的 Web Services Navigator 的使用可以清晰的看到 Web 服务间调用的关系。






回页首


下载

描述名字大小下载方法
项目交互文件ws-coopradcode.zip246 KBHTTP
关于下载方法的信息


参考资料



关于作者

David Schmidt 是 IBM 高级软件工程师。 自从 1991 年以来,他主要负责设计、开发、测试及质量保证等方面的工作。最近,他主要专注于 IBM alphaWorks 的 Web Services Navigator 的研究和开发。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?




回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款