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表达式 ($)
示例:文件上传中,动态设置下载文件的名字