使用多个Struts配置文件

关于这份教程

这份教程向 Java™ Web 开发人员介绍如何将 Apache Struts 设置为使用多个配置文件。将学习不同的Struts 配置文件的目的和结构,以及使用更小文件的合理性。将经历把现有 Struts 应用程序的大型、复杂的 struts-config.xml 文件分成按功能组织的多个配置文件的步骤。处理的是更小、更具可管理性的部分,可以让 Struts 应用程序更容易管理和重新配置,也有助于在出现问题的时候更容易找出故障点。还将学习其他一些可以改善 Struts 配置的清理类型。

谁应当阅读这份教程?

这份教程针对的 Java Web 开发人员至少应当有一些 Apache Struts 和 Apache Tomcat servlet 引擎的基本经验。应当知道如何启动和停止 Tomcat、安装 Struts 和部署 Struts 应用程序。

也可以使用 Tomcat 之外的 servlet 引擎(Struts 和其他 servlet 引擎也配合得很好),但是要知道如何设置和配置。这份教程假设正在使用 Tomcat,没有提供关于非 Tomcat 配置的额外细节。请参阅 参考资料 获得在非 Tomcat 的 servlet 容器上安装 Struts 的 Struts 文档链接。

Struts 使用 XML 配置文件,所以读者应当熟悉 XML。但是不需要是配置专家,因为在进入对配置文件进行分解的细节之前,我将提供一些 Struts 如何使用这些文件的基本信息。

前提条件

需要一台安装了 servlet 引擎(例如 Apache Tomcat)的机器或 ISP。强烈建议在本地的开发计算机上或者在非生产的 ISP 帐户上执行这份教程介绍的过程。不要在为成千上万用户服务的机器上做试验,因为将要对 servlet 容器做修改(如果 servlet 引擎不能自动重新装入修改,还需要手工地重启容器)。

我使用的是 Mac OS X 上的 Java 平台 5.0 版,但对这份教程来说这两者并不是必需的。Java 1.4 也工作得很好,但是可能会在教程示例中看到一些在以前的 JVM 中没有看到的编译警告。建议至少要使用Tomcat 的 5.0 版;5.5 更好。还需要一个自己选择的编辑器来处理 XML。

显然,需要安装了 Struts;如果不清楚如何安装 Struts,请参阅 Using the Struts Validator 的附录,那里一步一步地详细介绍了完整过程。也可以参阅这份教程中的 参考资料 获得多个有帮助的链接。

最后,在这份教程中,要配置 Struts 在线银行应用程序,在由 Chuck Cavaness 编写的 Programming Jakarta Struts 中描述了这个程序(请参阅 参考资料),所以还需要得到并安装它:

◆访问 http://examples.oreilly.com/0596006519/

◆下载 jakarta2ed-source.zip。

◆展开 ZIP 文件。

◆把 banking.war 放到 servlet 引擎的 Web 应用程序目录。

配置问题

我仍然记得那个时候:应用程序的配置 意味着保存连接数据库所需的用户名和口令的一两个文本文件。应用程序会读入这些文件(用手写的代码),然后基本上忘记了这些文件曾经存在过。这就是我当作真正傻瓜化配置的东西。不要笑 —— 在世界上有一些开发人员用专用格式保存应用程序需要的数据,用自己专用的代码读取这些值,而且从来不组织(或重新组织)这些文件。您可能会尴尬地想起自己最近写的一个应用程序正好做了同样的事。但是,这没什么;到了这一节末尾,对于配置您将了解得更多:为什么配置很重要、配置如何提供帮助。

应用程序与配置文件

在这份教程中使用应用程序配置数据 这个术语或仅使用配置 这个术语时,我指的是每个应用程序都常用的一种特定数据。在设置应用程序时使用这种数据;它控制应用程序如何设置某个选项或特性。在某些情况下,这个数据可能就是一个简单的用户名和口令组合,可能用于连接数据库或目录服务器。在其他情况下,它可能规定应用程序是否应当运行在调试模式、是否应当记录用户的请求、是否让管理员和经理登录。在更复杂的应用程序中,配置数据可能规定应用程序应当启动哪个模块,甚至指定这些模块本身应当如何表现。

但是,在所有这些情况中,这种数据并不经常变化。这些数据是相当静态的(应用程序用来与数据库对话的用户名和口令是经常改变,还是只用一个呢?),而且通常不是由用户和潜在客户输入的数据。更重要的是,它也不是应用程序的业务逻辑生成或使用的数据,例如订单细节或收费额。相反,它只是应用程序自己在幕后使用的数据。

