2013年3月

Struts 2 拦截器(二)

拦截器.jpg

1.声明拦截器

XML是声明拦截器的唯一选择,注解机制现在还不支持声明拦截器。

1.1 声明独立的拦截器和拦截器栈

通常,拦截器声明包含声明可用的拦截器并把它们与应该触发的动作关联起来。像所有框架组件的声明一样,拦截器的声明必须在package元素内部。以下是struts-default.xml文件中struts-default包的各个拦截器的声明:

<package name="struts-default" abstract="true">
    <interceptors>
         ......
        <interceptor name="execAndWait" class="org.apache.struts2.interceptor.ExecuteAndWaitInterceptor"/>
        <interceptor name="exception" class="com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor"/>
        <interceptor name="fileUpload" class="org.apache.struts2.interceptor.FileUploadInterceptor"/>
         ......

        <interceptor-stack name="defaultStack">
                <interceptor-ref name="exception"/>
                <interceptor-ref name="alias"/>
                <interceptor-ref name="servletConfig"/>
                <interceptor-ref name="i18n"/>
                <interceptor-ref name="prepare"/>
                <interceptor-ref name="chain"/>
                <interceptor-ref name="debugging"/>
                <interceptor-ref name="scopedModelDriven"/>
                <interceptor-ref name="modelDriven"/>
                <interceptor-ref name="fileUpload"/>
                <interceptor-ref name="checkbox"/>
                <interceptor-ref name="multiselect"/>
                <interceptor-ref name="staticParams"/>
                <interceptor-ref name="actionMappingParams"/>
                <interceptor-ref name="params">
                    <param name="excludeParams">dojo\..*,^struts\..*</param>
                </interceptor-ref>
                <interceptor-ref name="conversionError"/>
                <interceptor-ref name="validation">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
                <interceptor-ref name="workflow">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
            </interceptor-stack>

            <default-interceptor-ref name="defaultStack"/>
    </interceptors>
</package>

interceptors元素包含这个包内所有的interceptor和interceptor-stack的声明。每个interceptor元素声明了一个可以在包内使用的拦截器。这些声明实际上没有创建一个拦截器或者把这个拦截器与任何动作关联,它们只是把一个名字映射到一个类。struts-default包声明了几个栈,其中中最重要的是defaultStack。

interceptor-stack元素的内容是一系列interceptor-ref元素,这些引用必须都指向interceptor元素创建的某个逻辑名,interceptor-ref元素也可以传入参数来配置引用创建的拦截器实例。

最后,一个包可以定义一组默认的拦截器,default-interceptor-ref定义的拦截器组会与这个包内没有显示声明自己的拦截器的所有动作关联,它只指向一个逻辑名,这里是defaultStack。

XML文档结构

声明使用的XML文档必须遵守特定的顺序规则,这可以通过DTD查看:

<!ELEMENT struts ((package|include|bean|constant)*, unknown-handler-stack?)>
<!ELEMENT package (result-types?, interceptors?, default-interceptor-ref?, default-action-ref?, default-class-ref?, global-results?, global-exception-mappings?, action*)>

上述语句指出,struts-default.xml文件以struts元素开始,这个元素能够包含4种不同的元素类型的0个或者多个实例。与struts的元素不同,package元素的内容必须遵照特定的顺序。另外,所有包含在一个package元素中的元素,出action元素外,都只能出现一次。interceptors元素必须只出现一次或者一次也不出现,并且不需出现在result-types元素之后,并且在default-interceptor-ref元素之前。

1.2 将拦截器映射到动作组件

可以使用interceptor-ref元素完成拦截器和动作的关联,以下代码片段显示了如何将一系列拦截器和一个特定的动作关联起来:

<action name="MyAction" class="com.moodpo.review.MyAction">
    <interceptor-ref name="timer"></interceptor-ref>
    <interceptor-ref name="logger"></interceptor-ref>
    <!-- 需要再次声明使用defaultStack -->
    <interceptor-ref name="defaultStack"></interceptor-ref>
    <result>/success.jsp</result>
</action>

首先,这个动作命名了拦截器,但没有提及在struts-default包中声明的defaultStack。正因如此,这个动作必须在一个扩展了struts-default的包中。其次,虽然没有定义任何interceptor-ref的动作会继承默认的拦截器,但是只要动作声明了自己的拦截器,它就失去了自己的默认值,并且为了使用defaultStack就必须显式指出。

