当您要将企业应用程序转变成与 Web 服务一起使用时,最简单的途径就是将单个操作与单个的企业服务结合在一块。但这并不是最好的方法。在本文中,Jerome Josephraj 将向您展示如何构架基于分层的、正确的模型-视图-控制器(MVC)设计模式的Web 服务应用程序。为此,他修改了 Struts(一种流行的开放源码MVC 框架),以使其适用于 Web 服务领域。通过研究此处所概述的样本应用程序,您将知道如何才能将 Struts 与 Web 服务联合起来使用。
不断发展的 Java 编程语言和 Sun 公司的 J2EE 规范使得遵守各类准则的软件开发者们能够创建出分布式计算应用程序,这些应用程序在以前只能通过相关专门工具才可实现。这样,当某些开发团体要选择在 Java 平台中实现新系统时,其他团体就会通过另外的技术来创建、提高并维护应用程序,然后将它们集成到已有的各类分布式应用程序中去。这种情形就引起了互操作性的竞争。新应用程序与旧应用程序如何交互呢?答案就是:Web 服务。Web 服务是程序设计新的圣杯。它们能够共享并协调分散的各类计算资源。
在本文中,您将了解实现此目的的一种方法。您将看到如何在与 Web 服务相结合的开放源码 Struts 框加的基础上来构建应用程序。在开始之前您必须了解一些 J2EE 与 Web 服务的相关知识。这里,我将简要地介绍下 Struts 架构与模型-视图-控制器(MVC)。
MVC 模式:分离开发角色
MVC 设计模式很清楚的划定了程序员与设计者的角色界限。换句话说,从商业逻辑上拆解了数据。这种模式是让设计者集中于设计应用程序的显示部分,而开发者则集中于开发驱动应用程序功能所需的组件。
MVC 模式有好几种变异,不过它们都是基于相同的基础结构:应用程序的数据模型 (Model),显示层代码(View),以及程序控制逻辑( Controller) 是存在其中的独立但能相互间通信的组件。模型组件描述并处理应用程序数据。视图指的是用户界面;它反映的是模型数据并把它递交给用户。控制器是将视图上的行为(例如,按下 Submit 按钮)映射到模型上的操作(例如,检索用户详细信息)。模型更新后,视图也被更新,用户就能够完成更多行为。MVC 模式使代码易懂而且使代码更容易重用;另外,在很多工程中视图经常要被更新,MVC 模式将模型和控制器与这些所做的更改独立开来。
图 1. MVC 设计模式
Struts:基于 MVC 的坚固框架
Struts 是 MVC 模式基础上构建 Web 应用程序的一种开放源码框加。Struts 鼓励在 MVC 模式上构建应用程序而且提供大多数 Web 应用程序所共有的服务。
在 Struts 应用程序中,您可以构建模型层,这样业务逻辑与数据检索逻辑重用就很容易了。这层负责运行应用程序的业务逻辑,获取相关数据(例如,运行 SQL 命令或者读取平面文件)。
Struts 鼓励在模型-视图-控制器设计范例基础上构建应用程序。Struts 提供自己的控制器组件(ActionController 类)并与其他技术相结合来提供模型与视图。对于模型(Model 类), Struts 能与任何标准的数据访问技术相结合,包括 EJB、JDBC 以及 Object-Relational Bridge。对于视图(ActionForm 类),Struts 在 JSP 环境以及其他基于表示逻辑的系统中运行的很好。图 2 阐明了基于 Struts 应用程序的逻辑流程。
图 2. Struts 应用程序的逻辑流
简单粗糙的 Web 服务体系结构
构建 Web 服务最简单的途径就是将单个操作与单个企业服务结合起来,如图 3 所示。在这种设计方法中,实现业务逻辑的服务与实现数据检索的服务是混合在一起的。
图 3. 简单粗糙的 Web 服务体系结构
这样的一种 Web 服务可以很容易从已有的业务组件中开发出来。然而,它有很多的缺点:对用户没有提供统一认证,提供者与订阅者耦合不紧,业务逻辑没有重用。简而言之,对于一个连贯的解决方案来说这并不是一个非常好的体系结构。
在 MVC 模式基础上来实现 Web 服务解决方案会更好点。在后续部分,您将看到如何使用 Struts 来做到这一点。我将通过 WSManager 层来详述现有的 Struts 架构,这一层展示了采用 Web 服务的模型服务方法。
采用 Struts 应用程序的 Web 服务
在以后的开发工作中您可以扩大构建得比较好的 Struts 应用程序来支持 Web 服务。前面讲过,Struts 架构清晰地区分开了视图、控制器以及模型。模型包含所有必须的业务逻辑,从存储数据仓库中检索数据。您可以构建一个简单的 Web 服务层——称为 WSManager 层——这样的模型可以提供 Web 服务也可以订购 Web 服务。使用这种体系结构的应用程序将基于组件开发的最佳点与万维网结合在了一起,如图4所示。
图 4. 采用 Web 服务的 Struts 应用程序
下面的部分要讨论的是这种体系结构中不同组件的详细信息,特别要密切注意 WSManager 层,因为它是此体系结构中真正新加的部分。
Struts 控制器
MVC 体系结构的控制器部分主要集中于接收客户端的请求(一般为运行 Web 浏览器的用户),决定执行哪一种业务逻辑功能来响应请求,然后负责生成下一个用户界面连接到合适的视图组件上去。在 Struts 中,控制器的主要组件就是 ActionServlet 类的一个小服务程序。
ActionServlet 负责通过 XML 文档将 URI 请求映射到特定的行为。这个文档包含了 URI 请求列表而且还告知 ActionServlet 它应该如何分配每个请求。这种方法有几个好处:
☆ 应用程序的整个逻辑流程在分级文本文件中。
☆ 这种格式的列表更容易查看与理解,尤其是对于一个很大的应用程序而言。
ActionServlet 决定了应用程序的流程。许多 Action 类都继承了 ActionServlet 。每个 Action 类:
☆ 都映射到各自独立的处理过程
☆ 通过 Struts 的 ActionController 与 Struts JSPs 相结合
☆ 作为继承 Struts中 Action 类的一个 Java 类来实现。
Struts中 Action 类调用 WSManager 类中的相关方法来使用 Web 服务。 WSManager 获取所要求的响应——或者如果有一个被解除了就会出现异常——将它回传给 Struts 控制器。
WSManager
WSManager 接收 JAX-RPC 端点的请求。将 WSManager 类中的方法调用映射到新来的客户端请求。这些新来的客户端请求是 SOAP 信息的格式。WSManager 必须实现安全认证,转变参数,在指定请求到模型服务之前,要对这些请求进行参数预处理。请求所包含的参数形式有 Java 对象,Java原始参数,XML 文档,或者甚至是 SOAP 文档分片(例如,SOAP Element 对象)。这些类型必须要转化成内部所支持的 schema(例如,预定义的 Java 数据访问对象)。
虽然 WSManager 可以很直接地处理与 Java 对象结合的参数,但它还需要采取一些附加步骤来处理 XML 文档。建议以下步骤:
1. WSManager 类应该能够通过XML Schema来验证XML文档的有效性。
2. WSManager 类接着要把 XML 文档转化成内部支持的 schema。
3. 最后 WSManager 应该分解文档并且尽可能地将它映射到域对象中去。
WSManager 实现以下任务是非常重要的:
1. 身份验证和授权使用
2. 错误处理
3. 缓存。
WSManager 也可以生成响应;这个过程由方法调用返回值的简单构造所组成。WSManager 中保留这样的功能,您可以通过缓存数据从而避免重复访问模型服务层。您还可以集中管理响应聚集以及 XML 文档转换,如果您要返回给调度者的文档必须遵守与内部 schema 不同的 schema 时,这一点就显得尤其重要。
WSManager 处理所有SOAP 请求并把它们委派到模型层所暴露的业务逻辑。如果模型服务是作为一个 EJB 层来实现,那您一样可以在 EJB 层中通过 Session Fa?ade 设计模式来实现。如果您采用这种模式执行 WSManager ,您将获得很多好处,因为 WSManager 会:
☆ 作为初始联系点来管理请求与服务
☆ 调用安全服务,包括身份验证与授权使用,从而避免任何重复的层访问。
☆ 委派业务处理(采用由 Struts 应用程序所使用的模型服务)
☆ 在 WSManager 层缓存数据避免任何不必要的数据库访问。
发布者:展示 Web 服务
在 WSManager 类中实现的每一个公共方法都将作为一种 Web 服务发布出来。换句话说,您要为这些类发布一种 Web 服务描述 。Web 服务描述是由服务的 Web 服务描述语言(Web Services Description Language,WSDL)描述与由它所引用的任何 XML schema 所组成。(WSDL 是描述服务的标准语言。)
您可以在公共注册中心或在企业内的公司注册表上发布 Web 服务描述。同样,你也可以发布由WEB 服务定义的XML Schema到同一个公共的或者公司专有的(UDDI)注册中心上。 Java Web 服务客户端采用 JAXR 应用编程接口来查询公司或公共注册表上的服务描述。
如果您的客户都是专有的合作伙伴,那您就不需要使用注册表了。不过,您可以在您的应用程序的 Web 层或者在具备适当安全保护的熟知位置来发布您的 Web 服务描述( WSDL 和 XML schemas)。例如,假想有一个转售者的客户应用程序,他与某个特殊厂商有协定。客户应用程序已经在厂商开发时间中静态地 与 Web 服务结合在一起了。只有授权的团体才可以查询 XML schema 或者从 Web 层检索服务描述来生成客户端代码。您应该在 WSManager 层中执行有效客户的身份验证和授权使用。
订购者:使用 Web 服务
应用程序可以利用在公共注册表或者企业内部中已存的 Web 服务。 WSManager 有解析必要的 WSDL 文件的方法并且调用相关的操作返回一个值。Struts 控制器在 WSManager 类中调用相关方法来使用特定的 Web 服务。数据作为预定义的数据访问对象在 WSManager 和 Struts 控制器之间来回传送。访问 Web 服务时所发生的所有异常都将在 WSManager 中列举出来并且传回到 Struts ActionController.
服务请求者要通过使用服务代理来搜索 Web 服务;如果找到它所想要使用的 Web 服务,为使用这个服务它将与服务提供者建立一个合约,然后才能调用服务中的业务。
WSManager 使用 WSDL 文档、服务器名字、端口名、操作名以及包括 Java 原始类型、Java 数组、Java 对象 或者与 XML 文档等所有必需的请求参数一起来订购一个 Web 服务。
如果是在 UDDI 注册表中发布目标 Web 服务,那么所有基于 Struts 的应用程序都能使用像 XMethods (请参阅参考资料)这样的代理服务来订购它。在执行完所请求的操作后,供应者 Web 服务返回所期望的值。 WSManager 可以更改返回值使得它与应用程序所期待的 schema 相匹配;它也可以在应用程序要求基础上修改结果。从 WSManager 收到结果以后,Struts ActionController 能够处理结果并传送给相关视图,或者还能调用相关的模型服务来完成深层处理。
错误处理
所有的错误都是在 WSManager 层中处理的;这就消除了不必要的服务器开销。如果模型服务是当作 EJB 层来实现的,那么其性能会得到非常显著地增强。
在充当供应者角色时, WSManager 抛出所有如 SOAPFaultException 这样的异常。它还可以检查新来的请求并且抛出所有缺少强制字段的异常。您可以创建一个类来跟踪并在数据仓库中记录这些错误,以后可以做作参考。
在充当订购者角色时, WSManager 捕捉到由服务供应者所抛出的所有 SOAP 异常并且将它们更改为 WSManager 所要求的格式。您可以记录下所抛出的错误作为以后参考之用。如果需要,那么在需要时候也能够校验出响应值并且作为异常抛出。您可以创建一个类来记录这些异常,作为以后参考之用。 WSManager 能够验证响应值而且可以作为一个异常将它抛出。
审核
在充当供应者角色时, WSManager 可以记录下详细信息,将来可以做为审核。使用这些信息有很多用途,像:
☆ 在所接收到的大量采样点基础上登记客户端。
☆ 收集数据用作市场目的。
☆ 决定应用程序是否需要更新。
☆ 鉴别并捕捉非法用户。
缓存
Web 服务的客户往往要比一般的客户端-服务器体系结构中的客户要多些;因此在 Web 服务体系结构中,客户端就要做更多的工作,比如缓存。Web 服务正确使用数据缓存就可以实现其最大的性能。当服务的请求信息主要是只读的时候或者当那些信息按照比所要求的速率变化得还要慢时,您就应该考虑要在 Web 服务中使用缓存了。
身份验证与授权使用
您可以在 WSManager 层中执行所有订购者的身份验证。所有想使用 Web 服务的客户都要经过这样的身份验证逻辑。您可以使用基本用户身份验证或者数字证书来实现此目的。
Struts 视图
您要通过使用 JSP 技术来构建基于 Struts 应用程序的视图部分。JSP 页包含有静态 HTML 加上 动态内容,这些内容是基于对特别行为标签说明(在页面要求时)的。JSP 环境包括一套标准的行为标签。另外,在自定义标签库中组织了一套标准的工具,开发者们可以使用这些工具来定义他们自己的标签。
Struts 框架包含有扩展的自定义标签库,这个库能帮助用户界面国际化更为全面并能非常适度地与 ActionForm 组件相互作用。视图层比较单薄,它不提供业务逻辑。Struts 视图是通过 ActionForm 与 Struts 控制器相互作用的。
Struts ActionForm
ActionForms 只是一些 Java 类而已,它继承了 Struts 所提供的 ActionForm 类,这些类中包含有 aclearcase/" target="_blank" >ccessor 和 mutator 方法。 JSP 页或者 Action 类都会调用这些方法来检索或者从数据库刷新数据。
模型服务
模型服务是作为一组 Java 类来执行的。每个模型服务组件都会提供一套服务,而这些组件结合起来同样也提供一套普通服务。 ActionController 与 WSManager 类将数据当作预定义的数据访问对象来回传送。在处理过程中,ActionController 或者 WSManager 可以调用相关模型服务组件中所要求的方法。这些组件将所要求的数据以数据访问对象的形式传给模型服务,模型服务执行一切必须的商业逻辑处理然后从存储数据仓库中取出所需要的数据。模型服务组件聚集相关的预定义数据访问对象,然后将它传回给 ActionServlet 或者 WSManager 类。所有的错误或者确认信息都会通知给 ActionServlet 或 WSManager 层。
对于您的应用程序,您应该遵守以下的设计规则:
1. 模型服务不能含有任何与视图相关的代码(例如,会话处理)。
2. 所有的事务仅仅只能在 Action 或 WSManager 层中来提交。
3. 模型服务只能被同一模型服务组件中其他的模型服务或者高层 Action 类所调用。
数据存储层
数据存储层由所有的存储数据仓库组成。例如,它可能包含有关系数据库、平面文件或者甚至有 XML 文档。
示例
我已经附上了实现此处所讨论的体系结构的一个简单示例。所有的示例代码都包含在了 zip文件 中。这个例子举例说明了一个简单的新闻 Portal。新闻内容是从数据源(这里称为 DataSource )传送到 JSP 页,同时信息内容也要作为一个 Web 服务发布出来。这个 Portal 也可以从 StockQuote Web 服务中检索最新的股票报价并显示在同一个 JSP 页中。
这个 zip 文件包含了所有的源代码和 DataSource 的 SQL 脚本。它还包含有 Struts Action 和 ActionForm 类、模型服务类以及用来为发布者与订购者显示结果及其源代码的 JSP 页。它同时还包含有 WSDL 文件以及客户端源代码来访问已发布的 NewsContent Web 服务。
这里我不再阐述示例代码的详细信息了;您想要发现它的复杂性,最好的途径就是自己去试验。开始时,我会向您说明应用程序如何将信息内容(存储在 DataSource 中)和股票信息(从 Web 服务处获得)递交给 JSP 页的。
JSP 页被加载时,它就调用 Action 类,这个类在 MVC 设计模式中充当控制器角色。 Action 类通过传递预定义的数据访问对象来调用模型服务类中的 getNews() 方法。(清单 1 中所示的一小段 Action 类代码说明了如何从 Action 类中调用模型服务。)
getNews() 方法实现了所有必需的业务逻辑与数据检索逻辑。一旦从数据源取出了相关数据,模型服务就可以聚集数据访问对象并将它发送回给 Action 类。
清单 1. Action 类的部分代码
/*
* Create a pre-defined Data Access Object
*/
newsSearchResultVOB = new NewsSearchResult();
/*
* Call Business layer NewsMs's searchNews method passing the NewsCOD
* and get back a Collection of News
*/
newsList = (Vector) newsMs.getNews(newsSearchResultVOB);
/*
* Put the NewsList in the request.
*
*/
request.setAttribute(SystemConstants.NEWS_LIST, newsList);
//Set the newslist in the form
newsFrm.setNewsDetails(newsList);
// Set the NewsForm in the request
request.setAttribute(mapping.getAttribute(), newsFrm);
//return actionForward;
return mapping.findForward("success");
从模型服务处接收到数据访问对象后, Action 类通过传递转给模型服务的同一个数据访问对象来调用 WSManager 类中的 getStocks() 方法。 WSManager 类中的 getStocks() 方法执行 JAX-RPC 来订购股票 Web 服务。
从股票 Web 服务获得的结果要转变成本地的 schema 并且聚集回到预定义数据访问对象中去。这个对象从它被调用的地方传回到 action 类。 清单 2 阐明了如何使用 WSManager 中的 JAX-RPC 来订购 Web 服务,以及如何将股票 Web 服务的结果转变成本地 schema 并聚集回到数据访问对象中去。
清单 2. 订购 Web 服务及传送响应
public Vector getStocks() throws Exception {
//Method level variables
Vector stockValues = new Vector();
try {
// create service factory
javax.xml.rpc.ServiceFactory factory =
javax.xml.rpc.ServiceFactory.newInstance();
// define targetNameSpace
String targetNamespace = "http://www.themindelectric.com/"
+ "wsdl/net.xmethods.services.stockquote.StockQuote/";
// define qname
QName serviceName =
new QName(targetNamespace,
"net.xmethods.services.stockquote.StockQuoteService");
// define portname
QName portName =
new QName(targetNamespace,
"net.xmethods.services.stockquote.StockQuotePort");
// define operation name
QName operationName = new QName("urn:xmethods-delayed-quotes",
"getQuote");
//Specify wsdl location
java.net.URL wsdlLocation =
new java.net.URL("http://services.xmethods.net/soap/urn:xmethods-
delayed-quotes.wsdl");
// create service
javax.xml.rpc.Service service =
factory.createService(wsdlLocation, serviceName);
// create call
javax.xml.rpc.Call call =
service.createCall(portName, operationName);
//Populate an array with list of stock names
Vector populatedStockNames = this.getPopulatedList();
//Loop through the populated list, and for each stock get a result
java.util.Iterator i = this.getPopulatedList().iterator();
String stockName = null;
Float result;
com.ddj.wsstruts.valueobject.StockValue stockValue = null;
while(i.hasNext()) {
stockName = (String) i.next();
// invoke the remote web service
result = (Float) call.invoke(new Object[] {stockName});
if(category.isDebugEnabled()) {
category.debug(" The quote for " + stockName + " is: " + result);
}
//Set stock name and stock values in stockValue bean
stockValue = new com.ddj.wsstruts.valueobject.StockValue();
stockValue.setStockName(stockName);
stockValue.setStockValue(result.toString());
//Add the stockValue bean to stockValues vector
stockValues.add(stockValue);
}
}
一旦 Action 类调用完模型服务和 WSManager, ActionForm 中的股票值及信息内容域就会被填充。控制就被返回到 JSP 页面,从 ActionForm 获取所有必需的值并打印 UI.
结束语
在本文中,您看到了 Struts 架构是如何与 Web 服务相集成的。您也了解了如何使用 Struts 组件来提供和预订 Web 服务。本文附带的简单应用程序代码将帮助您深入理解所有这些是如何工作的。
使用此处所阐明的体系结构,您可以开发出这样的企业应用程序,它非常健壮,很容易维护,而且能很容易地与早期应用程序集成在一起。我希望您将可以开发出更多的关于 Struts 和 Web 服务的项目,并看到这种体系结构在您自己的项目中是多么的有用。