这种数据类型 对所有应用程序来说是公共的;而数据本身则不是。应用程序的配置数据在地区之间、公司之间,甚至在同一公司的同一应用程序的不同实例之间都会有不同。但是,要正确地设置自己,应用程序经常使用一些 不需要经常变化、甚至根本不变化的数据集。如果可以把这些数据分离出来,把它从代码中取出,放入静态资源(例如配置文件)中,那么就方便多了。当特殊情况出现时,数据库连接确实需要变化,那么只要更新用户名和口令,可能要重新启动用程序,然后就可以安心上床睡觉了。因为,在现实世界中,这类变化通常发生在早晨 4:00,是不是?谁想在午夜的时候,在代码中痛苦地寻找并更新硬编码的字符串,然后重新编译呢?好的配置通常意味着只需修改文本文件中的一两个值,然后就回去睡觉。

声明式编程

需要了解的一种特殊类型的配置是声明式编程。声明式编程的想法是:声明程序的某些方面,然后让某些引擎把这些声明变成实际的代码。您确实是在编程,但是没有实际地编写代码 —— 这是个奇怪的,但是很重要的范式。清单 1 是一个简单的示例,它是一个简单的 struts-config.xml 文件的一部分:

清单 1. struts-config.xml 中的声明式编程

<struts-config></struts-config>

<action-mappings></action-mappings>

           type="pizza.delivery.security.LoginAction"

name="loginForm"

scope="request"

input="/loginForm.jsp">

<exception key="error.failedLogin"></exception>              type="pizza.exception.InvalidPasswordException"

path="/loginForm.jsp" />

<exception key="error.failedUsername"></exception>              type="pizza.exception.InvalidUsernameException"

path="/loginForm.jsp" />





在这个代码段中,定义了两个异常,对应于用户试图登录时可能发生的两个特定问题。第一个处理错误的口令,第二个处理错误的用户名。在每种情况下,动作都被引导到预定义的异常类,这些类在应用程序的其他地方编码。在这里值得注意的是,这就是处理这些错误所需要做的全部工作。不需要以自己的形式编写任何检查错误用户名的代码,然后手工抛出 InvalidUsernameException 异常;对于错误口令来说也是一样。Struts 会替您做这些工作,因为您已经声明了出错情况,还声明了出错时要做什么。通过这种方式,可以把某些一般需要进行编程的工作转到配置数据中。

 

 

Struts 提供了许多更具声明性的选项,从定义错误到定义实际表单、错误消息、国际化,甚至更多。通过简单地指出可能发生什么 —— 例如在表单上应当有哪个字段,Struts 能够创建并把资源引导到正确的位置。这是让配置能够发挥最佳作用的方式。要添加新的错误情况(可能想在三次提供错误口令时锁定帐户),只需要编写一些 XML;不需要大量编码、编译和测试。这非常酷,对于向应用程序添加小特性来说极为 有用,不会破坏成百上千行现有的已经测试过的稳定代码。

对声明式编程的进一步讨论远远超出了这份教程的范围。但是,声明式编程已经成为如此重要的概念(在Struts 中和一般意义的编程中都是这样)所以我不得不稍做讨论。所以这一点是免费的!

Struts 配置:概述

Struts 配置的内容要比我迄今为止展示的这一点儿多得多。在下一节 Struts 配置 101 中将学习更多内容,但是这里是一个快速概述。

首先,Struts 是一个基于 servlet 的 Web 应用程序,所以它需要所有 Web 应用程序都使用的标准web.xml 描述符,需要 MessageResources.properties 表示消息和应用程序范围内的文本属性。然后,还有特定于 Struts 的配置文件:

◆struts-config.xml 处理基本的 Struts 配置任务。在这里定义全局常量和表单、动作,以及其他能够想到的事情。

◆validator-rules.xml 定义的规则可以用来确保表单数据的完整性,例如邮编的最小长度或者电话号码的格式。

◆validation.xml 处理验证规则与特定表单和动作的链接,把验证规则和 struts-config.xml 文件中定义的数据粘合在一起。

代码本身,加上您自己创建的保存应用程序特定数据的配置文件,已经有许多需要跟踪的内容了。而且,由于采用声明式编程,配置文件包含的不仅仅是几个用户名和口令。在定义表单、动作、出错和基本启动选项的时候,就得到了一个巨大的 struts-config.xml。当然,这就是这份教程的主题。

共享负载

程序员会迅速地采用“分而治之”的技术进行开发,但是在实践他们所宣扬的技术的时候,却经常失败。编写一个可以分解成多个模块、函数或类似东西的程序,需要付出努力。但是这种努力是值得的。把代码分解得越细,就能让更多的开发人员参与进来(可能把比较容易的部分委托给初级开发人员,而把比较难的部分委托给更有经验的开发人),并(至少在理论上)加速项目的开发。