1.3 设置、覆盖拦截器参数

很多拦截器可以被参数化。如果一个拦截器接受参数,那么interceptor-ref元素是向它们传入参数的地方。比如之前所说的workflow拦截器被参数化以忽略对动作的某些方法名的访问:

<interceptor-ref name="workflow">
    <param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>

如果我们想重用包含了defaultStack的拦截器栈,但是我们想改变excludeMethods参数的值要怎么办呢?很简单,代码如下:

<action name="MyAction" class="com.moodpo.review.MyAction">
    <interceptor-ref name="defaultStack">
        <param name="workflow.excludeMethods">dosomething</param>
    </interceptor-ref>
    <result>/success.jsp</result>
</action>

2.构建自定义拦截器

除了安排栈的顺序和在调试中学习解决顺序问题需要多加留意外,编写拦截器其实很容易。

2.1 实现Interceptor接口

在编写一个拦截器时需要实现com.opensymphony.xwork2.interceptor.Interceptor接口。

public interface Interceptor extends Serializable {
    public void destroy();
    public void init();
    public String intercept(ActionInvocation actionInvocation) throws Exception;
}

可以看到,这个简单的接口只定义了3个方法,前两个方法是典型的生命周期方法,真正的业务逻辑发生在intercept()方法中。

2.2 声明拦截器并构建新的默认栈

在定义完拦截器类后,需要将这个拦截器应用在动作上,所以可以构建一个自定栈,它的大致写法如下:

<package name="demo4Secure" namespace="/secure" extends="struts-default">

    <interceptors>
        <interceptor name="yourInterceptor" class="com.moodpo.review.utils.YourInterceptor"></interceptor>

        <interceptor-stack name="myStack">
            <interceptor-ref name="yourInterceptor"></interceptor-ref>
            <interceptor-ref name="defaultStack"></interceptor-ref>
        </interceptor-stack>
    </interceptors>

    <default-interceptor-ref name="myStack"></default-interceptor-ref>

    <action name="yourAction" class="com.moodpo.review.action.YourAction">
        <result>/success.jsp</result>
    </action>

</package>

首先,我们必须使用一个interceptors元素来包含所有的interceptor和interceptor-stack的声明。我们必须使用一个interceptor元素将Java类映射到一个逻辑名(yourInterceptor)。其次,我们构建了一个新栈,这个栈使用defaultStack并且把新拦截器追加到它的上面(当然根据业务的不同,也可以放在下面)。最后,我们把myStack声明为这个包的默认拦截器栈。以上这样做的好处是拦截器与动作代码完全分离,并且实现了可重用。

Struts 2 拦截器(一)

从开发人员日常工作的角度来看,动作组件可能是框架的核心和灵魂,然而在后台工作的默默无闻的拦截器却可以说是真正的英雄——拦截器负责完成了框架的大部分处理工作。

1.拦截请求的意义

1.1 清理MVC

拦截器消除了动作组件中的横切任务。日志记录功能是典型的横切任务,它不是某一个动作所特有的,而是横向关联所有动作。作为软件工程师,我们会把这个任务提到更高的层面,让它处在任何需要日志记录的请求上(或者说之前),达到将MVC关注点分离的目的。

拦截器承担的另一些任务被称为预处理或者后加工。预处理任务的一个很好的示例就是常见的数据转移,它通过params拦截器实现。

在Struts2中没有一个动作被单独调用,动作调用是一个分层的过程,总是包含一系列的拦截器在动作执行之前或者之后执行。框架不直接调用动作的execute()方法,而是创建一个叫做ActionInvocation的对象,它封装了动作的一系列被配置的动作执行之前之后触发的拦截器。下图展示了ActionInvocation类封装的完整的动作执行过程。

ActionInvocation 流程.png

拦截器的强大功能之一是改变调用工作流。有时候某个拦截器会决定动作不应该执行,此时,拦截器可以自己返回一个控制字符串,从而终止工作流。以workflow拦截器为例,这个拦截器做两件事情。首先,如果动作实现了Validateable接口,那么调用动作的validate()方法。其次,检查动作上是否出现了错误信息。如果出现了错误信息,它返回控制字符串并终止后续执行。

1.2 拦截器的好处

