Malcolm Davis
顾问
2001 年 2 月
内容: |
![]() |
![]() |
简介 |
JSP 就是 servlet |
别在我的 HTML 中使用太多的 Java |
MVC |
MVC Model 2 |
Struts |
详细分析 Struts |
邮件列表样例 |
前后比较 |
Struts 的前景 |
最后的注释 |
参考资源 |
作者简介 |
![]() |
本文介绍 Struts,它是使用 servlet 和 JavaServer Pages 技术的一种 Model-View-Controller 实现。Struts 可帮助您控制 Web 项目中的变化并提高专业化水平。尽管您可能永远不会用 Struts 实现一个系统,但您可以将其中的一些思想用于您以后的 servlet 和 JSP 网页的实现中。
简介
小学生也可以在因特网上发布 HTML 网页。但是,小学生的网页和专业开发的网站有质的区别。网页设计人员(或者 HTML 开发人员)必须理解颜色、用户、生产流程、网页布局、浏览器兼容性、图像创建和 JavaScript 等等。设计漂亮的网站需要做大量的工作,大多数 Java 开发人员更注重创建优美的对象接口,而不是用户界面。JavaServer Pages (JSP) 技术为网页设计人员和 Java 开发人员提供了一种联系钮带。
如果您开发过大型 Web 应用程序,您就理解变化这个词的含义。“模型-视图-控制器”(MVC) 就是用来帮助您控制变化的一种设计模式。MVC 减弱了业务逻辑接口和数据接口之间的耦合。Struts 是一种 MVC 实现,它将 Servlet 2.2 和 JSP 1.1 标记(属于 J2EE 规范)用作实现的一部分。尽管您可能永远不会用 Struts 实现一个系统,但了解一下 Struts 或许使您能将其中的一些思想用于您以后的 Servlet 的 JSP 实现中。
在本文中,我将以一个 JSP 文件为起点讨论该网页的优缺点,该文件中使用的元素可能是您所熟悉的。随后我将讨论 Struts,并说明它是如何控制您的 Web 项目中的变化并提高专业化水平的。最后,我将重新开发这个简单的 JSP 文件,在开发过程中我已顾及到网页设计人员和变化。
一个 JSP 文件就是一个 Java servlet
JavaServer Page (JSP) 文件只是审视 servlet 的另一种方式。JSP 文件的概念使我们能够将 Java servlet 看作一个 HTML 网页。JSP 消除了 Java 代码中经常出现的讨厌的 print()
语句。JSP 文件首先被预处理为 .java
文件,然后再编译为 .class
文件。如果您使用的是 Tomcat,则可以在 work
目录下查看预处理后的 .java
文件。别的容器可能将 .java
和 .class
文件存储在其他位置;这个位置与容器有关。图 1 说明了从 JSP 文件到 servlet 的流程。
(这与 Microsoft 的 Active Server Page (ASP) 明显不同。ASP 被编译到内存中,而不是编译到一个单独的文件中。)
简单的独立 JSP 文件
在小型 JSP 应用程序中,经常会看到数据、业务逻辑和用户界面被组合在一个代码模块中。此外,应用程序通常还包含用来控制应用程序流程的逻辑。清单 1 和图 2 展示了允许用户加入一个邮件列表的一个简单 JSP 文件。
清单 1. join.jsp
-- 一个简单的请求和响应 JSP 文件
<%@ page language="java" %> <%@ page import="business.util.Validation" %> <%@ page import="business.db.MailingList" %> <% String error = ""; String email = request.getParameter("email"); // 是否有电子邮件地址 if( email!=null ) { // 验证输入... if( business.util.Validation.isValidEmail(email) ) { // 存储输入... try { business.db.MailingList.AddEmail(email); } catch (Exception e) { error = "Error adding email address to system. " + e; } if( error.length()==0 ) { %> // 重定向到欢迎页... <% } } else { // 设置错误消息并重新显示网页 error = email + " is not a valid email address, please try again."; } } else { email = ""; } %> |
图 2. 在简单的请求和响应中,JSP 文件设置数据、控制到下一个网页的流程并创建 HTML
这个邮件列表 JSP 文件是一个独立的、自主完成所有任务的模块。未包含在这个 JSP 文件中的仅有代码是包含在 isValidEmail()
中的实际验证代码和将电子邮件地址存入数据库的代码。(将 isValidEmail()
方法分离到可重用的代码中似乎是当然的选择,但我曾见过直接嵌入网页中的 isValidEmail()
代码。单页方法的优点是易于理解,并且最初也易于构建。此外,对于各种图形化开发工具,入门也很容易。
join.jsp
的活动
- 显示打开的输入网页。
- 从表单参数中读取
email
的值。 - 验证
email
地址。 - 如果
email
地址有效:- 将该地址添加到数据库中。
- 重定向到下一个网页。
- 如果
email
地址无效:- 设置错误消息。
- 重新显示含有错误消息的
join.jsp
。
单页方法的后果
- HTML 和 Java 强耦合在一起
JSP 文件的编写者必须既是网页设计者,又是 Java 开发者。其结果通常要么是很糟的 Java 代码,要么是难看的网页,有时甚至 Java 代码和网页都很糟。
- Java 和 JavaScript 的不足
随着网页逐渐变大,很容易想到实现一些 JavaScript。当网页中出现 JavaScript 时,这种脚本就可能与 Java 代码产生混淆。可能产生混淆的一个例子是使用客户端的 JavaScript 来验证email
域。
- 内嵌的流程逻辑
要理解应用程序的整个流程,您必须浏览所有网页。试想一下拥有 100 个网页的网站的错综复杂的逻辑。
- 调试困难
除了很糟的外观之外,HTML 标记、Java 代码和 JavaScript 代码都集中在一个网页中还使调试变得相当困难。
- 强耦合
更改业务逻辑或数据可能牵涉相关的每个网页。
- 美学
在很大的网页中,这编码样式看起来杂乱无章。我过去进行 Microsoft ASP 开发时,我经常看到有 1000 行的网页。即使有彩色语法显示,阅读和理解这些代码仍然比较困难。
请别在我的 HTML 中加入太多的 Java 代码
在清单 1 中,不是 Java 代码中有大量的 HTML,而是在 HTML 文件中有大量的 Java 代码。从这个观点来看,除了允许网页设计人员编写 Java 代码之外,我实际上没做什么。但是,我们并不是一无所有;在 JSP 1.1 中,我们获得一种称为“标记”的新特性。
JSP 标记只是将代码从 JSP 文件中抽取出来的一种方式。有人将 JSP 标记看作是 JSP 文件的宏,其中用于这个标记的代码包含在 servlet 中。(宏的观点在很大程度上是正确的。)出于同样的原因,我不希望在 Java 代码中看到 HTML 标记,我也不希望在 JSP 文件中看到 Java 代码。JSP 技术的整个出发点就是允许网页设计人员创建 servlet,而不必纠缠于 Java 代码。标记允许 Java 程序员将 Java 代码伪装成 HTML 来扩展 JSP 文件。图 3 显示了从 JSP 网页中抽取代码并将它们放入 JSP 标记中的一般概念。
清单 2 是用来说明 Struts 标记的功能的一个例子。在清单 2 中,正常的 HTML
模型 -- 会话状态JoinForm
扩展了 ActionForm
并包含表单数据。本例中的表单数据只有电子邮件地址。我已为电子邮件地址添加了一个写方法和读方法,以供框架访问。为了便于说明,我重写了 validate()
方法,并使用了 Struts 的跟踪功能。Struts 将创建 JoinForm
并设置状态信息。
模型 -- 业务逻辑
如前所述,Action
是控制器和实际业务对象之间的接口。JoinAction
包装了对 business.jar
的调用,这些调用最初在 join.jsp
文件中。JoinAction
的 perform()
方法在清单 5 中列表。
清单 5. - JoinAction.perform()
public ActionForward perform(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // 抽取我们将会用到的属性和参数 JoinForm joinForm = (JoinForm) form; String email = joinForm.getEmail(); ActionErrors errors = new ActionErrors(); // 存储输入.... try { business.db.MailingList.AddEmail(email); } catch (Exception e) { // 记录日志,打印栈 // 将错误回显给用户 errors.add("email",new ActionError("error.mailing.db.add")); } // 如需任何消息,请将指定的错误消息键保存到 // HTTP 请求中,以供 标记使用。 if (!errors.empty()) { saveErrors(request, errors); // 返回到初始表单 return (new ActionForward(mapping.getInput())); } // 将控制权转交给 Action.xml 中指定的 'success' URI return (mapping.findForward("success")); } |
注:perform()
返回一个称为 ActionForward
的类,该类通知控制器下一步该执行什么操作。在本例中,我使用从控制器传入的映射来决定下一步的操作。
控制器
我已修改了 JSP 文件,并创建了两个新类:一个类用来包含表单数据,一个类用来调用业务包。最后,我通过修改配置文件 struts-config.xml
将它们整合起来。清单 6 显示了我添加的 action 元素,这个元素用来控制 joinMVC.jsp
的流程。
清单 6. Action 配置
action 元素描述了从请求路径到相应的 Action
类的映射,应该用这些类来处理来自这个路径的请求。每个请求类型都应该有相应的 action 元素,用来描述如何处理该请求。对于 join 请求:
joinForm
用来容纳表单数据。- 因为 validate 被标记为 true,所以
joinForm
将试图进行自我验证。 web.mailinglist.JoinAction
是用来处理对这个映射的请求的 action 类。- 如果一切顺利,该请求将转到
welcome.jsp
。 - 如果出现业务逻辑故障,流程将返回到
joinMVC.jsp
,这是最初发出请求的网页。为什么会这样呢?在清单 6 的 action 元素中,有一个称为input
的属性,其值为"/joinMVC.jsp"
。在我的JoinAction.perform()
(如清单 5 所示)中,如果业务逻辑失败,perform()
就返回一个ActionForward
,并以mapping.getInput()
作为参数。本例中的getInput()
是"/joinMVC.jsp"
。如果业务逻辑失败,它将返回到joinMVC.jsp
,这是最初发出请求的网页。
使用 Struts 前后的比较
正如我们在图 9 中所看到的那样,复杂性和层都有显著增加。不再存在从 JSP 文件到 Service 层的直接调用。
Struts 的优点
- JSP 标记机制的使用
标记特性从 JSP 文件获得可重用代码和抽象 Java 代码。这个特性能很好地集成到基于 JSP 的开发工具中,这些工具允许用标记编写代码。
- 标记库
为什么要另发明一种轮子,或标记库呢?如果您在库中找不到您所要的标记,那就自己定义吧。此外,如果您正在学习 JSP 标记技术,则 Struts 为您提供了一个起点。
- 开放源码
您可以获得开放源码的全部优点,比如可以查看代码并让使用库的每个人检查代码。许多人都可以进行很好的代码检查。
- MVC 实现样例
如果您希望创建您自己的 MVC 实现,则 Struts 可增加您的见识。
- 管理问题空间
分治是解决问题并使问题可管理的极好方法。当然,这是一把双刃剑。问题越来越复杂,并且需要越来越多的管理。
Struts 的缺点
- 仍处于发展初期
Struts 开发仍处于初级阶段。他们正在向着发行版本 1.0 而努力,但与任何 1.0 版本一样,它不可能尽善尽美。
- 仍在变化中
这个框架仍在快速变化。Struts 1.0 与 Struts 0.5 相比变化极大。为了避免使用不赞成使用的方法,您可能隔一天就需要下载最新的 Struts。在过去的 6 个月中,我目睹 Struts 库从 90K 增大到 270K 以上。由于 Struts 中的变化,我不得不数次修改我的示例,但我不保证我的示例能与您下载的 Struts 协同工作。
- 正确的抽象级别
Struts 是否提供了正确的抽象级别?对于网页设计人员而言,什么是正确的抽象级别呢?这是一个用 $64K 的文字才能解释清楚的问题。在开发网页的过程中,我们是否应该让网页设计人员访问 Java 代码?某些框架(如 Velocity)说不应该,但它提供了另一种 Web 开发语言让我们学习。在 UI 开发中限制访问 Java 有一定的合理性。最重要的是,如果让网页设计人员使用一点 Java,他将使用大量的 Java。在 Microsoft ASP 的开发中,我总是看到这样的情况。在 ASP 开发中,您应该创建 COM 对象,然后编写少量的 ASP 脚本将这些 COM 对象联系起来。但是,ASP 开发人员会疯狂地使用 ASP 脚本。我会听到这样的话,“既然我可以用 VBScript 直接编写 COM 对象,为什么还要等 COM 开发人员来创建它呢?”通过使用标记库,Struts 有助于限制 JSP 文件中所需的 Java 代码的数量。Logic Tag 就是这样的一种库,它对有条件地生成输出进行管理,但这并不能阻止 UI 开发人员对 Java 代码的狂热。无论您决定使用哪种类型的框架,您都应该了解您要在其中部署和维护该框架的环境。当然,这项任务真是说起来容易做起来难。
- 有限的适用范围
Struts 是一种基于 Web 的 MVC 解决方案,所以必须用 HTML、JSP 文件和 servlet 来实现它。
- J2EE 应用程序支持
Struts 需要支持 JSP 1.1 和 Servlet 2.2 规范的 servlet 容器。仅凭这一点远不能解决您的全部安装问题,除非使用 Tomcat 3.2。我用 Netscape iPlanet 6.0 安装这个库时遇到一大堆问题,按理说它是第一种符合 J2EE 的应用程序服务器。我建议您在遇到问题时访问 Struts 用户邮件列表的归档资料(请参阅参考资源)。
- 复杂性
在将问题分为几个部分的同时也引入了复杂性。毫无疑问,要理解 Struts 必须接受一定的培训。随着变化的不断加入,这有时会令人很沮丧。欢迎访问本网站。
- 在何处...
我还能指出其他问题,例如,控制器的客户端验证、可适用工作流程和动态策略模式在什么地方?但是,目前这太容易成为吹毛求疵的问题,有些问题是无关紧要的,或者说应该对 1.0 发行版提这些问题。随着 Struts 小组的不断努力,到您阅读本文时 Struts 说不定已经有了这些功能,或者它很快就会具有这些功能。
Struts 的前景
在这个软件开发的新时代,一切都变得很快。在不到 5 年的时间内,我已经目睹了从 cgi/perl 到 ISAPI/NSAPI、再到使用 VB 的 ASP、一直到现在的 Java 和 J2EE 的变迁。Sun 正在尽力将新的变化反映到 JSP/servlet 体系结构中,正如他们对 Java 语言和 API 所作的更改一样。您可以从 Sun 的网站获得新的 JSP 1.2 和 Servlet 2.3 规范的草案。此外,一个标准 JSP 标记库即将出现;有关这些规范和标记库的链接,请参阅参考资源。
最后的注释
Struts 使用标记和 MVC 解决了某些重大问题。这个方法有助于提高代码的可重用性和灵活性。通过将问题划分为更小的组件,当技术空间或问题空间中出现变化时,您就有更多的机会重用代码。此外,Struts 使网页设计人员和 Java 开发人员能将精力集中于自己最擅长的方面。但是,在强健性增强的同时,也意味着复杂性的增加。Struts 比简单的单个 JSP 网页要复杂得多,但对于更大的系统而言,Struts 实际上有助于管理复杂性。另外,我并不想编写自己的 MVC 实现,而只想了解一个这样的实现。不管您是否会使用 Struts,回顾这个 Struts 框架(对不起,应该是库)都会使您对 JSP 文件和 servlet 的特性、以及如何将它们组合起来用于您的下一个 Web 项目有更好的了解。正像翼间支柱是机翼结构中不可缺少的一部分一样,Strut 也可能成为您下一个 Web 项目的不可缺少的一部分。
参考资源
- 您可以下载本文中所用的代码:business.zip 用来构建
business.jar
文件,joinStruts.zip 用来构建joinStruts.war
文件。
- 有关 Struts 的文档、安装说明和下载,请参阅 Struts 的主页。
- 您可以从 Jakarta 项目下载最近的 Struts 实现。
- 如想入门,我建议您下载 JSP servlet 的参考实现 Tomcat 3.2。如果您使用 Tomcat 以外的容器,请访问 Struts 用户邮件列表,以获得在您特定的环境中安装 Struts 的帮助。
- 要获得文档或安装指南中未包含的信息,请参阅 Struts 用户邮件列表归档文件。
- Apache 邮件列表归档文件包含与其他资料一起归档的 Struts 信息。
- 要了解将构建和测试过程组合在一起的详细信息,请参阅 "Incremental development with Ant and JUnit"。
- 请参阅 Proposed Final Draft for Java Servlet 2.3 and JavaServer Pages 1.2 Specifications.
- 请参阅 Standard Tag Library for JavaServer Pages, JSR #000052。
作者简介
Malcolm G. Davis 住在阿拉巴马州伯明翰市,他在自己的咨询公司当总裁。他自称是一名 Java 传道者。他在宣传 Java 的优点的闲暇之余,他会去长跑,或者与自己的孩子一起玩。可以通过 malcolm@nuearth.com 与 Malcolm 联系。