码迷,mamicode.com
首页 > 其他好文 > 详细

struts2 自实现

时间:2018-12-31 15:51:31      阅读:205      评论:0      收藏:0      [点我收藏+]

标签:new   sde   edm   public   复习   接口   解耦   Opens   rabl   

  • Struts2 自实现:
  • 1). 搭建 Struts2 的开发环境

    2). 不需要显式的定义 Filter, 而使用的是 struts2 的配置文件.

    3). details.jsp 比先前变得简单了.

    ${requestScope.product.productName} -> ${productName}

    4). 步骤:

    I. 由 product-input.action 转到 /WEB-INF/pages/input.jsp

    在 struts2 中配置一个 action
    
    <action name="product-input">
        <result>/WEB-INF/pages/input.jsp</result>
    </action>

    II. 由 input.jsp 页面的 action: product-save.action 到 Product‘s save, 再到 /WEB-INF/pages/details.jsp

    <action name="product-save" class="com.atguigu.struts2.helloworld.Product"
        method="save">
        <result name="details">/WEB-INF/pages/details.jsp</result>  
    </action>
    
    在 Prodcut 中定义一个 save 方法, 且返回值为 details
    
    5. result:

    1). result 是 action 节点的子节点

    2). result 代表 action 方法执行后, 可能去的一个目的地

    3). 一个 action 节点可以配置多个 result 子节点.

    4). result 的 name 属性值对应着 action 方法可能有的一个返回值.

    <result name="index">/index.jsp</result>

    5). result 一共有 2 个属性, 还有一个是 type: 表示结果的响应类型

    6). result 的 type 属性值在 struts-default 包的 result-types 节点的 name 属性中定义.
    常用的有

    dispatcher(默认的): 转发. 同 Servlet 中的转发.
    redirect: 重定向
    redirectAction: 重定向到一个 Action
    注意: 通过 redirect 的响应类型也可以便捷的实现 redirectAction 的功能!

    <result name="index" type="redirectAction">
        <param name="actionName">testAction</param>
        <param name="namespace">/atguigu</param>
    </result>
    
    OR
    
    <result name="index" type="redirect">/atguigu/testAction.do</result>
    
    > chain: 转发到一个 Action
        注意: 不能通过 type=dispatcher 的方式转发到一个 Action
    
         只能是:
    
    <result name="test" type="chain">
        <param name="actionName">testAction</param>
        <param name="namespace">/atguigu</param>
    </result>
    
    不能是:
    
    <result name="test">/atguigu/testAction.do</result>
    1. ActionSupport

    1). ActionSupport 是默认的 Action 类: 若某个 action 节点没有配置 class 属性, 则 ActionSupport 即为
    待执行的 Action 类. 而 execute 方法即为要默认执行的 action 方法

    <action name="testActionSupport">
    <result>/testActionSupport.jsp</result>
    </action>

    等同于

    <action name="testActionSupport"
    class="com.opensymphony.xwork2.ActionSupport"
    method="execute">
    <result>/testActionSupport.jsp</result>
    </action>

    2). 在手工完成字段验证, 显示错误消息, 国际化等情况下, 推荐继承 ActionSupport.

    1. 关于 Struts2 请求的扩展名问题

    1). org.apache.struts2 包下的 default.properties 中配置了 Struts2 应用个的一些常量

    2). struts.action.extension 定义了当前 Struts2 应用可以接受的请求的扩展名.

    3). 可以在 struts.xml 文件中以常量配置的方式修改 default.properties 所配置的常量.

    <constant name="struts.action.extension" value="action,do,"></constant>

    1. 在 Action 中访问 WEB 资源:

    1). 什么是 WEB 资源 ?

    HttpServletRequest, HttpSession, ServletContext 等原生的 Servlet API。 

    2). 为什么访问 WEB 资源?

    B\S 的应用的 Controller 中必然需要访问 WEB 资源: 向域对象中读写属性, 读写 Cookie, 获取 realPath ....

    3). 如何访问 ?

    I. 和 Servlet API 解耦的方式: 只能访问有限的 Servlet API 对象, 且只能访问其有限的方法(读取请求参数, 读写域对象的属性, 使 session 失效...).

    > 使用 ActionContext
    
    > 实现 XxxAware 接口
    
    > 选用的建议: 若一个 Action 类中有多个 action 方法, 且多个方法都需要使用域对象的 Map 或 parameters, 则建议使用
    Aware 接口的方式
    
    > session 对应的 Map 实际上是 SessionMap 类型的! 强转后若调用其 invalidate() 方法, 可以使其 session 失效!

    II. 和 Servlet API 耦合的方式: 可以访问更多的 Servlet API 对象, 且可以调用其原生的方法.

    > 使用 ServletActionContext
    
    > 实现 ServletXxxAware 接口.
    1. 复习搭建 Struts2 的开发环境: 3 个步骤

    2. action VS Action 类

    1). action: 代表一个 Struts2 的请求.

    2). Action 类: 能够处理 Struts2 请求的类.

    > 属性的名字必须遵守与 JavaBeans 属性名相同的命名规则. 
        属性的类型可以是任意类型. 从字符串到非字符串(基本数据库类型)之间的数据转换可以自动发生
    
    > 必须有一个不带参的构造器: 通过反射创建实例 
    
    > 至少有一个供 struts 在执行这个 action 时调用的方法
    
    > 同一个 Action 类可以包含多个 action 方法. 
    
    > Struts2 会为每一个 HTTP 请求创建一个新的 Action 实例, 即 Action 不是单例的, 是线程安全的. 
    1. 关于值栈:

    1). helloWorld 时, ${productName} 读取 productName 值, 实际上该属性并不在 request 等域对象中, 而是从值栈中获取的.

    2). ValueStack:

    I. 可以从 ActionContext 中获取值栈对象
    II. 值栈分为两个逻辑部分

    > Map 栈: 实际上是 OgnlContext 类型, 是个 Map, 也是对 ActionContext 的一个引用. 里边保存着各种 Map:
             requestMap, sessionMap, applicationMap, parametersMap, attr
    
    > 对象栈: 实际上是 CompoundRoot 类型, 是一个使用 ArrayList 定义的栈. 里边保存各种和当前 Action 实例相关的对象.
                       是一个数据结构意义的栈.
    1. Struts2 利用 s:property 标签和 OGNL 表达式来读取值栈中的属性值

      1). 值栈中的属性值:

      对于对象栈: 对象栈中某一个对象的属性值

      Map 栈: request, session, application 的一个属性值 或 一个请求参数的值.

      2). 读取对象栈中对象的属性:

      若想访问 Object Stack 里的某个对象的属性. 可以使用以下几种形式之一:

      object.propertyName ; object[‘propertyName‘] ; object["propertyName"]

      ObjectStack 里的对象可以通过一个从零开始的下标来引用. ObjectStack 里的栈顶对象可以用 [0] 来引用,
      它下面的那个对象可以用 [1] 引用.

      [0].message

      [n] 的含义是从第 n 个开始搜索, 而不是只搜索第 n 个对象

      若从栈顶对象开始搜索, 则可以省略下标部分: message

      结合 s:property 标签: <s:property value="[0].message" /> <s:property value="message" />

      3). 默认情况下, Action 对象会被 Struts2 自动的放到值栈的栈顶.

    2. 使用 paramsPrepareParamsStack 拦截器栈后的运行流程

    1). paramsPrepareParamsStack 和 defaultStack 一样都是拦截器栈. 而 struts-default 包默认使用的是
    defaultStack

    2). 可以在 Struts 配置文件中通过以下方式修改使用的默认的拦截器栈

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

    3). paramsPrepareParamsStack 拦截器在于

    params -> modelDriven -> params

    所以可以先把请求参数赋给 Action 对应的属性, 再根据赋给 Action 的那个属性值决定压到值栈栈顶的对象, 最后再为栈顶对象的属性赋值.

    对于 edit 操作而言:

    I. 先为 EmployeeAction 的 employeeId 赋值
    II. 根据 employeeId 从数据库中加载对应的对象, 并放入到值栈的栈顶
    III. 再为栈顶对象的 employeeId 赋值(实际上此时 employeeId 属性值已经存在)
    IV. 把栈顶对象的属性回显在表单中.

    4). 关于回显: Struts2 表单标签会从值栈中获取对应的属性值进行回显.

    5). 存在的问题:

    getModel 方法

    public Employee getModel() {
    if(employeeId == null)
    employee = new Employee();
    else
    employee = dao.get(employeeId);

    return employee;

    }

    I. 在执行删除的时候, employeeId 不为 null, 但 getModel 方法却从数据库加载了一个对象. 不该加载!
    II. 指向查询全部信息时, 也 new Employee() 对象. 浪费!

    6). 解决方案: 使用 PrepareInterceptor 和 Preparable 接口.

    7). 关于 PrepareInterceptor

    [分析后得到的结论]

    若 Action 实现了 Preparable 接口, 则 Struts 将尝试执行 prepare[ActionMethodName] 方法,
    若 prepare[ActionMethodName] 不存在, 则将尝试执行 prepareDo[ActionMethodName] 方法.
    若都不存在, 就都不执行.

    若 PrepareInterceptor 的 alwaysInvokePrepare 属性为 false,
    则 Struts2 将不会调用实现了 Preparable 接口的 Action 的 prepare() 方法

    [能解决 5) 的问题的方案]

    可以为每一个 ActionMethod 准备 prepare[ActionMethdName] 方法, 而抛弃掉原来的 prepare() 方法
    将 PrepareInterceptor 的 alwaysInvokePrepare 属性置为 false, 以避免 Struts2 框架再调用 prepare() 方法.

    如何在配置文件中为拦截器栈的属性赋值: 参看 /struts-2.3.15.3/docs/WW/docs/interceptors.html

    <interceptors>
    <interceptor-stack name="parentStack">
    <interceptor-ref name="defaultStack">
    <param name="params.excludeParams">token</param>
    </interceptor-ref>
    </interceptor-stack>
    </interceptors>

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

    ----------------------------------源代码解析---------------------------------

    public String doIntercept(ActionInvocation invocation) throws Exception {
    //获取 Action 实例
    Object action = invocation.getAction();

    //判断 Action 是否实现了 Preparable 接口
    if (action instanceof Preparable) {
        try {
            String[] prefixes;
            //根据当前拦截器的 firstCallPrepareDo(默认为 false) 属性确定 prefixes
            if (firstCallPrepareDo) {
                prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX};
            } else {
                prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX};
            }
            //若为 false, 则 prefixes: prepare, prepareDo
            //调用前缀方法.
            PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes);
        }
        catch (InvocationTargetException e) {
    
            Throwable cause = e.getCause();
            if (cause instanceof Exception) {
                throw (Exception) cause;
            } else if(cause instanceof Error) {
                throw (Error) cause;
            } else {
                throw e;
            }
        }
    
        //根据当前拦截器的 alwaysInvokePrepare(默认是 true) 决定是否调用 Action 的 prepare 方法
        if (alwaysInvokePrepare) {
            ((Preparable) action).prepare();
        }
    }
    
    return invocation.invoke();

    }

    PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes) 方法:

    public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
    //获取 Action 实例
    Object action = actionInvocation.getAction();
    //获取要调用的 Action 方法的名字(update)
    String methodName = actionInvocation.getProxy().getMethod();

    if (methodName == null) {
        // if null returns (possible according to the docs), use the default execute 
        methodName = DEFAULT_INVOCATION_METHODNAME;
    }
    
    //获取前缀方法
    Method method = getPrefixedMethod(prefixes, methodName, action);
    
    //若方法不为 null, 则通过反射调用前缀方法
    if (method != null) {
        method.invoke(action, new Object[0]);
    }

    }

    PrefixMethodInvocationUtil.getPrefixedMethod 方法:

    public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) {
    assert(prefixes != null);
    //把方法的首字母变为大写
    String capitalizedMethodName = capitalizeMethodName(methodName);

    //遍历前缀数组
    for (String prefixe : prefixes) {
        //通过拼接的方式, 得到前缀方法名: 第一次 prepareUpdate, 第二次 prepareDoUpdate
        String prefixedMethodName = prefixe + capitalizedMethodName;
        try {
            //利用反射获从 action 中获取对应的方法, 若有直接返回. 并结束循环.
            return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY);
        }
        catch (NoSuchMethodException e) {
            // hmm -- OK, try next prefix
            if (LOG.isDebugEnabled()) {
                LOG.debug("cannot find method [#0] in action [#1]", prefixedMethodName, action.toString());
            }
        }
    }
    return null;

    }

    1. Action 实现 ModelDriven 接口后的运行流程

    1). 先会执行 ModelDrivenInterceptor 的 intercept 方法.

    public String intercept(ActionInvocation invocation) throws Exception {
        //获取 Action 对象: EmployeeAction 对象, 此时该 Action 已经实现了 ModelDriven 接口
        //public class EmployeeAction implements RequestAware, ModelDriven<Employee>
        Object action = invocation.getAction();
    
        //判断 action 是否是 ModelDriven 的实例
        if (action instanceof ModelDriven) {
            //强制转换为 ModelDriven 类型
            ModelDriven modelDriven = (ModelDriven) action;
            //获取值栈
            ValueStack stack = invocation.getStack();
            //调用 ModelDriven 接口的 getModel() 方法
            //即调用 EmployeeAction 的 getModel() 方法
            /*
            public Employee getModel() {
                employee = new Employee();
                return employee;
            }
            */
            Object model = modelDriven.getModel();
            if (model !=  null) {
                //把 getModel() 方法的返回值压入到值栈的栈顶. 实际压入的是 EmployeeAction 的 employee 成员变量
                stack.push(model);
            }
            if (refreshModelBeforeResult) {
                invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
            }
        }
        return invocation.invoke();
    }

    2). 执行 ParametersInterceptor 的 intercept 方法: 把请求参数的值赋给栈顶对象对应的属性. 若栈顶对象没有对应的属性, 则查询
    值栈中下一个对象对应的属性...

    3). 注意: getModel 方法不能提供以下实现. 的确会返回一个 Employee 对象到值栈的栈顶. 但当前 Action
    的 employee 成员变量却是 null.

    public Employee getModel() {
    return new Employee();
    }

    struts2 自实现

    标签:new   sde   edm   public   复习   接口   解耦   Opens   rabl   

    原文地址:http://blog.51cto.com/9237329/2337333

    (0)
    (0)
       
    举报
    评论 一句话评论(0
    登录后才能评论!
    © 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
    迷上了代码!