使用拦截器分层让软件更整洁,并增加了可读性和可测试性,也提高了灵活性。从这种灵活性中得到的两个主要益处是可重用和可配置。

每一个人都想重用软件,想达到可重用很简单,只需将想重用的逻辑隔离到清晰分割的单元。把这些逻辑隔离到拦截器之后,我们可以把它们放在任何地方,应用到所有动作类上。例如我们通常通过继承defaultstack获得的代码重用。

除代码重用之外,拦截器分层的能力还给我们带来了另外一些非常重要的好处。我们能够很容易的配置它们的顺序和数量。

2.拦截器的工作原理

2.1 总指挥 ActionInvocation

ActionInvocation封装了与特定动作执行相关的所有处理细节,所以知道ActionInvocation做什么就等于知道Struts2如何处理请求。当框架收到一个请求时,它首先必须决定这个URL映射到哪个动作,这个动作的一个实例会被加入到一个新创建的ActionInvocation示例中。接着,框架咨询声明性架构(通过xml或者Java注解),以发现哪些拦截器应该触发,指向这些拦截器的引用将被加入到ActionInvocation中。

2.2 如何触发拦截器

框架创建了ActionInvocation,并填充了需要的所有对象和信息,通过调用ActionInvocation公开的invoke()方法开始动作的执行,此时拦截器栈中的第一个拦截器开始调用过程。ActionInvation负责跟踪执行过程达到的状态,并把控制交给栈中合适的拦截器。ActionInvation通过调用拦截器的intercept()方法将控制转交给拦截器。后续拦截器继续执行,最终执行动作。这些都是通过递归调用ActionInvocation的invoke()方法实现。

  1. 调用ActionInvocation对象的invoke()方法
  2. 开始调用第一个拦截器
  3. 调用拦截器的intercept()方法,并把ActionInvocation实例作为参数传递
  4. 拦截器调用传递的参数即ActionInvocation实例的invoke()方法
  5. 开始调用第二个拦截器
    ......
    N. 栈中没有拦截器开始触发动作

在此过程中,ActionInvocation在内部管理处理状态,它总是能知道现在处在栈的哪里。

拦截器有一个三阶段的、有条件的执行周期,如下所述:

  1. 做一些预处理
  2. 通过调用invoke()方法将控制转移给后续的拦截器,最后直到动作;或者通过返回一个控制字符串终端执行
  3. 做一些后加工

3.Struts2 内置的拦截器

Struts2框架自带了一系列功能强大的内置拦截器,它们提供了你想从Web框架得到的大部分功能。除了defaultStack这个拦截器栈,框架还自带了很多拦截器和已经预先配置好的拦截器栈。

3.1工具拦截器

这些拦截器提供了辅助开发、调优及解决问题的简单工具。

3.1.1 timer拦截器

这个简单的拦截器只记录执行花费的时间,在拦截器栈中的位置决定了它实际测量什么时间。

3.1.2 logger拦截器

这个拦截器提供了一个简单的日志记录机制,记录了在预处理事的进入声明以及在后加工时的退出声明。

3.2 数据转移拦截器

3.2.1 params拦截器(defaultStack)

这个熟悉的拦截器将请求参数转移到通过ValueStack公开的属性(包括ModelDriven动作通过域模型对象提供的属性)上。params拦截器不知道这些数据最终会去哪里,它只是把数据转移到在ValueStack上发现的第一个匹配的属相上。

动作总是在请求处理周期开始时被放到ValueStack上。ModelDriven动作则被modelDriven拦截器放在ValueStack上。

3.2.2 static-params拦截器(defaultStack)

这个拦截器也将参数转移到ValueStack公开的属性上,不同的是参数来源于声明性框架(XML)的动作元素中。

<action name="Upload" class="com.moodpo.review.action.Upload">
    <!-- 上传路径 -->
    <param name="storePath">E:\temp\</param>
    <result>/Uploaded.jsp</result>
    <result name="input">/UploadForm.jsp</result>
</action>

注意一点,defaultStack中static-params拦截器在params拦截器之前触发,这意味着请求参数会覆盖XML中param元素的值。

3.2.3 autowiring拦截器

这个拦截器为使用Spring管理应用程序资源提供了一个集成点。复习Spring的时候再说吧。

3.2.4 servlet-config拦截器(defaultStack)

