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表达式 ($)

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

 

posted @ 2015-12-17 16:08  a617475430  阅读(1103)  评论(0编辑  收藏  举报