“分而治之”对配置数据也是个聪明的设计。把大型文件(例如 struts-config.xml)分解成许多小的、易于管理的部分是值得的。可以按照功能或按照模块管理更小的文件,从而创建迷你应用程序,每个小程序比起巨大的单个应用程序来说,管理起来极为简单。

Struts 配置 101

这一节介绍 Struts 配置文件并分解它们的内容,至少是在一个高的层次上。如果您是一个有经验的 Struts 开发人员,那么这里的多数内容可能就是回顾。但是,快速地重新读一遍还是值得的,因为在开始把它们分解成更小部分的之前,需要理解这些文件是如何组织的。新的 Struts 开发人员可以用这一节作为 Struts 配置的入门。这一节并不提供成为 Struts 大师所需要的所有信息,但是您肯定会找到在自己的编程中可能没有机会使用的一些配置技巧。

找到 Struts 的配置文件

下载了示例 Struts 应用程序之后(请参阅 前提条件),请导航到 WEB-INF/ 目录。(如果还没有这么做,需要用 jar 命令展开示例应用程序的 WAR 文件。)在这个目录中可以发现多个文件,我们主要侧重于两个文件:struts-config.xml 和 web.xml。struts-config.xml 文件是特定于 Struts 的配置文件,它告诉 Struts 如何配置自己,应用程序的资源在哪儿,报告什么错误消息,等等许多。web.xml 文件是标准的 Web 应用程序部署描述符,主要用来向 servlet 引擎提供一些通用的 servlet 信息。

Struts 开发中大部分时间要处理 struts-config.xml,这也是在这份教程中花费许多时间的地方。但是,在了解如何对这个文件进行分解之前,需要透彻地理解这些配置文件做的工作以及它们的功能是如何组织的。如果理解了这些文件的结构 —— 特别是 struts-config.xml —— 那么就能够用一种符合逻辑的、不会把应用程序搞糟的方式分解配置数据。

使用 struts-config.xml

struts-config.xml 文件位于(至少目前是)Struts 示例 Web 应用程序的 WEB-INF/ 目录。请打开这个文件看看;清单 2 列出了它的一部分作为参考:

清单 2. struts-config.xml 的一部分

<!---->

"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"

"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config></struts-config>

<form-beans></form-beans>

<form-bean name="loginForm"></form-bean>       type="com.oreilly.struts.banking.form.LoginForm"/>


name="accountInformationForm"

dynamic="true"

type="org.apache.struts.action.DynaActionForm">

<form-property name="accounts" type="java.util.ArrayList"></form-property>




name="accountDetailForm"

dynamic="true"

type="org.apache.struts.action.DynaActionForm">

<form-property name="view"></form-property>        type="com.oreilly.struts.banking.view.AccountDetailView"/>



<global-exceptions></global-exceptions>


key="global.error.invalidlogin"

path="/login.jsp"

scope="request"

type="com.oreilly.struts.banking.service.InvalidLoginException"/>

<global-forwards></global-forwards>

<forward name="Login" path="/login.jsp"></forward>

<forward name="SystemFailure" path="/systemerror.jsp"></forward>

<forward name="SessionTimeOut" path="/sessiontimeout.jsp"></forward>

<action-mappings></action-mappings>


path="/login"

type="com.oreilly.struts.banking.action.LoginAction"

scope="request"

name="loginForm"

validate="true"

input="/login.jsp">

<forward name="Success"></forward>       path="/action/getaccountinformation" redirect="true"/>

<forward name="Failure"></forward>       path="/login.jsp" redirect="true"/>



<!---->


contentType="text/html;charset=UTF-8"

debug="3"

locale="true"

nocache="true"/>

<message-resources null="false" parameter="BankingMessageResources"></message-resources>

 

在清单 2 中的这几个基本部分,每个都有自己的用途:

◆文档头:这是 XML 声明和 DOCTYPE 语句。这些不是配置数据,而是特定于 XML 和验证的。对于每个Struts 应用程序来说,它们都是一样的。

◆数据源:这一部分嵌套在 struts-config 根元素中,在 data-sources 元素内。每个数据源由一个 data-source 元素代表,定义应用程序可以使用的数据库连接。虽然在示例应用程序中没有定义数据源,但是在大多数企业应用程序中数据源是很常见的。

◆表单 bean:这一部分说明应用程序使用的表单 bean。用表单 bean 可以说明某类表单提交的信息,避免为所有的表单和 Java 服务器页面(JSP)编写 JavaBean 代码。这一部分由 form-beans 表示,每个 bean 由一个 form-bean 元素表示。

◆全局异常:在表单 bean 之后是全局异常,嵌套在 global-exception 元素内。应用程序的任何部分都可以使用这些异常。例如,如果用户超时或输入错误的口令,那么应用程序的许多地方可能需要发出错误。这类全局异常对于在大型企业应用程序中减少重复代码并统一错误消息来说非常好。