这个拦截器将来源于Servlet API的各种对象注入到动作中,动作只需实现必要的接口即可。以下的接口可以用来取得与Servlet环境相关的不同对象。

ServletContextAware设置ServletContext
ServletRequestAware设置HttpServletRequest
ServletResponseAware设置HttpServletResponse
ParameterAware设置Map类型的请求参数
RequestAware设置Map类型的请求属性
SessionAware设置Map类型的会话属性
ApplicationAware设置Map类型的应用程序领域属性
PrincipalAware设置Principal对象(安全相关)

每一个接口包含一个方法——当前资源的设置方法。最佳实践建议避免使用Servlet API对象,因为他们会将动作代码绑定到Servlet API.

3.2.5 fileUpload拦截器(defaultStack)

fileUpload拦截器将文件和元数据从多重请求转换为常规的请求参数,以便能够将它们像普通参数一样设置到动作上。

3.3 工作流拦截器

工作流拦截器提供改变请求处理的工作流的机会,这里的工作流是指贯穿拦截器、动作、结果,最后又回到拦截器的处理路径。工作流拦截器检查处理的状态,有条件的干涉、改变正常路径。

3.3.1 workflow拦截器(defaultStack)

确实有一个拦截器就叫workflow。它与动作协作,提供数据验证以及验证错误发生时改变后续工作流的功能。workflow拦截器还引入了另一个重要的拦截器概念——使用params调整拦截器的执行。workflow拦截器可以使用以下几个参数:

alwaysInvokeValidate(true或者false,默认是true,意味着validate()方法会被调用)
inputResultName(验证失败时选择的结果的名字,默认值是Action.INPUT)
excludeMethods(workflow拦截器不应执行的方法名,这样可以略过一个特定入口方法的验证检查)

这些内容在defaultStack中进行了配置,如下来源于struts-default.xml的代码片段:

</interceptor-stack>
    ......
    <interceptor-ref name="workflow">
        <param name="excludeMethods">input,back,cancel,browse</param>
    </interceptor-ref>
</interceptor-stack>

这样配置将排除input,back,cancel,browse方法上的validate()验证。

3.3.2 validation拦截器(defaultStack)

validation拦截器是Struts2验证框架的一部分,提供了声明性的方式验证你的数据。不用编写验证代码,验证框架让你使用XML或者Java注解描述数据的验证规则。

注意:validation拦截器在workflow拦截器之前触发。

3.3.3 prepare拦截器(defaultStack)

prepare拦截器提供了一种向动作追加额外工作流处理的通用入口点。当prepare拦截器执行时,它在动作上查找prepare()方法(首先查找是否实现了Preparable接口),它允许执行任何类型的预处理。

prepare拦截器也很灵活,例如它可以在一个动作上为不同的执行方法定义特别的预备方法。以下是预备方法的命名约定:

动作方法名    预处理方法1                  预处理方法2 
input()        prepareInput()          prepareDoInput() 
update()     prepareUpdate()       prepareDoUpdate()
3.3.4 modelDriven(defaultStack)

modelDriven拦截器通过调用getModel()方法改变执行的工作流,并将模型对象放在ValueStack上从请求接受参数。在不使用这个拦截器的情况下,参数会被params拦截器直接转移到动作对象上。

3.4 其他拦截器

3.4.1 exception拦截器(defaultStack)

exception拦截器在defaultStack中出现在第一位,也应该在任何你创建的自定义栈中出现在第一位。exception拦截器捕获异常,并根据类型将它们映射到用户自定义的错误页面。它的位置在栈的顶端,这样可以保证它能够捕获动作调用所有阶段可能生成的所有异常,而在后加工阶段又会最后一个触发,因此能够捕获所有的异常。当捕获异常时,exception拦截器会创建一个ExceptionHolder对象,并将其放在ValueStack的最顶端,ExceptionHolder是一个异常的包装器,它把跟踪栈和异常作为JavaBean属性公开出来,可以在错误页面中通过标签访问这些属性。

3.4.2 token拦截器和token-session拦截器

token和token-session拦截器可以作为避免表单重复提交系统的一部分。token拦截器用来向被拦截器检查的请求传入一个令牌,如果唯一的令牌再次来到拦截器,那么这个请求被视为重复的。

3.4.3 scoped-modelDriven拦截器(defaultStack)

