用java来建立一个很有价值的web 应用不是一个简单的任务。在架构这个应用时要考虑很多的因素和问题。从更高的层次来看,开发人员面临着关于如何构建用户接口,何处驻留业务逻辑,以及如何实现数据持久性这些问题。这3层都有各自的问题需要回答。而每一层又需要实现那些技术?应用如何设计来进行松散耦合并能进行灵活变更?应用架构是否允许某一层变更而不影响到其它的层次?应用应该如何处理容器一级的服务比如事务?
在为你的应用创建一个架构之前有许多问题需要澄清。幸运的是,有很多开发者都意识到这个问题,并建立了很多框架来解决这些问题。一个良好的框架可以让开发人员减轻重新建立解决复杂问题方案的负担和精力;它可以被扩展以进行内部的定制化;并且有强大的用户社区来支持它。框架通常能很好的解决一个问题。然而,你的应用是分层的,可能每一个层都需要各自的框架。仅仅解决UI问题并不意味着你能够很好的将业务逻辑和持久性逻辑和UI 组件很好的耦合。例如,你不应该使具有JDBC代码的业务逻辑放入控制器之中,这不是控制器应该提供的功能。一个UI 控制器应该是轻量化的组件,由它代表对UI范围之外的其它应用层的服务调用。良好的框架自然地形成代码分离的原则。更为重要的是,框架减轻了开发人员从头构建持久层代码的精力,从而集中精力来应用逻辑上,这对客户端来说更为重要。
本文讨论了如何结合几个著名的框架来达到松散耦合,如何设计你的架构,以及如何达到各个层次的一致性设计。面临的挑战是,将框架整合起来,以使每一层都向另外的层次以一种松散的方式来暴露接口,而不管底层功能使用的是什么技术。本文还讨论整合3种著名开源框架的一种策略。对表现层,我们使用Struts;业务层使用Spring;对于持久层我们使用的是Hibernate。你尽可以取代这里的某个框架而使用你喜欢的框架已达到同样的效果。图1显示了框架被整合起来时,从最高层次看到的视图。
应用层
许多设计良好的web 应用,可以被按职责分为四层。这些层次是表现层、持久层、业务层、和领域模型层。每一个层次都有其独特的职责,不能把各自的功能与其它层次相混合。每一个应用层都应该和其它层隔离开来,但允许使用接口在层间进行通信。我们开始来看看每个层,并讨论一下它们各自都应该提供什么和不应该提供什么。
表现层
一个典型的web 应用的末端是表现层。许多Java 开发者都知道Struts 提供了什么东西。然而,太多时候,耦合代码比如业务逻辑被放进org.apache.struts.Action中。所以,我们先总结一下Struts 之类的框架应该提供什么。下面就是Struts 的职责所在:
- 管理用户的请求和响应
- 提供一个控制起来将调用委托到业务逻辑和其他上游处理
- 将来自于抛出例外的其他层的例外处理到Struts Action 中
- 组装可以在视图中表现的模型对象
- 执行UI 校验
下面是一些经常可以使用Struts进行编码但是不应该和表现层关联的事情:
- 直接和数据库交互,比如JDBC 调用
- 与应用相关的业务逻辑和校验
- 事务管理
在表现层中引入这些类型的代码将导致类型耦合和维护负担。
持久层
一个典型Web应用的另一端是持久层。这也是应用中最容易很快失控的地方。开发者通常低估了自己构建自己的持久层框架的挑战。一个定制的,内部开发的持久层不仅需要大量的开发时间,并且通常缺乏功能和难以管理。目前有许多解决这些问题的开源对象关系映射 (ORM) 框架。特别地, Hibernate 框架就允许Java中的对象-关系的持久性和查询服务。Hibernate 对已经熟悉了SQL 和JDBC API 的Java开发者来或具有中度的学习曲线。Hibernate 的持久对象基于POJO和Java 群集(collections)。此外,使用Hibernate 不和你的IDE接口。下面列出了你需要在持久性框架中编写的代码类型:
- 查询关系信息到对象中。Hibernate 是通过称为HQL的OO查询语言,或者使用更有表现能力的规则API,来完成这个工作的。除了使用对象而不是表,使用字段而不是列的方式,HQL非常类似于 SQL。也有一些新的特定的HQL 语言特征需要学习;但是,它们是很容易理解和良好编写的。HQL 是一种用于查询对象的自然语言,而对象,只需要很少的学习曲线吧。.
- 存储、更新和删除存储在数据库中的信息
- 高级的对象关系映射框架比如Hibernate支持大部分主流SQL数据库,它们支持父/子关系,事务,继承和多态。
下面是应该在持久层避免的一些事情:
- 业务逻辑应该置于应用的更高层中。这里只允许数据访问方法。
- 不应该使持久逻辑和表现逻辑耦合。避免表现组件如JSP或者基于servlet的类中的逻辑直接和数据访问进行通信。通过将持久性逻辑隔离在其自己的层中,应用将具有更加灵活的修改性而不影响到其他层的代码。例如, Hibernate 可以使用其他持久框架和API代替,而不需要修改其它层中的代码。
业务层
典型的Web应用的中间组件一般是业务层和服务层。从编程的角度来说,service layer经常被忽略。这种类型的代码散布于UI表现层和持久层并不是不多见。这些都不是正确的地方因为它导致了紧密耦合的应用和难以维护的代码。幸运的是,大多数框架都解决了这个问题。这个空间内最流行的两个框架是Spring 和PicoContainer。它们都被视为是具有非常小的足迹(footprint)并且决定如何将你的对象整合在一起的微容器(microcontainer)。这些框架都建立在一种叫做依赖性注入(dependency injection) (也称控制反转(inversion of control:IOC))的简单概念之上。我们将关注Spring中通过针对命名配置参数的bean属性的setter 注入的使用。Spring 也允许一种更加高级的构造器注入(constructor injection)形式作为setter injection 的可选替代。对象通过简单的XML 文件进行连接,该配置文件包含对各种对象的引用,比如事务管理处理器(transaction management handler),对象工厂,包含业务逻辑的服务对象,以及数据访问对象(DAO)。
我们随后会用一些例子来澄清Spring中使用这些改变的方式。
业务层应该负责下面的问题:
- 处理应用的业务逻辑和业务校验
- 管理事务
- 允许与其他层进行交互的接口
- 管理业务级对象之间的依赖性
- 加入了表现和持久层之间的灵活性,以便它们不需要彼此进行直接通信
- 从表现层暴露上下文给业务层以获得业务服务
- 管理从业务层到表现层的实现
领域模型层
最后,因为我们要解决实际的问题的web应用,我们需要一套在不同的层间移动的对象。领域模型层包含的是表达实际业务对象的对象,比如Order, OrderLineItem, Product 等等。这一层允许能让开发者不再构建和维护不必要的数据传输对象DTO来匹配其领域对象。例如, Hibernate允许你读取数据库信息到一个领域对象的对象图中,以便你可以在离线的情况下将其表现在UI层中。这些对象可以被更新并跨过表现层发送回去,然后进行数据库更新。另外,你不再需要将对象转变成DTO,因为它们在不同的层间移动时可能会丢失事务。这种模型允许Java 开发者能够以OO风格的方式很自然的处理对象,而不用编写额外的代码。
整合一个简单的例子
到此,应该对各种层次和组件有一个高层的理解了罢。可以开始一些实践了。再次说明。我们的例子整合了Struts, Spring, 和Hibernate 框架。每个框架都包含大量的内容细节,我们不会多述。我们的目的使用一个例子向你说明如何将它们整合在一起构建一个优雅的Web应用架构。实例将演示一个请求是如何得到各层的服务的。此应用的用户可以将一个订单保存在数据库中并且察看数据中的已有订单。进一步的增强允许将用户更新和删除现有订单。
首先,我们将常见我们的领域对象,因为它们是要和各层沟通的。这些对象将允许我们能够定义那些对象需要持久化,那些业务逻辑需要提供,以及应该设计那些表现接口。接下来,我们将使用Hibernate 来为领域对象配置持久层和定义对象关系映射。然后,我们将定义和配置我们的业务层。在完成这些组件后,我们将讨论如何使用Spring将这些层关联起来。最后,我们将提供一个表现层,它知道如何与业务服务层通信以及如何处理来自于其他层的例外。
摘要: 第1.5式. 将JSP 应用转到Struts
问题
你需要将一个已有的基于JSP的web 应用转换为Struts 的应用。
动作分解
在加入新的功能到应用中时,可应用Struts采取重构的方式来进行。随着你对Struts 知识的增加,你可以将现有代码重新架构成使用Struts。如果没有计划对应用进行新的开发,就一次性重构现有的JSP代码。
变化
迁移一个现有JSP应用的困难程度取决于应... 阅读全文
对于RAD 工具的四个层次, JavaServer Faces 定义了其中3个:一个组件架构,一个标准的UI 部件集,以及一个应用架构。JSF的组件架构定义了通用的方式来建立UI 部件。此架构能驱动标准的JSF UI 组件(按钮,超链接,复选框,文本框等等),也为第3方组件留了空间。组件是面向事件的,所以JSF 允许你处理客户产生的事件 (如,文本框中值的变化或者点击了按钮)。
因为对Web应用来说,不象其桌面应用堂兄弟,它必须总是要满足多个客户 (比如桌面浏览器,移动电话和PDA等), JSF 有一个强大的架构来以不同的方式显示组件。它也有一个可扩展的机制来进行输入校验 (如字段长度) 以及在显示字符串和对象之间进行转换。
Faces 也能够自动的保持你的UI 组件和收集用户输入值的Java 对象之间的同步,并且通过调用后台Bean来对事件进行响应。另外,它有一个强大的导航系统并且全面支持多语言特征。这些特征构成了JSF的应用基础架构—即对新应用系统必不可少的基本构建块。
JavaServer Faces 定义了工具支持的基础,但是其实现留给了工具厂商,这是Java的习惯。你可以自行选择业界领导提供的工具,它们都可以是你能够像使用其他RAD 开发工具如Visual Studio. NET一般可视化的布局和设计你的Web UI。 (如图1.1, 1.2, 和 1.3 分别展示了在IBM, Oracle, 和 Sun的IDE开发工具中开发JSF是什么样子) 。或者,如果你愿意在没有设计工具下开发Faces 应用。
在这些赞美之词之后,我们应该指出JavaServer Faces 和桌面UI 框架如Swing 或者SWT之间的关键不同之处: JSF 是运行在服务器之上。因此, Faces 应用将运行在一个标准的Java web 容器之中,如Apache Tomcat [ASF, Tomcat],Oracle Application Server [Oracle, AS],或者IBM WebSphere Application Server [IBM, WAS],然后向客户显示HTML 或者其他标记语言。
如果你点击一个Swing 应用中的按钮,它将产生一个事件,而你可以直接在驻留在桌面中的代码来处理该事件。相反, web 浏览器并不知道JSF组件和其事件的任何东西;它仅仅知道显示HTML而已。所以当你点击一个Faces 应用按钮,它将产生一个请求,有浏览器发送到服务器。Faces 负责将该请求转换成一个可以被服务器中的应用逻辑所处理的事件。它也负责保证你在服务器所定义的每一个UI 部件都正确显示给浏览器。
图1.4 显示了一个Faces 应用的高阶视图。你可以看到,应用运行在服务器上可以和其他子系统集成,如EJB服务或者数据库服务。当然, JSF还提供许多其他服务可以帮助你你更小的代价构建强大的Web应用。
JavaServer Faces 有一个特定的目标:使web 开发更快更容易。它允许开发人员以组件,事件,后台Bean以及它们之间的交互来进行思考,而不是请求,响应和标记。换句话说,它掩盖了Web开发的大量的复杂性,是开发人员能够集中于如何使他们的应用做的最好。
工业支持
对JCP社区和Sun 扩展Java的方式来说,最好的事情莫过于有大量的公司,组织和个人投身其中。通过JCP 产生一种规范实际上算不上快速,但结果却是非常好的。JavaServer Faces 在2001年5月通过Java 规范请求(JSR) 127 引入;规范的最终版本,JSF 1.0,在2004年3月3日才发布。而JSF 1.1 (维护发布版) 则是2004年5月27日发布的。参与开发Faces的公司和组织 (除Sun之外)包括Apache软件基金, BEA 系统, Borland 软件,IBM,Oracle,Macromedia,等等。
这些公司开发的产品可分为3类(某些可能适合不止一类):J2EE 容器,开发工具,和UI 框架。因为JavaServer Faces是一个与工具一起工作和运行于J2EE 容器中的UI框架,这样做则非常之好。最重要的是这些公司中包括许多业界巨头。这意味着你可以期望JSF 具有大量的工业支持。并且,如果你的供应商不支持JSF,你也可以免费下载Sun的参考实现 [Sun, JSF RI]。
要跟踪最新的JSF 新闻,文章,产品和供应商,请访问JSF Central [JSF Central],本书作者所运作的一个社区站点。
JSF,Struts,和其他框架
我们面对这一情况:有大量的Java web 框架可用。它们中某些,如Struts [ASF, Struts] 和 WebWork [OpenSymphony, WebWork],有助于表单处理和其他问题,比如遵循Model 2,集成数据源,以及通过XML配置文件中心化控制引用的所有应用资源。这些基本框架提供了广泛的基础设施,但是还没有屏蔽基本的HTTP请求响应处理。
其他框架,象Tapestry [ASF, Tapestry],Oracle的应用开发框架 (ADF) UIX [Oracle, ADF UIX],以及SOFIA [Salmon, SOFIA],都提供一个UI 组件模型和某些事件处理机制。这些UI 框架,包括JSF,目的是简化整体变成模型。经常,基础架构和UI 框架具有重叠的功能。
为了理解这种重叠,你可以想象web 应用架构师一个服务栈。靠近栈底部的服务没能抽象基础协议的许多细节;它们更像粗加工品。栈中靠近顶部的服务则隐藏了更多讨厌的细节,提供更高级别的抽象。最低层的服务由web servers,Servlet API,和JSP处理。大部分框架都提供一些附加服务的子集。图1.6显示了这个栈,以及与JSF,Struts,servlets,JSP,和典型的web server的关系。
你可以从图中看到JSF 支持足够多的服务,这也使得它自己成为强大的框架。在大多数情况下,这就是你需要的东西。后续发布的Faces极有可能也会包括传统的服务。
然而,即使Faces 与Struts这样的框架有些重叠,也并不是必须替代它们。 (事实上,如Struts的领导, Craig McClanahan, 是JavaServer Faces的开发指导) 如果你将他们集成起来,你就可以访问栈中的所有服务(第 14 章将包含Struts 集成)。你也可以和其他框架一起使用JSF,比如Spring [Spring-Faces]。
对于面向UI的框架,JSF 和他们很多功能都有重叠。这些项目的某些申明将在其未来版本中支持JSF。Faces 的独特之处在于有通过JCP的工业巨头参与的开发联盟,以及将成为J2EE的一部分。作为结果,它将分享受强有力的工具支持,并将随很多J2EE server一起交付。
组件无处不在
令人悲哀的是,“组件”一词的过度使用在今天已经到处蔓延了。操作系统是一个组件,应用程序是一个组件,EJBs 是组件,库是组件,甚至厨房的水槽也是。有大量的书论及组件,有好的书指出组件有好多定义存在。
如果你知道他的确实意义,对这个词的滥用,你就不会感到陌生。如果你在词典中查找“组件(component)”这个词,你就会看到他有一个同义词供选—整体的一个部分。因此,如果你使用这个词的字面意思,在一个分布式应用欢迎用,操作系统确实是一个组件。
更有趣的是,从概念上讲,厨房水槽对操作系统相比对Faces组件来说更有共通之处。你不用自己从头制造它—你只需要购买一个符合你需要的水槽:尺寸,颜色,材料,容器数,等等。对其他厨房用品也是如此,比如橱柜和工作台面。所有这些组件都有特定的接口可以使他们能够和其他东西进行集成,但是依赖于特定的环境服务。(例如,接口管)。最终结果可能是独特的,但整体是由独立可重用的部件组成。
如果我们采用厨房组件的概念,并应用到软件商,我们会得出这个定义:
厨房的“环境依赖性”就是诸如房间本身,配管,电路等等的因素。本质上,环境是所有组件的容器。一个容器是拥有组件,并且提供一系列允许进行组件操作的服务的系统。有时,这种操作在IDE (设计时)中进行,有时则在部署环境中运行,比如J2EE server之中 (运行时)。
短语“独立部署” 意味着一个组件是一个自包含的单元,可以被安装到一个容器中。厨房水槽是一个独立的,自包含的组件,可以安装在工作台中。
当你改造你的厨房时,你雇用一个承包商,由他来组装你所选择的组件 (橱柜,抽屉,水槽等等) 成为一个完整的厨房。我们使用组件构建软件时,我们也是将各种组件组装起来,创建能够运行的软件系统。
JSF 组件, Swing 组件, servlet, EJB, JavaBean, ActiveX 控件,以及Delphi 可视组件库 (VCL) 组件都符合这个定义。但这些组件却集中于不同的事情。JSF 和Swing 组件单独针对UI 开发,而ActiveX 和 VCL 控件可以也可以不影响UI。Servlets 和 EJBs 则更粗糙一些— 他们在应用和业务逻辑领域提供大量的功能。
因为JSF 着眼于UI 组件,我们来相应窄化我们的组件定义。
如果你是在开发传统的GUI应用,那么UI 组件的概念应该对你非常熟悉了。JavaServer Faces 的精彩之处在于将标准的UI 组件模型引入到Web世界。
第1.4式. 从Struts 1.1 升级至Struts 1.2
问题
你想要升级基于Struts 1.1 的应用至Struts 1.2。
动作分解
Table 1-4. Struts 1.1 和 1.2 的Taglib URI |
Struts 1.1 Taglib URI |
Struts 1.2.4 Taglib URI |
http://jakarta.apache.org/struts/tags-bean |
http://struts.apache.org/tags-bean |
http://jakarta.apache.org/struts/tags-html |
http://struts.apache.org/tags-html |
http://jakarta.apache.org/struts/tags-logic |
http://struts.apache.org/tags-logic |
http://jakarta.apache.org/struts/tags-template |
http://struts.apache.org/tags-template |
http://jakarta.apache.org/struts/tags-tiles |
http://struts.apache.org/tags-tiles |
http://jakarta.apache.org/struts/tags-nested |
http://struts.apache.org/tags-nested |
- 将validation.xml文件中开头的的DOCTYPE 声明修改为:
<!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN" http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
- 将struts-config.xml文件中开头的DOCTYPE 声明修改为:
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN" "http://struts.apache.org/dtds/struts-config_1_2.dtd">
- 将ActionError 类的使用替换为ActionMessage 类。
- 将除了定制ActionForms 的validate( )方法之外的地方的ActionErrors类的使用替换为ActionMessages类。
- 去除ActionServlet中除了config参数之外的其他任何init-param元数的依赖。这些参数在Struts 1.1 中已经不赞成,而在Struts 1.2已经不支持。而是将这些参数值移到struts-config.xml文件中。这些参数大多数被controller元素的属性替代了。
- 去除html:form标签中对name, scope, 和type属性的依赖。这些参数在Struts 1.1 中已经不赞成,而在Struts 1.2已经不支持。
动作变化
Struts 1.2 的正式目标是去除不赞成方法并完成对模块化的支持。尽管Struts 1.2 并未对Struts 1.1 的核心进行彻底的修改,它也包括了一些值得努力去升级的新的特征和增强。大部分这些特征都将在本秘籍中讨论。下面是一些明显的增强:
- 新的validwhen校验器用作复杂的交叉字段校验(第8.4式)
- 支持通配符的action mapping,允许你在多个相关的URL中重用action元素。(第7.8式)
- 新的预构建的action 包括一个新的MappingDispatchAction类和一个场所切换LocaleAction类(第6.10 式和第12.4式)
对于新应用,应该尽量使用Struts 1.2。如果你已经有一个Struts 1.1 应用,你将发现Struts 1.2 引入了大量新的和有用的特征。比之于从Struts 1.0 升级至Struts 1.1,从升级Struts 1.2 要容易些,只需要少一些的代码修改。
相关招式
Struts wiki 有一些关于这个升级的详细内容。相关的wiki 页面可以在http://wiki.apache.org/struts/StrutsUpgradeNotes11to124中找到。
第1.3式. 从Struts 1.0迁移至Struts 1.1
问题
你需要将一个基于Struts 1.0的应用迁移到Struts 1.1.
动作分解
使用Struts1.1中对应的文件替换Struts 1.0 JAR 文件、标签库描述符(TLD) 文件、以及XML DTD 文件。如果你有使用Struts标签库绝对URI的JSP 页面,你需要修改它们。使用新的标签库重新编译和构建你的应用,解决兼容性错误。
最后,你需要将原来使用不赞成API的代码修改为使用新的Struts 1.1 API。
变化
Struts 1.1 在Struts 1.0基础上作了较大变化,从功能上讲,基于 Struts 1.0 的应用可以通过使用Struts1.1中的对应文件来替换Struts 1.0 的JAR 和TLD文件来进行迁移,这没什么大的困难。你需要修改所使用的标签库的URI,因为它们在Struta1.1中已经改变;这一般来说需要修改你的 web.xml部署描述符。如果你在JSP中使用绝对URI,这些值也需要修改。Table 1-3列出了标签库URI的改变。
Table 1-3. Struts标签库URI |
Struts 1.0.2 Taglib URI |
Struts 1.1 Taglib URI |
http://jakarta.apache.org/struts/tags-bean-1.0.2 |
http://jakarta.apache.org/struts/tags-bean |
http://jakarta.apache.org/struts/tags-html-1.0.2 |
http://jakarta.apache.org/struts/tags-html |
http://jakarta.apache.org/struts/tags-logic-1.0.2 |
http://jakarta.apache.org/struts/tags-logic |
http://jakarta.apache.org/struts/tags-template-1.0.2 |
http://jakarta.apache.org/struts/tags-template |
Not Available with Struts 1.0.2 |
http://jakarta.apache.org/struts/tags-tiles |
Not Available with Struts 1.0.2 |
http://jakarta.apache.org/struts/tags-nested |
Struts1.1中最明显的改变是Struts 的ActionServlet (org.apache.action.ActionServlet) 和Action类(org.apache.struts.Action)。Struts 1.1 也引入了请求处理器RequestProcessor (org.apache.struts.action.RequestProcessor)的概念。ActionServlet将请求处理委托给请求处理器。在Struts 1.1中,你不再需要一定要扩展ActionServlet来进行定制化;相反,你应该子类化RequestProcessor。如果一个基于 Struts 1.0的应用没有扩展ActionServlet,那么不需要做任何修改就能使用RequestProcessor。如果ActionServlet被子类化了,你却应该扩展RequestProcessor。
另一个主要的增强是Struts的Action。Struts 1.1 引入了一个新方法execute( ), 即其子类应该实现这个方法,而不是原来的perform()方法。Example 1-1展示了一个实现perform()方法的简单Action例子。
Example 1-1. Struts 1.0 Action
package org.apache.struts.webapp.example;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public final class ExampleAction extends Action {
public ActionForward perform(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
try {
ExampleService service = new ExampleService( );
Service.doService( );
}
catch (ServiceException ex) {
throw new ServletException( ex );
}
return (mapping.findForward("success"));
}
}
Example 1-2则是使用Struts1.1的同一个例子。
Example 1-2. Struts 1.1 Action
package org.apache.struts.webapp.example;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
public final class ExampleAction extends Action {
public ActionForward execute (ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
ExampleService service = new ExampleService( );
Service.doService( );
return (mapping.findForward("success"));
}
}
如你所见,基于Struts 1.1的Action, 例外处理不再需要在方法中执行。Struts 1.1 现在支持将例外处理作为框架的一部分。我们将在第9.1式练习这个招数。
你并不是一定要修改你的Actions 来使用execute( )方法,因为Struts 1.1 仍旧支持perform( )方法;但是该方法已经不赞成使用了。
]
|
如果你直接从Struts 1.0 迁移至Struts 1.2, Struts 1.1 中的不赞成因素,比如perform( )方法,已经从Struts 1.2 API中删除了。 |
虽然这个方法将继续发挥作用,但是我们还是建议你尽可能的将你的代码修改来使用execute( )方法。这样可以减少进一步升级到Struts 1.2的难度和工作。更明显的是,这可以使你得到Struts 1.1 的例外处理能力的优势。
参见
第9.1 式Struts 1.1的例外处理。