◆全局转发:接下来是全局转发,在 global-forwards 元素内。每个转发均由一个 forward 元素表示,提供了应用程序范围内的到达特定页面的方式。可以用通用的错误处理页面,而且使用全局转发意味着所有资源都引用这个页面的同一对象。更好的是,如果改变了出错页面,那么可以在这里修改它的 URL,那么所有的资源就会立即利用新路径。(这就是配置文件的美妙之处!)

◆动作映射:在处理完异常和转发之后,要定义应用程序的动作。每个动作均由 action 元素代表,全嵌套在 action-mappings 元素内。这些动作将特定的 URI(例如 /logout)映射到 Struts Action 类(例如 com.oreilly.struts.banking.action.LoginAction)。同样,只要在配置文件中进行修改,就可以轻易地修改动作的实现。

◆控制器指令:在 Struts 1.1 和以后的版本中,可以设置 Struts 控制器的许多属性(用 controller 元素)。Struts 现在对控制器使用声明式配置,所以配置文件的这一部分比起编写自己的控制器 Servlet(这可不是个有趣的任务)来说方便得多。

◆消息资源:如果选择为应用程序使用资源束,可以传递进参数,对如何使用资源进行配置(例如,如果提供了错误的键,应当发生什么)。这个配置要通过 message-resources 元素进行。

◆插件:可以通过 plug-in 元素,引用应用程序想要使用的任何插件。如果您阅读过使用 Struts 验证器的教程(请参阅 参考资料),可能已经熟悉了这项功能。实际上,Struts 验证器可能是最常用的 Struts 插件。

这算不上是对 struts-config.xml 的全面分析,但是提供了对基本部分的认识。在把配置分解到多个文件时,这些知识很重要,因为需要有些方法来理清思路。例如,可以按照逻辑分解配置,把与程序程序的一个部分有关的所有动作、表单 bean 和错误放在一个文件中,再把应用程序另一个部分的配置组件放在另一个文件中。或者,可能想按照组件来分解文件,把所有动作放在一个文件中,把所有异常放在另一个文件中。不管做什么选择,都需要理解这个组织方式才能做出良好的决策。

使用 web.xml

需要让 servlet 引擎知道如何处理 Struts 应用程序。WEB-INF/web.xml 是做这件事的地方。清单 3 显示了示例 Struts 应用程序的 web.xml 文件的一部分:

清单 3. 部分 web.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE web-app

PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

<servlet>

<servlet-name>banking</servlet-name>

<servlet-class>org.apache.struts.action.ActionServlet

</servlet-class>

<init-param>

<param-name>config</param-name>

<param-value>/WEB-INF/struts-config.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<!-- Other entries -->

</web-app>

清单 3 中的大部分都与 Struts 配置数据的定位有关。config 参数使 Struts 知道在哪里找到自己的配置数据。(在这个示例中,位置就是 WEB-INF/struts-config,不过很快就会发生变化。)清单 3 中剩下的大部分对于 servlet 来说相当普通;Struts ActionServlet 指定一个名称 —— banking —— 然后在启动时装入。

示例应用程序的配置还包含其他几个项目,多数与标记库有关。虽然这些也很有趣,但不是这份教程的重点,所以我把它们留给您自己研究。

其他

我现在要快速地介绍其他几个配置项目,只是为了完成这个对 Struts 配置的简短分析:

◆文档类型定义(DTD):DTD 实际并不是配置数据,但是它们位于 WEB-INF/ 目录,与 struts-config.xml 和 web.xml 在一起。但它们是重要的文件,因为这些配置文件要引用它们。(在这个示例中,struts-config.xml 引用了 struts-config_1_1.dtd,而 web.xml 引用了 web-app_2_3.dtd。)

◆标记库描述符(TLD):TLD 定义标记库,把标记名称与处理标记功能的 Java 类关联起来。这些也很重要;因为如果不在某些地方使用标记库,将会发现编写真正的大型 Struts 会相当冗长和累人。

◆属性文件:在 WEB-INF/classes/ 目录中,通常会发现至少一到两个属性文件。这些是应用程序使用的Java 属性文件(名称-值对,比如 “title.login=Struts Online Banking - Account Login”)。示例应用程序有一个用来进行消息传递的属性文件(如果想提供本地化资源,就可能有更多的属性文件)和一个用来进行日志记录的属性文件。

需要再次指出的是,不必在这些领域中都成为专家;您可能以前只使用过这些项目中的少数几个,或者是第一次看到这些项目。只要对于每个文件影响 Struts 的方式有基本的理解,具体来说,对于 Struts 的配置有基本理解,就可以继续主要的工作:把配置文件分解到多个文件中。

从一到多