这个拦截器为动作的模型对象提供跨请求的向导式的持久性,它增加了modelDriven拦截器的功能,允许你将模型对象存储到绘画作用域中。

3.4.4 execAndWait拦截器

当一个请求需要执行很长时间时,最好能给用户一些反馈,execAndWait拦截器可帮助用户避免急躁。

3.5 内置的拦截器栈

大部分可能会用到的其他内置拦截器栈都是defaultStack的简化版本,这些较小的栈是为了成为模块化的构建单元,用来构建更大的栈。

一般情况下推荐使用defaultStack,在某些情况下,多余的拦截器并不会那么影响性能,况且混乱的组织拦截器可能会迅速增加调试的复杂度,因此还是使用内置的最容易的途径——defaultStack吧。

Struts2不仅非常灵活,而且它也代表着更多的开箱即用。

Action of Struts 2

Struts2是一个面向动作的框架,Action是它的核心。

1.Struts2中Action的作用

封装业务单元 为数据转移提供场所 为结果选择路由

2.使用包(package)机制将Action分组

包是一种逻辑容器,想Java的包一样,它提供了一种基于功能或者领域的共性将action组件分组。包级别上定义的URL可用来映射到动作;此外它还具有继承的特性,你能够继承框架已经定义好的一些组件。 包声明只需设置它的四个属性:

属性          描述
name(必须)    包的名字
namespace    包内所有action的命名空间
extends      被继承的父包
abstract     如果为true,这个包只能用来定义可继承的组件,不能定义动作

<package name="chapterThreeSecure" namespace="/chapterThree/secure" extends="struts-default">
        <action name="AdminPortfolio">
            <result>/chapterThree/AdminPortfolio.jsp</result>
        </action>
        <action name="AddImage">
            <result>/chapterThree/ImageUploadForm.jsp</result>
        </action>
        <action name="RemoveImage">
            <result>/chapterThree/ImageRemoved.jsp</result>
        </action>
</package>

上面的配置展示了包的用法。当一个请求(比如AddImage)到达时,框架会在/chapterThree/secure命名空间下查找AddImage的action方法,如果没有设置命名空间,请求动作就会进入默认命名空间查找。因为继承了struts-default包,而使用其中的默认命名空间。

struts-default包中的组件

