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)