现在理解了配置文件中的内容,可以看看如何重新进行组织了。在这一节,将把 struts-config.xml(对于中等大小的应用程序来说,通常在 100 到 1000 行之间)分解成更具可管理性的文件。

组织配置文件

首先,需要决定是要按功能,还是按类型 来分解文件。

按功能分解文件意味着要把配置文件分解成多个文件,每个文件处理应用程序的一些特定部分。例如,可能有一个文件定义了与创建新帐户有关的所有数据,另一个则处理用户登录。

按照类型分解文件意味着对于 struts-config.xml 中的每种数据类型都有一个文件。所以可能有一个文件针对所有的 form-bean 元素,另一个针对 action-mapping 和 action 元素等等,也就是针对在 struts-config.xml 中可能出现的各种元素,都有文件与之对应。

稍停一会儿,想想哪种方式会更好呢?所有 A 型性格的人可能喜欢把元素分解到独立文件中。那么当需要添加新的 Action 时,毫无疑问应当将它放在哪里 —— 只要把它放在 struts-actions.xml(或者其他什么叫作 action-related 的文件中)。再也不需要多费时间去想要在哪里安放一个新的 global-forward。但是,这是个极为糟糕的想法!

要理解这种方法的问题,请考虑一下如何向 Struts 应用程序中添加功能(这句话中的词语对于哪种方法更好应当提供了线索):

◆规划出要添加什么。

◆开发一个表单来接受需要的数据(并带有对应的 form-bean)。

◆定义新表单需要的 forward(或者也可能是 global-forward)。

◆编写一个 action-mapping 来处理表单(并添加一两个 action 元素)。

◆如果需要,在一个 exception 元素(或两个)中定义可能需要的出错消息。

通过这些步骤,已经很清楚:要添加一个功能,总是需要添加至少 两个元素到 struts-config.xml 中(一个 form-bean 和一个 action)。这样,如果根据元素类型对 struts-config.xml 进行分解(每个文件只容纳一种类型的元素),那么需要为新功能打开至少两个文件。如果需要处理两个或更多的配置文件,而仅仅是为了添加一个功能,那么不会得到什么好处。这还会增加出错的可能性;需要为这个功能在多个文件之间保持一个公共命名方案,而且如果在一个文件中做了修改,那么可能需要在其他文件中也做类似的修改,这可不是件好事!

更好的文件分解方式是按照功能。可能把所有与身份验证有关的 action、forward、exception 等等放在叫作 struts-authentication.xml 的文件中。如果添加新的与身份验证有关的功能(可能想开发一个“忘记口令”表单),那么可以只打开这一个文件,并把需要的内容添加进去。这可以把相关的功能放在一起,消除了在多个文件间统一命名方案的需要,而且即使是最没有经验的程序员也容易理解。这就是这份教程余下的部分中使用的方法,而且对于所有 类型的应用程序,我都强烈推荐这个方法。

 

分解和命名配置文件

既然现在已经决定了按照功能进行分解,所以差不多 是采取行动的时候了。但是还需要多做一点儿准备和计划。首先,对 struts-config.xml 文件做一个备份。不应当冒着把事情搞砸却不能容易地把修改恢复的风险。在处理 web.xml 时,也要做个备份。

有了一些备份的保障之后,下一步就是确定对应用程序来说确实是 全局性的功能。例如,可能有一个与出错有关的 action 或 forward,确实是在整个应用程序范围内使用的;也就是说,所有类型的表单都用这一套配置处理出错。请对所有看起来要涉及整个应用程序的代码行做个标注。

看看有了多少行。如果严格地确定这些真正全局的数据,那么通常只会有 10 行或 15 行。如果觉得有20 行、30 行或者更多行属于整个应用程序公共的,那么再查一遍,看看是否可以缩小范围。而且,请记住这些是整个应用程序直接 使用的数据。例如,可能标记了与错误处理有关的所有行;可能有多个路径、两个动作、一个资源束和一个数据库连接,所有这些都协助处理错误。第一眼看上去,可能这些都是公共资源。但是,它们中的大多数并不是由应用程序直接使用的;实际上,通常只有一个 forward 把用户带到出错页面或者表单。然后在用户已经被重定向之后,才用剩下的这些资源处理错误。所以只有 forward 才是真正公共的;剩下的行可以移到一个特定于错误处理的文件中。通过这种方式,可以把内容缩减到少于 20 行。

为 struts-config.xml 另做一份拷贝(如果愿意,可以把它叫作 struts-config-all.xml)。然后,在主 struts-config.xml 中,删除标记的所有行。最终结果应当是一个非常苗条的 struts-config.xml 文件,只包含应用程序范围内的数据。请在 struts-config-all.xml 中,删除留在 struts-config.xml 中的这些行。现在,在 struts-config.xml 和 struts-config-all.xml 中,有了应用程序的每一行配置数据(没有重复行)。

