• Struts2【数据存储中心与OGNL】


     

    一  OGNL(对象图导航语言)简介

     如果要在Struts2使用OGNL表达式,必须放到Struts2标签中

    <%@ taglib uri="/struts-tags" prefix="s" %>

      OGNL相对其他表达式语言具有下面几个优势:

      (1)支持对象方法调用

        <s:property value="'abcde'.length()"/>

       (2) 支持类静态的方法调用和值访问

        <!-- 访问静态变量 -->
        <s:property value="@java.lang.Integer@MAX_VALUE"/>
        <!-- 访问静态方法 -->
        <s:property value="@java.lang.Math@abs(-100)"/> <!-- 没有显示,就是出错了,实际原因是Struts2不允许调静态方法,在default.properties中设置了false -->

       (3) 支持赋值操作和表达式串联

     *  (4)   访问OGNL上下文和ActionContext

     *  (5) 操作集合对象


     

    二  访问OGNL上下文和ActionContext

    官方文档解释如下: 

       【疑问】  

          Q: 什么是OGNL的root对象?

          A: OGNL中文名是对象导航图,所谓的对象图就是说通过一个对象,OGNL可以获得与这个对象关联的其他对象,所以根对象就是关联其他对象的对象。例子如下:

    //Person就是根对象
    public class Person{
       private  Man man; //Person的子类
       private LittleMan LittlerMan;//Man的子类,Man又是Person的子类
       ....       
    } 

          Q: 什么是OGNL的context对象?

          A:如果多个对象之间没有联系,这时候我们就需要给OGNL传递一个Map类型的对象,把这些没有联系的对象全部放进Map,这个Map对象我们就称作是他的context对象,注意:root对象也被放进了Map中。

       【总结】   

         现在Struts2对OGNL进行了封装,OGNL context我们现在叫做ActionContext,OGNL root我们现在叫做value stack(root),大概可以得知,ActionContext装的多个对象之间没关联,而ValueStack装的根对象和于根对象关联的对象。



      2.1  Context Map(=ActionContext)

                         包装后的OGNL就是根据ActionContext(数据中心)来获取值的,那么存的是什么

                    ContextMap
    key value 注释
    application ServletContext中的所有属性(指的是Map对象) 相当于EL的applicationScope(意味着是说map本身)
    session HttpSession中的所有属性(指的是Map对象) 相当于EL的sessionScope
    request HttpServletRequest中的所有属性(指的是Map对象) 相当于requestScope
    valuestack(root) List(栈结构)  
    action 动作类 动作类的属性
    parameters 包含当前Http请求参数的Map,map=request.getParameterMap(),key是参数  
    attr 从四个属性范围中查找 可以获得其他范围的属性${}


      2.2  Root(=ValueStack)

      包装后的OGNL Root是ValueStack(Root),那么在ValuseStack中存储的是什么呢?
        通过很多文章,我想都应该知道,在Struts2接收到*.action请求时创建Action实例,ValueStack和ActionContext,把Action实例压入栈顶,在调用动作方法之前,会把Action相应属性放到ValueStack顶层节点,所有属性还是默认值,ActionInvocation调用了动作方法后,会经过一系列拦截器,这是params拦截器就动态的开始封装了参数,然后才轮到动作方法执行。我们关注的是压栈的过程

                      未执行Action之前:

       

                     执行了如下Action之后:

    public class StudentAction extends ActionSupport implements ModelDriven<Student> {
        private Student student=new Student();
        private String[] hobbies;
        private String testStr="duange";
        generate getter and setter...
    }

           

      这样就清楚看到了ValueStack中存入了什么东西,Action实例和Action中关联的类对象实例

     ValueStack接口的默认实现类OgnlValueStack

      在Struts2中,通过ValueStack接口默认实现类 com.opensymphony.xwork2.ognl的API,我们不但可以往ValueStack(Root)对象里放值,这里ValueStack中的Root对象的类型是CompoundRoot,这个类继承了ArrayList,并提供了peek,push,pop等方法实现了栈结构,所以所说的Action压入ValueStack,也可以说是Action对象时CompoundRoot的一个元素,也可以往context Map(ActionContext)里面放值,并且OGNL在jsp页面使用时的具体原理也就是调用了这个默认实现类的API,因为方法的参数就是一个OGNL表达式。

      网上大多数都说,ValueStack就是Struts2对OGNL的封装,我觉得是因为ValuetStack的API不仅可以操作root对象,并且可以获取ActionContext的引用,并获取context Map中的对象

    ActionContext ac=ActionContext.getContext();
    ValueStack vs=ac.getValueStack();
    S.o.p(vs.getContext()==ac.getContextMap()); //返回true

      

      清楚了数据的存储的两个地方contextMap和valueStack(root)后,我们先看下Struts2给我们封装成ActionContext和ValueStack提供的API,来了解如何在非页面的地方存数据以及如何放数据,了解这些是因为他就是在jsp页面使用OGNL操作的原理。


    【ActionContext,ValueStack,Action小结】

      (一) 生命周期

        ActionContext(ContextMap):每一次请求都会创建,保持在线程的局部变量(ThreadLocal)中,线程安全 

        ValueStack(root):每一次请求都会创建,保存在线程的局部变量(TheadLocal)中,线程安全

        action:每一次请求都会创建,保存在ValueStack栈顶,线程安全  

      (二) 三者间的联系与流程梳理

        当发出一个请求时,Struts2的核心过滤器进行过滤,如果请求符合要求(后缀要求),符合的话,会创建这个请求的ActionContext,ValueStack,Action实例,把Action实例放进ValueStack中,把ValueStack放入Request请求中,然后传入Jsp页面。我们使用OGNL表达式就可以获取ValueStack中的Action属性了。 

      (三) Action实例创建时机

        我们具体分为转发与跳转两种情况:

          转发:如果Action实例在服务器进了转发,那么所有Action都会压入一个ValueStack中,并共享ValueStack

          跳转:如果Action实例在客户端跳转,那么当新的请求过来时就相当于一个新的线程,所以三者都是新new出来的。


      2.3  ActionContext API

        操作:存取ActionContext中的数据

        第一步:获取ActionContext实例

            因为ActionContext实例绑定到当前线程中,核心过滤器负责来创建ActionContext实例到当前线程中,所以我们直接调用静态方法即可

    ActionContext actionContext=ActionContext.getContext();

        第二步:页面使用<s:debug></s:debug/>     

            该标签可视化了ActionContext与ValueStack,方便我们进行开发 

            

       第三步:在动作类中对ActionContext进行存取操作

    //往ActionContext中放入值
    ActionContext.getContext().put("myput","muputValue");

        查看jsp中debug页面,在Stack Context(ActionContext)发现了数据

        

      

    //从ActionContext中获取值
    ActionContext.getContext().get("myput");
    //获取contextMap中的根对象
    ValueStack vs=ActionContext.getContext().getValueStack();
    System.out.println(vs.getRoot() instanceof ArrayList);//从ActionContext中获取ValueStack
    //获取ValueStack的三种方式
    //方式一:通过AcionContext获取
    ActionContext ac=ActionContext.getContext();
    ValueStack vs1=ac.getValueStack();
    //方式二,通过request请求获取
    Map<String,Map<String,Object>> map=(Map<String, Map<String, Object>>) ac.get("request");
    ValueStack vs2=(ValueStack) map.get("struts.valueStack");
    sop(vs1==vs2);//true
    //方式三,通过Map对象获取
      ValueStack vs3=(ValueStack)ac.get("com.opensymphony.xwork2.util.ValueStack.ValueStack");
    sop(vs1==vs3)//true 

      【源码:三种取得ValueStack的方式获得是一个对象】

        方式一:ActionContext.getContext().getValueStack();

      

      

        方式二:ActionContext.getContext().get("request").get("struts.valueStack");

     

        方式三:ActionContext.getContext().get(ValueStack.VALUE_STACK);

      

    //获取session的三种方式
    //方式一:从request请求中拿取session
    HttpSession hs=ServletActionContext().getRequest().getSession();
    hs.put("mysession","mysessionValue");
    String value=(String)hs.get("mysession");
    //方式二:从ActionContext拿取参数
    Map<String,Object> m=(Map<String, Object>) ac.get("session");
    m.get("p2");
    //方式三:从属性名为request中的Map对象拿取名为session的Map对象
    Map<String,Map<String,Object>> request=(Map<String, Map<String, Object>>) ac.get("request");
    request.get("session").get("niubi");
    //获取请求参数(注意,value是一个数组,哪怕只有一个参数)
    String[] s3=(String[]) ac.getParameters().get("name");
    sop(s3[0]);
    //获取应用范围的数据
    ActionContext.getContext().getApplication().put("dd","ddValue");

     [ 番外篇 ]

       Q:既然ServletActionContext和ActionContext都能获取request,servletContext数据,那么ServletActionContext和ActionContext获取的数据一样吗?

       A:ServletActionContext的本质从ActionContext中获取数据,但由于key值不同,获取的数据就不同

    【示例一:获取request】

      ServletActionContext中的getRequest方法源代码:

     public static HttpServletRequest getRequest() {
         return (HttpServletRequest) ActionContext.getContext().get(HTTP_REQUEST);
    }
            

      区别在这里:

      图一:ServletActionContext从如下key拿value

    public interface StrutsStatics {
        /**
         * Constant for the HTTP request object.
         */
        public static final String HTTP_REQUEST = "com.opensymphony.xwork2.dispatcher.HttpServletRequest";
      .... }

      图二:ActionContext从如下key拿value

    //ActionContext从属性名为“request”取值
    request

    //Request对象

     

    【示例二:获取ServletContext】

    ServletActionContext获取ServletContext也是通过上述的方式

    ServletActionContext.getServletContext()

    ActionContext.getContext().get("
    com.opensymphony.xwork2.ActionContext.application")


    他与ActionContext中的application是不相同的,从ActionContext获取的application是Map对象,而从ServletActionContext获取的getServletContext()是ServletContext本身



    
    

      2.4  ValueStack API

       获取ValueStack实例

    ActionContext ac=ActionContext.getContext();
    ValueStack vs1=ac.getValueStack();

        ValueStack操作ActionContext中的数据

    actionContext.put("a1", "a1Value");
    //两者取值都为a1Value
    方式一:actionContext.get("a1");
    方式二:valueStack.getContext().get("a1");
    
    
    //获取context Map中的value stack(根)
    CompoundRoot root=valueStack.getRoot();

       ValueStack操作根对象

      通过一开始的图,我们知道这个value stack是一个List结构,翻阅CompoundRoot的源码,可以看到他继承了ArrayList

      

    让我们关注下其中一个方法  cutStack(int index),可以看出实际上截取只是new了一个新的value stack(根),让我们通过实际代码来看下

    (1)往CompoundRoot(一般都说是ValueStack)中压入数据
    CompoundRoot cr=vs1.getRoot();
    //根实现了栈结构,因此我们压入数据
    cr.push(1);
    cr.push(2);

     (2)从<s:debug>。。下看栈结构

       

       (3)从栈中弹出,注意调用cutStack后的弹出情况

    //打印当前栈
    S.o.p(cr);
    S.o.p(cr.cutStack(1).peek());
    S.o.p(cr.peek());

          

    结论:原来的value stack没有删除,只是new除了一个新的value stack,含头不含尾的截去了栈顶,所以弹出的就是从栈顶往下index为1的对象,至于最后一行为什么还是弹出2,就是说虽然截取了,但原本被切的栈并没有被删除

     

       ValueStack操作普通数据

     

    //set(String key, Object o);
    ValueStack vs=ActionContext.getContext().getValueStack();
    vs.set("p1","pp1");
    查看根,发现他往栈顶压入了一个HashMap类型的对象


    方法执行原理:检测栈顶是不是一个Map(动作类永远在栈顶),如果栈顶不是一个Map,会创建一个HashMap,把数据装进Map,然后把Map压入栈顶
    //再次set一次
    vs.set("p2","pp2");

    结论:只有一个Map类型的对象,并不会创建两个

    //再次set一次,属性名相同
    vs.set("p2","pp2Again");

    结论:覆盖掉了属性名相同的

    //第一个参数是OGNL表达式
    //第二个参数是要设置的值
    //从栈顶开始搜索,相当于调用setP1("p1New");
    vs.setValue("p1","p1New");

    结论:把根中属性名为p1的值赋值为了p1New

    //往contextMap中放入了属性名为p1的数据
    vs.setValue("#p1","p1Value");
    vs.findValue("p1"); //查找根中的名为p1的值,输出"p1New"
    vs.findValue("#p1");//查找contextMap中的名为p1的值,输出“p1Value”

    findValue()方法会先从根中找,再从contextMap中去找,意味着

    vs.findValue("p1");//也可以找到contextMap中的值,

    另外,在jsp页面使用的<s:property value="#p1">的底层实现机制就是vs.findvalue("#p1");方法

    
    

     

    三。OGNL表达式的其他用法

      用法一:获取contextMap中的数据,OGNL表达式用‘#’开头


     ( 1 )   contextMap中放入数据,并查看contextMap
       
     ( 2 )  我们要获取year属性的值

     ( 3 )  在jsp页面显示contextMap中的值.
          前提:引入<%@  taglib uri="/struts-tags" prefix="s"%>标签
          使用:<s:property value="#year"> // 取出值(year2)



      用法二:获取root中的数据,OGNL表达式直接使用属性名

       ( 1 )   根(Root)中放入数据,并查看根
         

      ( 2 )  取出Date类型对象的year属性的值

      ( 3 )  在jsp页面显示根中的值

           前提:引入<%@  taglib uri="/struts-tags" prefix="s"%>标签

      •   <s:property value="year"> //此处就使用OGNL表达式来获取根中的值,它会依次从栈顶往下寻找属性为year的值。
      •       <s:property value="nnnnn">//如果没有对应的属性呢?什么都不会显示.
      •       <s:property value="[1].year"> //有两个Name相同的属性,默认取栈顶最近的,在OGNL表达式前面加上“[第几个].”来取值
        •   //[2].year这个原理就是调用了cutStack(int index);然后在新new的栈中peek()出来的原理

     

      用法三:在jsp中取数据,可以用ognl表达式也可以用el表达式

      EL表达式在Struts2中做了小小的改动,Struts2对原始的requet(服务器提供的request)进行了包装,我们知道EL表达式中${p1.name}就相当于request.getAttribute("p1"),我们查看下包装类对该方法的变动,org.apache.struts2.dispatcher.StrutsRequestWrapper是对应的包装类

      下面是对request中方法的改变源代码:

      我们发现其中在try{}语句中,得到了栈值对象,并调用了findValue方法(ValueStack的API)。先从ValueStack(Root)中找,然后再从contextMap(ActionContext)中查找属性名对应的value值

    示例:

    //动作类
    ServletActionContext.getRequest().setAttribute("duanyu", "ddd");
    <s:property value="#request.duanyu"/> //相当于${duanyu}

      

      用法三:集合的投影(只输出集合的一部分属性)

      用法四:使用OGNL构造List对象和Map对象

    <s:property value="{'a','b','c'}" /><br/>
    <s:property value="#{'k1':'k1Value','k2':'k2Value' }"/><br/>

          实际用法

    <s:radio name="gender" list="#{'man':'男性','woman':'女性' }"/><br/>
    <s:checkboxlist name="hobby" list="{'吃屎','睡觉','看书'}"/>

    显示结果:

    显示结果源码:

      用法五:把OGNL表达式当做普通字符串对待

    <s:property value="'plain text'" />

        用法六:把字符串当做OGNL表达式(%{})

    <!-- p3是contextMap中的一个属性 -->
    <s:textfield name="ddd" label="%{p3}"/>

        用法七:"$"的两个主要用途

        (1)在Struts配置文件中,引用OGNL表达式

        (2)国际资源文件中,引用OGNL表达式 ($)

          示例:文件上传中,动态设置下载文件的名字

  • 相关阅读:
    iOS 有用的代码片段
    iOS 限制软盘输入法
    UIlabel 遇到\n 换行iOS
    关于delegate 与 protocol 的理解 iOS
    ios 跳转到app store
    iOS 上下左右滑动手势
    求某段程序运行的高精度时间
    转载——GDB中应该知道的几个调试方法
    文章翻译——使用 GNU 的 GDB调试器,内存布局和栈——01
    第十章扩展——__cdecl 的压栈方式——printf
  • 原文地址:https://www.cnblogs.com/xingdongpai/p/5049723.html
Copyright © 2020-2023  润新知