只要继承struts-default包,就会自动使用它已经定义好的组件。大部分常用的拦截器在其中已经定义好了,这个包在struts2-core.jar的struts-default.xml中定义,它声明了大多数应用程序用到的拦截器栈:

  <package name="struts-default" abstract="true">
         ........

        <!-- A complete stack with all the common interceptors in place.
             Generally, this stack should be the one you use, though it
             may do more than you need. Also, the ordering can be
             switched around (ex: if you wish to have your servlet-related
             objects applied before prepare() is called, you'd need to move
             servletConfig interceptor up.

             This stack also excludes from the normal validation and workflow
             the method names input, back, and cancel. These typically are
             associated with requests that should not be validated.
             -->
        <interceptor-stack name="defaultStack">
            <interceptor-ref name="exception"/>
            <interceptor-ref name="alias"/>
            <interceptor-ref name="servletConfig"/>
            <interceptor-ref name="i18n"/>
            <interceptor-ref name="prepare"/>
            <interceptor-ref name="chain"/>
            <interceptor-ref name="debugging"/>
            <interceptor-ref name="scopedModelDriven"/>
            <interceptor-ref name="modelDriven"/>
            <interceptor-ref name="fileUpload"/>
            <interceptor-ref name="checkbox"/>
            <interceptor-ref name="multiselect"/>
            <interceptor-ref name="staticParams"/>
            <interceptor-ref name="actionMappingParams"/>
            <interceptor-ref name="params">
              <param name="excludeParams">dojo\..*,^struts\..*</param>
            </interceptor-ref>
            <interceptor-ref name="conversionError"/>
            <interceptor-ref name="validation">
                <param name="excludeMethods">input,back,cancel,browse</param>
            </interceptor-ref>
            <interceptor-ref name="workflow">
                <param name="excludeMethods">input,back,cancel,browse</param>
            </interceptor-ref>
        </interceptor-stack>

        ......

   </interceptors>

    <default-interceptor-ref name="defaultStack"/>

    <default-class-ref class="com.opensymphony.xwork2.ActionSupport" />
</package>

注意那个叫做params的拦截器,从请求到动作的数据自动转移就是由它完成的。

3.实现动作(Action)

struts2中的action只需要实现一个返回控制字符串的execute()方法即可,不需要实现action接口,因此基本上任何一个类都可以成为一个动作(action).即使如此框架还是提供了一些接口和实现来让工作变得更简单,成本更小。

Action接口
如果一个action实现了com.opensymphony.xwork2.Action接口,它就可以使用一下常量,而这些也正是框架中使用的默认值:

public static final String ERROR="error";
public static final String INPUT="input";
public static final String LOGIN="login";
public static final String NONE="none";
public static final String SUCCESS="success";

ActionSupport类
此类提供了数据验证、错误信息传输及错误信息国际化等功能。

Ⅰ.基本验证

ActionSupport类使用两个接口与默认拦截器栈的DefaultWorkflowInterceptor拦截器配合完成基本验证。

        <interceptor-ref name="workflow">
            <param name="excludeMethods">input,back,cancel,browse</param>
        </interceptor-ref>

当workflow拦截器被触发时,它会在action中查找并执行validate()方法,ActionSupport实现了该方法,不过在实际项目中我们一般要使用自己的业务验证逻辑重写此方法。

@Override
public void validate() {
    // TODO Auto-generated method stub

    //添加一个字段的数据错误
    addFieldError("fieldName", "errorMessage");
    //使用国际化
    addFieldError("name", getText("properties_name"));
    //添加一个action范围内的错误
    addActionError("anErrorMessage");

}

当执行完validate()方法后,workflow拦截器会检查是否有错误信息。如果有错误信息,它将改变请求的工作流,它会立即停止请求的处理,将用户带回到表单页面并显示相应的错误提示。

Ⅱ.使用资源包(国际化)

ActionSupport实现了两个接口,它们协作完成了国际化文本信息的功能。首先是实现com.opensymphony.xwork2.TextProvider接口,可以从资源属性文件中通过关键字获取文本的本地化信息。下面是regist_en.properties文件的内容:

user.exists=This user already exists.
username.required=Username is required.
password.required=Password is required.
portfolioName.required=Portfolio Name is required.

属性文件创建之后,在action中使用getText("property_name")方法即可引用资源属性文件中的本地化信息。

其次,ActionSupport实现了com.opensymphony.xwork2.LocaleProvider接口,它只提供了一个方法getLocale(),可通过浏览器发送来的地域语言信息获取用户所在的地域,从而判断使用那一种资源属性文件来国际化。

4.action传递域对象数据

action把请求中的数据放在简单的JavaBean对象上,这种做法很好很强大,但是当JavaBean对象足够复杂之后(如具有很多属性),这种做法就不够强大了。既然框架提供了数据转移的机制,我们何不直接使用域对象进行数据转移呢?是的,框架允许我们这么做。

有两种实现方式可以做的这一点:

使用JavaBean对象,让数据直接传输到这个对象上 使用模型驱动(ModelDriven)的action

Ⅰ.使用JavaBean方式

public String execute(){
    getPortfolioService().createAccount( user ); 
    return SUCCESS;
}

private User user; 

public User getUser() {
    return user;
}

public void setUser(User user) {
    this.user = user;
}

public void validate(){

    if ( getUser().getPassword().length() == 0 ){           
        addFieldError( "user.password", getText("password.required") );
    }
    if ( getUser().getUsername().length() == 0 ){           
        addFieldError( "user.username", getText("username.required") );
    }
            ....
}

后台代码变的简单了,前台代码就变复杂点了,就是这样无奈。

<s:textfield name="user.name" label="姓名"/>

Ⅱ使用模型驱动(ModelDriven)

使用ModelDriven与使用JavaBean方法不同,它使用getModel()方法将域对象公开。它引入了新的接口和拦截器,但拦截器已经包含在默认拦截器栈中了,我们只需要实现getModel()方法即可。

public class ModelDrivenRegister extends ActionSupport implements ModelDriven {

    public String execute(){//注意在execute方法中只能使用user本来的引用,而不能做任何将其
                                    //改变引用的操作
        getPortfolioService().createAccount( user ); 
        return SUCCESS;
    }

    private User user = new User();//注意使用模型驱动需要初始化模型对象

    public Object getModel() {
        return user;
    }

    public void validate(){
        PortfolioService ps = getPortfolioService();

        if ( user.getPassword().length() == 0 ){            
            addFieldError( "password", getText("password.required") );
        }
        ......
    }

    public PortfolioService getPortfolioService( )  {
        return new PortfolioService();
    }
}

值得庆幸的是使用模型驱动的方式传递数据在视图层不需要做任何改变,这也许就是为什么一般选择使用模型驱动而不选择JavaBean方式的原因了。

Ⅲ.使用域对象传递数据的潜在危险

像上面那样使用域对象传递数据的做法很好,但是这样做的前提是要将域对象公开出去,无论是使用JavaBean方式还是模型驱动方式都是如此。但是无论如何将域对象整个的暴露给用户总不是一个很好的做法,它还存在一些潜在的威胁。比如说域对象中有一些敏感的数据你不想公开给外界;一个恶意的用户在请求中添加一个适当命名精心设计的查询语句,这样这个参数就会被自动写入到域对象的属性上。当然你可以进行适当的过滤,但是这样一来使用域对象的目的就变的无效了。这个问题现在也没有很好的解决方法,看的人自己想想,在实际的开发中记住这一点就行。

Hello Struts 2

本文主要复习Struts2的两种声明性架构:

  • 基于xml的声明性架构;
  • 基于java注解的声明性架构.

需要的jar包:

  • commons-fileupload-1.2.1.jar
  • commons-io-1.3.2.jar
  • commons-logging-1.0.4.jar
  • commons-logging-api-1.1.jar
  • freemarker-2.3.16.jar
  • javassist-3.7.ga.jar
  • ognl-3.0.jar
  • struts2-core-2.2.1.1.jar
  • xwork-core-2.2.1.1.jar

1.基于xml的声明性架构

web.xml配置:

<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

struts.xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
    <!-- 运行于开发者模式 -->
    <constant name="struts.devMode" value="true"></constant>
    <package name="struts2demo1" namespace="/demo1" extends="struts-default">
        <action name="Name">
            <result>/nameCollection.jsp</result>
        </action>
        <action name="Hello" class="xiaoxie.review.struts2demo.HelloworldAction">
            <result name="SUCCESS">/helloworld.jsp</result>
        </action>
    </package>
</struts>

这个示例中包含了两个动作(action),其中一个几乎什么都没做。package元素是一个重要的容器元素,示例中它声明了一个当框架将url映射到动作时需要使用的命名空间:

http://+localhost:8080/+struts2demo/+demo1/+helloworld.action
协议+主机名:端口+servlet上下文+命名空间+动作

nameCollection.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Name Collection</title>
</head>
<body>
    <center>
        <div>
            <h4>输入姓名</h4>
            <s:form action="Hello">
                <s:textfield name="name" label="姓名"/>
                <s:submit value="Submit"></s:submit>
            </s:form>
        </div>
    </center>
</body>
</html>

helloworld.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>HelloWorld</title>
</head>
<body>
    <center>
        <h4>Hello</h4>
        <div><s:property value="coutomeName"/></div>
    </center>
</body>
</html>

HelloworldAction.java

package xiaoxie.review.struts2demo;

public class HelloworldAction {
    private String name;
    private String coutomeName;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public String getCoutomeName() {
        return coutomeName;
    }
    public void setCoutomeName(String coutomeName) {
        this.coutomeName = coutomeName;
    }
    public String execute(){
        setCoutomeName("Hello "+getName());
        return "SUCCESS";
    }
}

使用JavaBean的方式编写action使领域数据总是存储在action中,action的execute方法可以很方便的访问到数据;而为了在其他地方(比如jsp)也能访问到数据,框架底层也把数据放到了ValueStack中。ValueStack的机制是将action中的所有属性作为它的第一级属性公开出来,这样就可以使用ONGL来访问(<s:property value="coutomeName"/>).

2.基于java注解的声明性架构

在web.xml中添加以下代码

<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    <init-param>
        <!-- 扫描xiaoxie包,查找注解 -->
        <param-name>actionPackages</param-name>
        <param-value>xiaoxie</param-value>
    </init-param>
</filter>

标记哪个类是action类有两种方法:

  • 让action类实现com.opensymphony.xwork2.Action接口
  • 使用命名约定,类名以Action结尾

例如:

@Result(name="SUCCESS",location="/helloworld.jsp")  
public class HelloworldAction{ ...... }