接下来,开始把 struts-config-all.xml 中的内容分解到相关的功能片断中。先从大段的功能开始。下面关于分组的几个好建议可以应用到任何应用程序:

◆身份验证和安全性(也记录用户的登录/退出)

◆错误处理

◆帮助/信息性数据

除此之外,当然可以有特定于应用程序的分类。可能有与管理用户、添加帐户、下订单、运货或其他功能有关的分组。自己仔细判断,并让这些初始分类尽可能大。对于每个分组,创建叫作 struts-functionality-grouping.xml 的新文件。这样,就会得到 struts-authentication.xml、struts-error.xml、 struts-shipping.xml 之类的文件。一直使用 struts- 前缀是个好主意,这样就可以轻易地看出哪个配置文件与 Struts 相关(还有一个好处是,目录列表可以把这些文件分组在一起)。

接下来,对每个文件中的行做个计数。如果选择主要分组时做得比较好,那么每个文件的大小通常在 30 到 300 行之间。(这个范围分布很广,但实际取决于应用程序的组织方式)。如果这些文件确实非常非常小(10 或 15 行),那么如果有可能,可以考虑把一些小的分组合并在一起。维持 50 个都是 20 行的文件,并不比维持一个 1000 行的文件好多少;这里的想法是让事情更简单,而不仅仅是变个样儿。在另一方面,如果仍然有文件的大小是 300、400 或 500 行,请考虑对这些大文件重复以上过程,把它们分成更小的功能片断。

我们的目标是在大文件和大量 文件之间寻找一个良好的平衡。理想情况下,最终应当得到 5 到 10 个文件,每个文件有 50 到 100 行配置数据。一旦实现了这一点,那么就得到了一个真正能够管理的应用程序,而且还能让多个团队在上面工作。(安全人员只负责 struts-authentication.xml,编写帮助文档的小组只处理 struts-help.xml,以此类推。)不论应用程序有多简单或多复杂,比起一个巨大的配置文件,这都是一个巨大的进步。

引用多个配置文件

配置数据分解到多个文件之后,就完成了任务中最艰难的部分。现在,只需要让 Struts 知道这些文件即可。

确保做这项工作时应用程序没有运行。请打开 web.xml(已经备份过)。应当看到像清单 4 那样引用 struts-config.xml 的几行:

清单 4. web.xml 引用 struts-config.xml

<servlet>

<servlet-name>banking</servlet-name>

<servlet-class>org.apache.struts.action.ActionServlet

</servlet-class>

<init-param>

<param-name>config</param-name>

<param-value>/WEB-INF/struts-config.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

对于使用过 Struts 很长时间的人来说,清单 4 看起来应当很熟悉。只要做一个简单的修改,就可以引用额外的配置文件,如清单 5 所示:

清单 5. web.xml 引用多个配置文件

<servlet>

<servlet-name>banking</servlet-name>

<servlet-class>org.apache.struts.action.ActionServlet

</servlet-class>

<init-param>

<param-name>config</param-name>

<param-value>/WEB-INF/struts-config.xml,

/WEB-INF/struts-authentication.xml,

/WEB-INF/struts-help.xml

</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

有多少文件就可以添加多少,中间用逗号分隔。不要忘记包含每个文件的完整路径(相对于 Web 应用程序的根);除此之外,再简单不过了。重新启动应用程序,就万事俱备了。

使用来自文件而不是默认 struts-config.xml 的资源也再简单不过了。在内部,Struts 应用程序会把所有配置文件装配成单一的内存表示。一旦应用程序运行起来,就不知道配置数据使用了多少个文件。不需要修改任何一行代码。

 

添加配置数据

另一个值得考虑的问题是如何把配置数据添加 到这个设置中。通常有以下三个基本场景:

◆要向一个已经确定的分组添加一个非常具体的功能。例如,可能向帮助系统添加一个新屏幕或链接。

◆要添加拥有自己分组的大型功能集。例如,要向应用程序添加一个全新的、自包含的用户管理系统。

◆要添加横切应用程序的功能,它会影响公共数据和一个或多个分组。例如,要添加新的错误处理系统,

它会影响全局数据(有一个新的 forward,应用程序的所有部分都会用到它)、错误处理分组和登录分组。

在第一种情况下,只需要把数据添加到特定于分组的一个文件中,例如 struts-help.xml。这通常是最简单的添加,因为操作的文件已经存在,而且只需要处理一个文件。

在第二种情况下,仍然处理一个文件,但是是一个新文件。所以可以创建 struts-manageUsers.xml 并输入新的配置数据。但是,然后需要停止应用程序,打开 web.xml,添加新的配置文件。所以,虽然仍然是在单独的文件中进行隔离的修改,但是必须处理 web.xml 文件以反映新的修改。

