Struts 用户指南(1)
1.?介绍?
1.1?Model-View-Controller?(MVC)?设计模式?
?
FIXME?-?需要一个对该模式一般性的介绍。(译注:可以参考机械工业出版社的《设计模式》。)?
?
1.2?将MVC概念映射到Struts组件中?
?
Struts?的体系结构实现了Model-View-Controller设计模式的概念,它将这些概念映射到Web应用程序的组件和概念中。?
?
1.3?Model:?系统状态和商业逻辑JavaBeans?
?
基于MVC的系统中的?Model?部分可以细分为两个概念?--?系统的内部状态,?能够改变状态的行为。用语法术语来说,我们可以把状态信息当作名词(事物),把行为当作动词(事物状态的改变)。?
?
通常说来,你的应用程序将系统内部的状态表示为一组一个或多个的JavaBeans,使用属性(properties)来表示状态的细节。依赖于你的应用程序的复杂度,这些beans可以是自包含的(以某种方式知道怎样永久地保存它们的状态信息),或者可以是正面的(facades),知道当被请求时怎样从外部数据源(例如数据库)中取得信息。Entity?EJBs通常也用来表示内部状态。?
?
大型应用程序经常将系统可能的商业逻辑行为表示为可以被维护状态信息的beans调用的方法。举个例子,你有一个为每个当前用户保存在session中的购物车bean,里面是表示当前用户决定购买物品的属性。这个bean有一个checkOut()方法用来验证用户的信用卡,将定单发给库房以选择货品和出货。别的系统分别地表示同样的行为,或许使用Session?EJBs。?
?
在一些小型应用程序中,同样的行为又可能嵌入到作为Controller一部分的?Action?类中。这在逻辑非常简单或者并不想要在其它环境中重用这些商业逻辑时是恰当的。Struts框架支持所有这些方法,但建议将商业逻辑(“做什么”)和?Action?类(“决定做什么”)分离开。?
?
1.4?View:?JSP页面和表示组件?
?
基于Struts的应用程序中的?View?部分通常使用JSP技术来构建。JSP页面包含称为“模版文本”的静态HTML(或XML)文本,加上插入的基于对特殊行为标记解释的动态内容。JSP环境包括了其用途由JSP规范来描述的一套标准的行为标记,例如?。另外,还有一个用来定义你自己标记的标准机制,这些自定义的标记组织在“定制标记库”中。?
?
Struts包括了一个广阔的便于创建用户界面,并且充分国际化的定制标记库,与作为系统?Model?部分一部分的ActionForm?beans美妙地相互配合。这些标记的使用将在后面做详细讨论。?
?
除了JSP页面和其包含的行为及定制标记,商业对象经常需要能够基于它们在被请求时的当前状态将自己处理成HTML(或XML)。从这些对象处理过的输出可以很容易地使用?标准行为标记包括在结果的JSP页面中。?
?
1.5?Controller:?ActionServlet和ActionMapping?
?
应用程序的?Controller?部分集中于从客户端接收请求(典型情况下是一个运行浏览器的用户),决定执行什么商业逻辑功能,然后将产生下一步用户界面的责任委派给一个适当的View组件。在Struts中,controller的基本组件是一个?ActionServlet?类的servlet。这个servlet通过定义一组映射(由Java接口?ActionMapping?描述)来配置。每个映射定义一个与所请求的URI相匹配的路径和一个?Action?类(一个实现?Action?接口的类)完整的类名,这个类负责执行预期的商业逻辑,然后将控制分派给适当的View组件来创建响应。?
?
Struts也支持使用包含有运行框架所必需的标准属性之外的附加属性的?ActionMapping?类的能力。这允许你保存特定于你的应用程序的附加信息,同时仍可利用框架其余的特性。另外,Struts允许你定义控制将重定向到的逻辑名,这样一个行为方法可以请求“主菜单”页面(举例),而不需要知道相应的JSP页面的实际名字是什么。这个功能极大地帮助你分离控制逻辑(下一步做什么)和显示逻辑(相应的页面的名称是什么)。?
?
2.?创建Model组件?
?
2.1?概述?
?
你用到的应用程序的需求文档很可能集中于创建用户界面。然而你应该保证每个提交的请求所需要的处理也要被清楚的定义。通常说来,Model?组件的开发者集中于创建支持所有功能需求的JavaBeans类。一个特殊应用要求的beans的精确特性依赖于具体需求变化会非常的大,但是它们通常可以分成下面讨论的几种类型。然而,首先对“范围”概念做一个简短的回顾是有用的,因为它与beans有关。?
?
2.2?JavaBeans和范围?
?
在一个基于web的应用程序中,JavaBeans可以被保存在(并从中访问)一些不同“属性”的集合中。每一个集合都有集合生存期和所保存的beans可见度的不同的规则。总的说来,定义生存期和可见度的这些规则被叫做这些beans的?范围?。JSP规范中使用以下术语定义可选的范围(在圆括号中定义servlet?API中的等价物):
page?-?在一个单独的JSP页面中可见的Beans,生存期限于当前请求。(service()方法中的局部变量)?request?-?在一个单独的JSP页面中可见的Beans,也包括所有包含于这个页面或从这个页面重定向到的页面或servlet。(Request属性)?
?
session?-?参与一个特定的用户session的所有的JSP和servlet都可见的Beans,跨越一个或多个请求。(Session属性)?
?
application?-?一个web应用程序的所有JSP页面和servlet都可见的Beans。(Servlet?context属性)?
?
记住同一个web应用程序的JSP页面和servlets共享同样一组bean集合是很重要的。例如,一个bean作为一个request属性保存在一个servlet中,就象这样:?
?
MyCart?mycart?=?new?MyCart(...);?
?
request.setAttribute("cart",?mycart);?
?
将立即被这个servlet重定向到的一个JSP页面使用一个标准的行为标记看到,就象这样:?
?
?
<jsp:useBean?id="cart"?scope="request"
class="com.mycompany.MyApp.MyCart"/>
?
?
2.3?ActionForm?Beans?
?
Struts框架通常假定你已经为每一个你的应用程序中请求的输入创建了一个?ActionForm?bean(即一个实现了?ActionForm?接口的类)。如果你在你的?ActionMapping?配置文件中定义了这样的beans(见“创建Controller组件”),Struts的controller?servlet在调用适当的?Action?方法前将自动为你执行如下的服务:?
?
用适当的关键字检查用户的session中是否有适当的类的bean的一个实例。?
?
如果没有这样的session范围的bean,自动建立一个新的bean并添加到用户的session中。?
?
对每个名字对应于bean中的一个属性的请求参数,调用相应的set方法。这个操作类似于当你以通配符“*”选择所有属性使用标准的JSP行为标记?。?
?
更新的ActionForm?bean在被调用时将被传递给Acton类的perform()方法,以使这些值能够立即生效。?
?
当你在写你的ActionForm?beans时,记住以下的原则:?
?
ActionForm?接口本身不需要特殊的实现方法。它是用来标识这些特定的beans在整个体系结构中的作用。典型情况下,一个ActionForm?bean只包括属性的get方法和set方法,没有商业逻辑。?
?
通常在一个ActionForm?bean中只有很少的输入验证逻辑。这样的beans存在的主要理由是保存用户为相关的表单所输入的大部分近期值?--?甚至在错误被检测到时?--?这样同样的页面可以被重建,伴随有一组出错信息,这样用户仅仅需要纠正错误的字段。用户输入的验证应该在?Action?类中执行(如果是很简单的话),或者在适当的商业逻辑beans中执行。?
?
为每个表单中出现的字段定义一个属性(用相关的getXxx()和setXxx()方法)。字段名和属性名必须按照JavaBeans的约定相匹配。例如,一个名为?username?的输入字段将引起?setUsername()?方法被调用。?
?
你应该注意一个“表单”在这里讨论时的意义并不必须对应于用户界面中的一个单独的JSP页面。在很多应用程序中一个“表单”(从用户的观点)延伸至多个页面也是很平常的。想想看,例如,通常在安装新的应用程序时使用的导航安装程序的用户界面。Struts鼓励你定义一个包含所有字段属性的单独的ActionForm?bean。不管字段实际上是显示在哪个页面上。同样的,同一表单的不同的页面应该提交到相同的Action类。如果你遵照这个建议,在大多数情况下,页面设计者可以重新组织不同页面中的字段而不需要改变处理逻辑。?
?
2.4?系统状态Beans?
?
系统的实际状态通常表示为一组一个或多个的JavaBeans类,其属性定义当前状态。例如,一个购物车系统包括一个表示购物车的bean,这个bean为每个单独的购物者维护,这个bean中包括(在其它事物之中)一组购物者当前选择购买的项目。分别地,系统也包括保存用户信息(包括他们的信用卡和送货地址)、可获得项目的目录和它们当前库存水平的不同的beans。?
?
对于小规模的系统,或者对于不需要长时间保存的状态信息,一组系统状态beans可以包含所有系统曾经经历的特定细节的信息。或者经常是,系统状态beans表示永久保存在一些外部数据库中的信息(例如CustomerBean对象对应于表?CUSTOMERS?中的特定的一行),在需要时从服务器的内存中创建或清除。在大规模应用程序中,Entity?EJBs也用于这种用途。?
?
2.5?商业逻辑Beans?
?
你应该把你的应用程序中的功能逻辑封装成对为此目的设计的JavaBeans的方法调用。这些方法可以是用于系统状态beans的相同的类的一部分,或者可以是在专门执行商业逻辑的独立的类中。在后一种情况下,你通常需要将系统状态beans传递给这些方法作为参数处理。?
?
为了代码最大的可重用性,商业逻辑beans应该被设计和实现为它们不知道自己被执行于web应用环境中。如果你发现在你的bean中你必须import一个?javax.servlet.*?类,你就把这个商业逻辑捆绑在了web应用环境中。考虑重新组织事物使你的?Action?类(Controller任务的一部分,在下面描述)翻译所有从HTTP请求中请求被处理为对你的商业逻辑beans属性set方法调用的信息,然后可以发出一个对?execute()?的调用。这样的一个商业逻辑类可以被重用在除它们最初被构造的web应用程序以外的环境中。
依赖于你的应用程序的复杂度和范围,商业逻辑beans可以是与作为参数传递的系统状态beans交互作用的普通的JavaBeans,或者使用JDBC调用访问数据库的普通的JavaBeans。而对于较大的应用程序,这些beans经常是有状态或无状态的EJBs。?
?
2.6?题外话:?访问关系数据库?
?
很多web应用程序利用一个关系数据库(通过一个JDBC?driver访问)来保存应用程序相关的永久数据。其它应用程序则使用Entity?EJBs来实现这个目的,他们委派EJBs自己来决定怎样维护永久状态。如果你是使用EJBs来实现这个目的,遵照EJB规范中描述的客户端设计模式。?
?
对于基于直接数据库访问的web应用程序,一个普通的设计问题是当需要访问低层数据库时怎样产生一个适当的JDBC连接对象。解决这个问题有几种方法?--?以下原则描述了推荐的一种方法:?
?
创建或得到一个允许一组数据库连接被多个用户共享的ConnectionPool类。Struts(当前)没有包括这样的一个类,但是有很多这样的类可以得到。?
?
当应用程序初始化时,在应用程序展开(deployment)描述符中定义一个有一个“启动时加载”值的servlet。我们将把这个servlet叫做?启动?servlet。在大多数情况下,这个servlet不需要处理任何的请求,所以没有一个?会指向它。?
?
在启动servlet的?init()?方法中,配置并初始化一个ConnectionPool类的实例,将其保存为一个servlet?context属性(从JSP的观点看等同于一个application范围的bean)。通常基于传递给启动servlet初始化参数来配置联接缓冲池是很方便的。?
?
在启动servlet的?destroy()?方法中,包含了释放联接缓冲池所打开的联接的逻辑。这个方法将在servlet容器结束这个应用程序的时候被调用。?
?
当?Action?类需要调用一个需要数据库联接的商业逻辑bean中的方法(例如“insert?a?new?customer”)时,将执行下面的步骤:?
?
为这个web应用程序从servelt?context属性中得到一个联接缓冲池对象。?
?
调用联接缓冲池对象的?open()?方法来得到一个在?Action?类调用中使用的联接。?
?
调用商业逻辑bean中合适的方法,将数据库联接对象作为一个参数传递给它。?
?
调用分配的联接中的?close()?方法,这将引起这个联接为了以后其它请求的重用被返回到缓冲池中。?
?
一个通常的编程错误是忘记了把数据库联接返回给缓冲池,这将最终导致用完所有的联接。一定要确信?Action?类的逻辑总是返回联接,甚至在商业逻辑bean抛出一个违例时。?
?
遵照上面推荐的设计模式意味着你能够编写你的商业逻辑类而不需要担心它们怎样得到一个JDBC联接来使用--?简单地在任何需要访问数据库的方法中包含一个Connection参数。当你的商业逻辑类在一个web应用程序中被利用时,分配和释放适当的联接是?Action?类的责任。当你使用相同的商业逻辑类时,例如,在一个批处理工作中,提供一个适当的联接是那个应用程序的责任(这不需要从缓冲池中获得,因为大多数批处理工作运行于一个单线程环境中)。