Model就是在对用户请求的整个控制过程中,真正处理用户请求并保存处理结果的对象,在整个过程中,我们一般利用JavaBean来把一些信息保存起来以便在各个对象之间传递。因为在框架中,Model对象是真处理商业逻辑功能的对象,因此也就是框架中应用需求实现相关性最大的的部分。在Struts的实现里,Model的具体表现形式就是ActionForm对象和与其对应的Action对象了。对用户提交表单的数据进行校验,甚至对数据进行预处理都能在ActionForm中完成。通常的应用中,一般是一个Model对象和一个请求页面对应的关系,但也可以一个Model对象对应多个页面请求。如果struts-config.xml配置文件没有指定一个Model对象对应的Action,那么控制器将直接把(通过Model对象完成数据封装的)请求转到一个View对象。下图表示的是Model layer 的层次结构。
在Struts中Model以一个或多个java bean的形式存在。这些bean分为三类:Action Form、Action、JavaBean or EJB。Action Form通常称之为FormBean,封装了来自于Client的用户请求信息,如表单信息。Action通常称之为ActionBean,获取从ActionSevlet传来的FormBean,取出FormBean中的相关信息,并做出相关的处理,一般是调用JavaBean或EJB等。
许多需求文档将构建Web应用的焦点集中在视图上。我们必须确保每一个提交的请求都在模型视图中都已经被定义。通常,开发者在模型组件中关注于开发JavaBean类以实现所有的功能需求。 应用应该准确的使用哪些beans,根据其需求不同而差异巨大,但是,在经过区分后通常都能分被为若干个类别。
二、创建Model 组件
1、JavaBeans
在一个Web基础的应用中,能使用许多不同的"属性(attributes)"集合来保存(和访问)JavaBeans。 每个集合都有它自己不同的生命周期和beans存储在哪里的可见度。 同时,beans通过 作用域 来定义生命周期和可见度规则。 在JavaServer Pages (JSP)规范中定义了作用域选择使用以下几项(在括号中是servlet API中的等价概念定义)。
page :Beans只会在一个JSP页中可见,只在当前的请求周期中存在。 (在 service 方法中的本地变量)
request : Beans只会在一个JSP页中可见,与page相同或servlet包含本页,或转发到本页。 (Request属性)
session :Beans能被所有的JSP页和servlet通过特定的用户session来使用, 它可以跨越一个或多个请求。 (Session属性)
application :Beans能被Web应用中的所有JSP页和servlets来使用。 (Servlet context属性)
我们需要记住的是在一个web应用中JSP页面和servlet会共享bean集合的设置。 例如在一个servlet中将一个bean存储到attribute中如下:
MyStudy Mystudy = new MyStudy(...); request.setAttribute("cart", MyStudy); |
在这个servlet将请求转发给一个JSP页面后,我们马上可以使用标准的动作标签(tag)来看到相应的值:
< jsp:useBean id="cart" scope="request" class="com.mycompany.MyApp.MyStudy"/ > |
2、ActionForm Beans
在 actionform beans频繁地有属性相当于属性在我们的model beans的时候,那form beans它们自己应该考虑成为一个控制器组件。 同样地,他们能在模型和视图层之间传递资料。
Struts框架通常假定我们在我们的应用中已经为输入定义一个 ActionForm beans(简而言之,一个扩展自 ActionForm 类的Java类)。 ActionForm beans有时仅仅调用表单beans(form beans)。 这可能会是一个细粒度的对像,它让每个表单对应一个bean,还有就是一个bean服务于若干个表单甚至全部应用形成粗粒度的情况。
如果在我们的Struts配置文件中定义了bean,Struts的controller servlet在调用适当的 Action 方法前将自动为我们提供如下服务:
使用适当的关键字检查在用户适当的作用域(request或session)中是否有适当类的bean的一个实例。
如果没有这样的实例可用,则自动建立一个新的bean实例并将期加入到适当的作用域中(request或session)。
对于每个请求参数通过其名称来对应到bean的一个属性(property)上,并调用相应的setter方法来设置属性值。 这个方法类似于标准JSP中以以通配符"*"来使用 < jsp:setProperty > 标记。
更新后的 ActionForm bean被传递给 Action 类[ org.apache.struts.Action ] 的 execute 方法, 以使这些值能被我们的系统状态和业务逻辑bean来使用。
我们应该注意一个"表单(form)"在这里并不是必须对应于用户界面中一个单独的JSP页面。 在很多应用程序中一个"表单"(从用户的观点)延伸至多个页面也是很平常的。 想想看,例如,在新程序的安装时所使用的导航程序的用户界面。 Struts鼓励我们定义一个包含所有字段属性的单独的 ActionForm bean, 而不用管这些字段实际显示于哪个页面上。同样的,同一表单的不同页面应提交到相同的Action类。 如果我们遵照这个建议,在大多数情况下,页面设计者可以重新组织不同页面中的字段而不需要改变处理逻辑。
一个小的应用也许只需要一个ActionForm来为所有的输入表单提供服务。 其它应用可以为每个大的子系统来分别使用一个ActionForm。 还有一些人可能更喜欢为每一个输入表单或工作流分别使用不同的ActionForm类。 真正如何使用ActionForm完全在于我们,框架自身并不在意的。
ActionForm 接口本身不需要特殊的实现方法。它是用来标识这些特定的beans在整个体系结构中的作用。典型情况下,一个 ActionForm bean只包括属性的get方法和set方法,没有商业逻辑。
通常在一个 ActionForm bean中只有很少的输入验证逻辑。这样的beans存在的主要理由是保存用户为相关的表单所输入的大部分近期值,这样同样的页面可以被重建,伴随有一组出错信息,这样用户仅仅需要纠正错误的字段。用户输入的验证应该在 Action 类中执行(如果是很简单的话),或者在适当的商业逻辑beans中执行。
为每个表单中出现的字段定义一个属性(用相关的getXxx()和setXxx()方法)。字段名和属性名必须按照JavaBeans的约定相匹配。例如,一个名为 username 的输入字段将引起 setUsername() 方法被调用。
下面是ActionForm类的具体描述:
ActionForm类
框架假设用户在应用程序中为每个表单都创建了一个ActionForm bean,对于每个在struts-config.xml文件中定义的bean,框架在调用Action类的perform()方法之前会进行以下操作:
1、在相关联的关键字下,它检查用于适当类的bean实例的用户会话,如果在会话中没有可用的bean,它就会自动创建一个新的bean并添加到用户的会话中。
2、对于请求中每个与bean属性名称对应的参数,Action调用相应的设置方法。
3、当Action perform()被调用时,最新的ActionForm bean传送给它,参数值就可以立即使用了。
ActionForm类扩展org.apache.struts.action.ActionForm类,程序开发人员创建的bean能够包含额外的属性,而且ActionServlet可能使用反射(允许从已加载的对象中回收信息)访问它。
ActionForm类提供了另一种处理错误的手段,提供两个方法:
Public ActionErrors validate(ActionMappin mapping,ServletRequest request) Public ActionErrors validate(ActionMappin mapping,HttpServletRequest request) |
我们应该在自己的bean里覆盖validate()方法,并在配置文件里设置<action>元素的validate为true。在ActionServlet调用Action类前,它会调用validate(),如果返回的ActionErrors不是null,则ActinForm会根据错误关键字将ActionErrors存储在请求属性列表中。
如果返回的不是null,而且长度大于0,则根据错误关键字将实例存储在请求的属性列表中,然后ActionServlet将响应转发到配置文件<action>元素的input属性所指向的目标。
如果需要执行特定的数据有效性检查,最好在Action类中进行这个操作,而不是在ActionForm类中进行。
方法reset()可将bean的属性恢复到默认值:
public void reset(ActionMapping mapping,HttpServletRequest request) public void reset(ActionMapping mapping,ServletRequest request) |
典型的ActionFrom bean只有属性的设置与读取方法(getXXX),而没有实现事务逻辑的方法。只有简单的输入检查逻辑,使用的目的是为了存储用户在相关表单中输入的最新数据,以便可以将同一网页进行再生,同时提供一组错误信息,这样就可以让用户修改不正确的输入数据。而真正对数据有效性进行检查的是Action类或适当的事务逻辑bean。
3、系统状态Beans
系统的实际状态通常表示为一组一个或多个JavaBean类,其属性定义了当前的状态。 例如,在一个购物车系统中,将包括一个表示购物车的bean,这个bean为每个购物者所维护, 它包括了购物者所选择购买的物品条目。 另外,系统也包括保存用户信息(包括他们的信用卡和送货地址)、可获得的条目和当前库存水平这些不同的bean。
对于小规模系统,或是对于不需要长时间保存的状态信息,一组系统状态bean可以包含 所有系统曾经经历的特定细节的信息。 或者经常是,系统状态bean会表示永久保存在一些外部数据库中的信息(例如 CustomerBean 对象对应于CUSTOMERS表中特定的一行数据), 在需要时从服务器的内存中创建或清除。Entity Enterprise JavaBeans也是用于这种用途的。
4、商业逻辑Beans
我们应该把用程序中的功能逻辑在设计时封装成为JavaBean的方法调用。 这些方法可以是用于系统系统装态bean相同的类中的一部分,或者可以是在专门执行商业逻辑的独立的类中。 在后一种情况下,我们通常需要将系统状态bean传递给这些方法做为参数以进行处理。
为了达到代码的最大可重用性,商业逻辑bean应该被设计和实现为它们并 不 知道自己被执行于web应用环境中。 如果发现在我们的bean中正在import javax.servlet.* 中的类,我们就把这个商业逻辑限定在了web应用环境中。 应考虑重新组织我们的 Action 类通过对商业逻辑beans属性的set方法的调用以翻译所有从HTTP请求中发出的请示信息,之后再发出一个对 execute 方法的调用。 这样才可以让商业逻辑类在最初被构造的web应用程序以外的环境中被重用。
依赖于我们的应用程序的复杂度和范围,商业逻辑beans可以是与作为参数传递的系统状态bean交互作用的普通JavaBean, 或都是使用JDBC访问数据库的普通的JavaBean。而对于较大的应用程序,这些bean经常是有状态或无状态的Enterprise JavaBeans (EJBs)。
下面是Action 类的具体描述:
Action类
Action类真正实现应用程序的事务逻辑,它们负责处理请求。在收到请求后,ActionServlet会:
为这个请求选择适当的Action
如果需要,创建Action的一个实例
调用Action的perform()方法
如果ActionServlet不能找到有效的映射,它会调用默认的Action类(在配置文件中定义)。如果找到了ActionServlet将适当的ActionMapping类转发给Action,这个Action使用ActionMapping找到本地转发,然后获得并设置ActionMapping属性。根据servlet的环境和被覆盖的perform()方法的签名,ActionServlet也会传送ServletRequest对象或HttpServletRequest对象。
所有Action类都扩展org.apache.struts.action.Action类,并且覆盖类中定义的某一个perform()方法。有两个perform()方法:
处理非HTTP(一般的)请求:
public ActionForward perform(ActionMapping action,AcionForm form,ServletRequest request, ServletResponse response) throws IOException,ServletException |
处理HTTP请求:
public ActionForward perform(ActionMapping action,AcionForm form,HttpServletRequest request, HttpServletResponse response) throws IOException,ServletException |
Action类必须以"线程安全"的方式进行编程,因为控制器会令多个同时发生的请求共享同一个实例,相应的,在设计Action类时就需要注意以下几点:
不能使用实例或静态变量存储特定请求的状态信息,它们会在同一个操作中共享跨越请求的全局资源
如果要访问的资源(如JavaBean和会话变量)在并行访问时需要进行保护,那么访问就要进行同步
Action类的方法
除了perform()方法外,还有以下方法:
可以获得或设置与请求相关联的区域:
public Locale getLocale(HttpServletRequest request) public void setLocale(HttpServletRequest request,Locale locale) |
为应用程序获得消息资源:
public MessageResources getResources() |
检查用户是否点击表单上的"取消"键,如果是,将返回true:
public Boolean isCancelled(HttpServletRequest request) |
当应用程序发生错误时,Action类能够使用下面方法存储错误信息:
public void saveErrors(HttpServletRequest request,ActionErrors errors) |
ActionError实例被用来存储错误信息,这个方法在错误关键字下的请求属性列表中存储ActionError对象。
5、访问关系数据库
很多web应用程序利用一个关系数据库(通过一个JDBC driver访问)来保存应用程序相关的永久数据。 其它应用程序则使用Entity EJBs来实现这个目的,他们委派EJBs自己来决定怎样维护永久状态。如果我们是使用EJBs来实现这个目的,遵照EJB规范中描述的客户端设计模式。
对于基于直接数据库访问的web应用程序,一个普通的设计问题是当需要访问低层数据库时怎样产生一个适当的JDBC连接对象。解决这个问题有方法如下:
创建或得到一个允许一组数据库连接被多个用户共享的ConnectionPool类。Struts(当前)没有包括这样的一个类,但是有很多这样的类可以得到。
当应用程序初始化时,在应用程序展开(deployment)描述符中定义一个有一个"启动时加载"值的servlet。我们将把这个servlet叫做 启动 servlet。在大多数情况下,这个servlet不需要处理任何的请求,所以没有一个<servlet-mapping> 会指向它。
在启动servlet的 init() 方法中,配置并初始化一个ConnectionPool类的实例,将其保存为一个servlet context属性(从JSP的观点看等同于一个application范围的bean)。通常基于传递给启动servlet初始化参数来配置联接缓冲池是很方便的。
在启动servlet的 destroy() 方法中,包含了释放联接缓冲池所打开的联接的逻辑。这个方法将在servlet容器结束这个应用程序的时候被调用。
当 Action 类需要调用一个需要数据库联接的商业逻辑bean中的方法(例如"insert a new customer")时,将执行下面的步骤:
1)为这个web应用程序从servelt context属性中得到一个联接缓冲池对象。
2)调用联接缓冲池对象的 open() 方法来得到一个在 Action 类调用中使用的联接。
3)调用商业逻辑bean中合适的方法,将数据库联接对象作为一个参数传递给它。
4)调用分配的联接中的 close() 方法,这将引起这个联接为了以后其它请求的重用被返回到缓冲池中。