在第三种情况下,必须额外小心。需要修改 struts-config.xml(针对公共数据)和受到影响的其他文件,例如 struts-error.xml 和 struts-authentication.xml。不需要编辑 web.xml,但是需要重新启动应用程序,所以这类修改要最当心。

不论做什么类型的修改,都要防备“快速修复”综合症。您会发现,打开 struts-config.xml 然后添加几行非常容易(然后就确信已经为后面几个小时添加了应用程序范围内的数据)。请不要 10 秒钟就做了一个破坏掉前面的艰苦工作的决策,请确定自己确切地知道新配置数据属于哪里,然后把它放在那里。

进一步清理

这一节介绍一些附加步骤,可以用来组织 Struts 配置并简化维护任务。

分离配置数据

好的 servlet 引擎会阻止用户访问 Struts 应用程序的 /WEB-INF/ 目录树下的内容。而且,可以看到,所有配置文件都在这里。但是,如果构建了 8 个或 10 个配置文件,然后把它们添加到 web.xml,还有几个标记库描述符(TLD)和其他几个配置文件,那么这个目录会变得相当凌乱。如果有一个初级程序员进入这个目录,并处理不同的配置文件,那么出错的可能性会增加。如果这是您(或者您的老板)所关心的,那么可以考虑为 struts-* 配置文件创建不同的目录,如清单 6 所示:

清单 6. 分离配置文件

<servlet>

<servlet-name>banking</servlet-name>

<servlet-class>org.apache.struts.action.ActionServlet

</servlet-class>

<init-param>

<param-name>config</param-name>

<param-value>/WEB-INF/config/struts-config.xml,

/WEB-INF/config/struts-authentication.xml,

/WEB-INF/config/struts-help.xml

</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet><servlet-name></servlet-name>

在有些情况下,可能想在 web 应用程序的根下创建其他目录,例如 /config/。但是,servlet 引擎没有设置成自动保护这些目录,就像对 /WEB-INF/ 目录所做的那样。当然可以在 servlet 引擎中设置这些内容,以使额外的目录也得到保护,但是为什么要做这些额外的工作呢?更好的方法是在 /WEB-INF/ 下为这些文件创建目录,例如 /WEB-INF/config。然后,把所有 struts-*.xml 文件移动到这个子目录。最后,更新 web.xml 文件。

创建这个子目录不是一个必需的步骤,但是对于进一步分离配置数据通常很有用。当然,使用同样的原则,也应当考虑把 TLD 文件、数据文件等文件分别放在自己的目录中。最终,会有一个更复杂的目录结构,但是每件事组织和分解得很好,既容易查找,也容易维护。

摆脱标记库声明

配置文件分解到逻辑分组之后,就会极大地降低应用程序的维护量。但是,还可以进一步采取几个方便的步骤,进一步降低配置的复杂性。首先,考虑典型的 web.xml 文件和这个文件中的大量标记库声明。作为示例,清单 7 显示了这份教程中使用的 Struts 银行示例的 web.xml 文件的最后一部分:

清单 7. web.xml 中的标记库声明

<taglib>

<taglib-uri>/WEB-INF/struts-html.tld</taglib-uri>

<taglib-location>/WEB-INF/struts-html.tld</taglib-location>

</taglib>

<taglib>

<taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>

<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>

</taglib>

<taglib>

<taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>

<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>

</taglib>

这里的问题不如查看成百上千行的 struts-config.xml 文件时那么明显,但是仍然是个问题。如果想使用其他标记库(例如 JSTL 标记库),问题就明显了,必须向 web.xml 文件添加另外一个或两个声明。

这本身并不是一个立即可以发现的问题,直到您认识到 web.xml 会影响或破坏应用程序时才会发现。如果在这个文件中犯了小小的输入错误或者弄错了嵌套,那么整个应用程序可能会停止工作。它可能还意味着重启应用程序(进而意味着额外的测试,这会花时间,还有诸如此类的影响)。显然,这不是一个理想的配置情况。

那么考虑一个非常简单的解决方案:把标记库声明转移到 Java 服务器页面(JSP)中。例如,可以把清单 7 中的三个标记库声明转移到清单 8 所示的 JSP 中:

清单 8. JSP 文件中的标记库声明

<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%@ taglib uri="http://jakarta.apache.org/struts/tags-html"

prefix="html" %>

<%@ taglib uri="http://jakarta.apache.org/struts/tags-bean"

prefix="bean" %>

<%@ taglib uri="http://jakarta.apache.org/struts/tags-logic"

prefix="logic" %>

现在,JSP 只需要添加一条 include 指令来包含这个 JSP 文件即可:

<!-- Include the standard tag library JSP -->

<%@ include file="/config/taglibs.jsp" %>

我通常把这个指令放在我的 JSP 的顶部。这样,从来不需要麻烦 web.xml 文件,应用程序不会因为添加一个标记库这么点儿小事就重启,我的应用程序也更加容易管理。我鼓励您也采取同样的转移,让自己少些麻烦。

 

使用多个消息资源束

这是另外一个 Struts 101 配置任务。在许多 Struts 书籍中都会找到它,包括 参考资料 中列出的 Struts Cookbook。同样,在许多书籍中都会有它,因为它是一个非常好的想法。

在 struts-config.xml 文件中已经看到过 message-resources 元素:

<message-resources parameter="BankingMessageResources" null="false"/>

但是,不太有人知道:在 struts-config.xml 中这些元素可以放置不止一个。 例如,下面是非常合法的配置片断:

<message-resources parameter="BankingMessageResources" null="false"/>

<message-resources parameter="BankingHelpResources" key="help"/>

<message-resources parameter="BankingGuestUserResources" key="guest"/>

这里面的第一个元素(没有 key 属性)成为默认的资源集。所以所有的 JSP 和 servet 都可以通过 servlet 上下文访问这组资源。但是,通常是应用程序中的大块部分才需要资源。例如,前面的代码示例中包含的一组资源只针对应用程序的帮助部分(消息、图标、出错、字段标签等等),而且另一组资源特定于访客(没有登录的用户)。在核心应用程序、系统的帮助部分和访客屏幕之间,没有太多共享的信息。所以不必把所有这些资源混合到一个文件中,可以把它们分解到三个独立的文件中。

但是,比起使用三个(或多个)struts-config.xml 文件,这略有不同。在多个 stuts-config.xml 的情况下,多个文件实际上合并成一组配置数据。在多个资源束的情况下,多个文件没有 合并。相反,如果 JSP 页面想使用非默认的资源集,就必须指出要使用哪个资源(在这个示例中是 BankingMessageResources 包)。要做到这一点,需要使用 message 标记,它是 bean 标记库的一部分。

所以,假设:

◆把 bean 标记库与前缀 bean 关联。

◆在 BankingHelpResources 包中有一个属性的键是 help.label.seeAlso。

要引用这个属性,应当在 JSP 中使用以下行:

<bean:message bundle="help" key="help.label.seeAlso" />

额外的属性 bundle 允许指定要使用的非默认包,剩下的标记正常操作,就像访问默认包中的键一样。

您可能还注意到:即使有多个包,示例也只使用了一个特定于包的前缀:与帮助有关的资源在 BankingHelpResources 中,但是键仍然用 help 作为前缀,就像在 help.label.seeAlso 中那样。一开始这看起来可能是多余的,为什么在这个键只在特定于 help 的包中才可用的时候还用 help 做前缀呢?

回答是:一切为了文档:

◆这样会让键的目的更加清晰:其他程序员就不必猜想这个键是做什么的(如果键只是 label.seeAlso,可能就不那么清楚了)。

◆如果包的名称本身不太有说明性(可能有一些没有读过这份教程的初级程序员把它改成了BankingExtraResources),那么标签仍然可以帮助识别出键的目的。

◆如果想把资源从 BankingHelpResources 转移到另一个文件,那么键仍然会保留清晰性。

应当一直采用清楚的命名方式,不管您认为变量或键的上下文或使用方式让它们的目的有多么清楚。

结束语

尽管我是一个程序员(不管是因为业务还是因为选择),但是我在配置、设置、部署和管理应用程序上,要花费比实际编码更多的时间。我并非特别关心这个(实话实说,我一点儿也不喜欢它),但是它是生活的现实。因此,像这份教程所示的解决方案会让我的生活更轻松。只需花一点儿时间把 Struts 应用程序的配置文件分解,但结果绝对物有所值。不会有单点故障:不会只有一个文件,不会只有一个程序员能理解这个文件,也不会只有一个服务器保存这个文件;分解了可能的故障点,降低了出现严重错误的机会。

而且,就像我前面说过的,至少多睡几夜好觉也是值得的。

下载

Demo: Struts Online Banking application

关于作者

Brett McLaughlin 从 Logo 时代(还记得那个小三角吗?)就从事计算工作。最近几年,他已经成为 Java 技术和 XML 社区最著名的作者和程序员之一。他为 Nextel Communications 工作过,实现了复杂的企业系统;在 Lutris Technologies 工作时,实际编写了应用服务器;最近是在 O'Reilly Media, Inc. 工作,在这里他继续编写和编辑这方面的图书。他的最新大作 Java 1.5 Tiger: A Developer's Notebook 是关于最新版本 Java 技术的第一本书,他的经典作品 Java and XML 一直是在 Java 语言中使用 XML 技术方面的权威作品之一。

 

你可能感兴趣的:(xml,Web,struts,配置管理,企业应用)