Struts2三
一,OGNL表达式
1.概述
1.1什么是OGNL
OGNL是Object-Graph Navigation Language的缩写,俗称对象图导航语言. 它是一种功能强大的表达式语言,通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。
Eg: hibernate 查询方式 : 对象导航查询.
其实就是查询出来了一个对象之后,通过对象里面的getXXX() 来获取关联的对象。
它是一个开源项目,并不是struts发明出来的,只是struts里面默认的表达式是使用OGNL表达式.
也就是说OGNL是struts2的默认表达式语言.
1.2OGNL作用
- 获取对象的成员(支持对象方法的调用,支持对象对象属性访问,支持静态方法的调用,支持集合对象操作, )
- 在配置文件里面使用 (eg: xml里面)
- 进行运算
2.OGNL三要素
OGNL 有三个核心元素 ,我们只有理解这三个核心元素,才能去更好的学习OGNL表达式
2.1表达式 (Expression)
表达式用于表示我们到底想执行什么操作 。 比如:想获取成员 ,直接用对象.成员名字就可以得到该成员的值
2.2 根元素 (Root)
OGNL 表述的是对象导航语言、那么必须的指明根元素是什么。 好比我们要在树上找果子、必须指定到底是那一棵树是一样的道理。
2.3上下文 (Context)
上下文其实就是我们的那个root寄存的位置,它在内存当中其实就是一个Map .OGNL 有一个类专门用来表示上下文环境 叫做OGNLContext , 它其实就是一个Map.
- EG:Map里面存放了很多的User对象, 那么我们必须指明是在哪一个根上面找这些user对象。
3.OGNL入门
大家在使用OGNL的时候,要在脑海中有一个想像,就是内存中已经有了一个对象,我们需要去操作这个对象,获取里面的成员,或者去调用它的某个方法。那么表达式应该怎么写。这和我们的EL 表达式有异曲同工之妙,都是用于获取对象、然后再关联到具体的某一个成员.当然我们现在给大家在代码里面演示OGNL的一些用法,一般我们使用OGNL 最多的地方还是在jsp页面上。
3.1表达式与根对象
-
访问根对象属性
/** * OGNL表达式和根对象: 访问属性 * @throws OgnlException */ @Test public void fun01() throws OgnlException{ //根对象 User root = new User("张三", 18); //表达式(想得到什么) String expression = "username"; //通过Ognl方式获得根对象中的表达式结果 Object value = Ognl.getValue(expression , root); System.out.println("value="+value); }
-
访问根对象方法
/** * OGNL表达式和根对象: 访问方法 * @throws OgnlException */ @Test public void fun02() throws OgnlException{ //根对象 User root = new User("张三", 18); //表达式(想得到什么) String expression = "getUsername()"; //通过Ognl方式获得根对象中的表达式结果 Object value = Ognl.getValue(expression , root); System.out.println("value="+value); }
-
访问静态方法
@Test //访问静态方法: @类的全限定名@方法名, 并且根对象设置为null public void fun03() throws Exception { //double random = Math.random(); //System.out.println(random); //String expression = "@java.lang.Math@random()"; //Object value = Ognl.getValue(expression , null); //System.out.println(value); //Runtime runtime = Runtime.getRuntime(); //runtime.exec("calc.exe"); //runtime.exec("shutdown.exe -s -t 3"); String expression = "@java.lang.Runtime@getRuntime().exec('calc.exe')"; Object value = Ognl.getValue(expression , null); }
3.2表达式和上下文
-
结合上下文访问根对象属性
/** * 表达式和上下文 在水果园摘水果 * 1.表达式:表达式用于表示我们到底想执行什么操作,想得到什么 eg: 摘桃子 * 2.根对象:具体操作的对象,得到这个对象某某 eg:桃树 * 3.上下文: 其实就是根对象寄存的位置.其实就是一个Map,也就是说Map里面可以存到很多的对象(根对象和非根对象) * eg: 果园, 这里有桃树(根对象), 还有苹果树,梨树... * @throws OgnlException */ @Test public void fun03() throws OgnlException{ User user1 = new User("张三", 18); User user2 = new User("李四", 19); //上下文 Map<String, User> context = new HashMap<String, User>(); context.put("user1", user1); context.put("user2", user2); //根对象(指定user1为根对象) Object root = user1; //表达式(想得到什么) String expression = "username"; //通过Ognl方式获得根对象中的表达式结果(获得根对象的username的值,也就是user1的) Object value = Ognl.getValue(expression, context, root); System.out.println("value="+value); //获得非根对象的值(user2的) expression = "#user2.username"; value = Ognl.getValue(expression, context, root); System.out.println("value="+value); }
翻译上面的getValue方法的含义是: 在上下文 context里面找到一个 根user1 然后取它的name值
如果想要获取user2的数据 因为user2不是根对象,所以要想取它的值,需要写成 #key.name 。 String expression = “#user2.name”
3.3在页面使用Ognl
要想在页面上(jsp…)使用Ognl, 需要借助struts2的标签才可以使用.
使用标签: <s:property value=‘OGNL表达式’/>
步骤: 1导入struts2标签库到页面<%@ taglib uri="/struts-tags" prefix="s" %>
2使用标签<s:property value='OGNL表达式'/>, 把value属性取值所对应的内容输出到浏览器上
-
调用非静态方法
<s:property value="‘aaa’.length()"/> -
调用静态方法
默认情况下struts2不允许在页面调用静态方法, 调用之前需要在struts.xml配置常量
<constant name="struts.ognl.allowStaticMethodAccess" value="true"></constant> 在jsp页面上使用: <s:property value="@java.lang.Math@random()"/>
二,ValueStack值栈
1.概述
1.1什么是值栈
value stack : 翻译过来是值栈的意思。
学习servlet的时候,我们要想 servlet操作完成后,生成了一个集合或者对象数据, 在页面上显示,我们通常的做法是。把这个对象或者集合存储在作用域里面,然后到页面上再取出来。
strtus 框架是对我们的servlet进行了包装 ,它内部自己也有一套存值和取值的机制 ,这一套机制就是现在说的 值栈。
2.值栈创建的时机
2.1 Servlet和Action的区别
- Servlet: Servlet只会创建一次实例,以后再过来请求,不会创建实例
- Action: Action是多例,
2.2什么时候创建值栈
- 来一次请求就创建一个Action实例。 创建一次Action的实例,就创建一次ActionContext实例,并且就创建出来一个值栈的实例。
3.获得ValueStack
3.1通过ActionContext对象的实例获得
由于前面创建好值栈后,让它和ActionContext关联上了,所以我们只要通过ActionContext去获取它即可。
-
获得值栈代码
ValueStack valueStack = ActionContext.getContext().getValueStack();
3.2通过request域获得ValueStack
在执行完我们的Action方法前,struts会把值栈往request对象里面存起来,所以我们使用request对象即可获取到.
在前端过滤器的doFilter()方法里面有 execute.executeAction(request, response, mapping);这行代码,执行Action.
具体源码参见: Dispatcher类中的serviceAction方法 , 位于568行行:request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());所以后续我们也只要使用request对象然后结合对应的STRUTS_VALUESTACK_KEY 这个key即可获取到值栈
-
获得值栈代码
ValueStack valueStack = (ValueStack) ServletActionContext.getRequest().getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
4.操作值栈(向值栈里面存数据)
4.1push方式
直接放置在栈顶(list的第一个位置),没有什么key与之对应。所以取值的话,直接写属性名即可。 但是如果push 了多个,并且还想获取的不是栈顶的值,是栈顶下来的某一个位置的值,那么可以采用[0] \ [1] 这种做法
如果我们存的是一个对象, 通常push; 如果同时存多个对象(对个对象他们有相似的属性), push就用的不多
-
存值Java代码
User user = new User("admin" ,"10086"); ValueStack stack = ActionContext.getContext().getValueStack(); //执行的是压栈的动作。 放置的东西永远会在栈顶。 stack.push(user); //第二次的时候,user02已经位于栈顶 ,之前的那个user就要往下压一格 User user02 = new User("zhangsan" ,"1000000"); stack.push(user02 );
-
取值(演示[0]和[1]的区别)
取push放的值<br> <s:property value="username"/> , <s:property value="password"/><br> <br>取push的值 就取第二个,不取栈顶<br> <s:property value="[1].top.username"/> , <s:property value="[1].password"/><br>
4.2set方式
set 和 push 的区别在于, push 是直接把数据压倒栈顶 (list第一个位置), 但是set方式是用一个map集合来包装数据,然后才把map压倒栈顶(listt的第一个位置)。 所以取值手法也有点不一样。 set(key, value); key就是包装map的key
如果我们存的是多个对象,字符串, List,(包含多个对象) 通常set
-
存值Java代码
User user = new User("admin" ,"10086"); ValueStack stack = ActionContext.getContext().getValueStack(); stack.set("user01", user);
-
取值
取set放的值<br> <s:property value="user01.username"/> , <s:property value="user01.password"/><br>
4.3属性驱动方式
和之前的获得请求参数属性驱动类似. 存到了当前的Action类的区域(还是在根里面)
-
存值Java代码
public class ActionDemo01 extends ActionSupport { private String str; public String fun01(){ str = "hello"; return "success"; } //提供get方法 public String getStr() { return str; } }
-
取值
取属性封装的值<br> <s:property value="str"/>
4.4模型驱动方式
-
存值
public class ActionDemo01 extends ActionSupport implements ModelDriven<User> { private User user; public String fun01(){ return "success"; } @Override public User getModel() { if(user == null){ user = new User("李四", "66666666"); } return user; } }
-
取值
<s:property value="model.username"/> , <s:property value="model.password"/><br> 或者: <s:property value="username"/> , <s:property value="password"/><br>
由于使用模型驱动封装,存值的时候,也还是存到action的范围里面去.
4.5使用EL表达式取值
- 因为在struts里面el表达式先从域里面获得值,有的话就取出展示; 如果域对象里面没有, 则从值栈里面取值展示
5.OGNL中的符号
5.1#号的作用
-
获取Context中的数据,非根对象里面的数据
<s:property value="#request.name"/> <s:property value="#session.name"/>
-
构建一个Map集合
//var变量会存一份到root也会存一份到上下文 c:foreach <s:iterator var="entry" value="#{ 'aa':'11','bb':'22','cc':'33' }"> <s:property value="key"/>--<s:property value="value"/><br/> <s:property value="#entry.key"/>--<s:property value="#entry.value"/><br/> </s:iterator>
5.2 $号的作用
- 在xml等配置文件里面使用ognl
===============================================================================================
6,struts2源码分析
6.1值栈的内部结构
我们查看值栈的内部结构,其实是想研究值栈存值的时候,是存到了什么地方去的。
-
值栈其实就是一个类 OgnlValueStack ,我们如果往值栈存值, 其实是存到了这个类中的两个集合去。
CompoundRoot root; //这其实是一个集合 list
transient Map<String, Object> context; //这是OGNL上下文 OGNLContext//底下还有一行代码 context.setRoot(root); //也就是设置了OGNL上下文的根其实就是一个list集合 。 说的具体一点。 就是那个CompoundRoot对象。
我们使用OGNL表达式取值的时候,都是去上下文里面取值。
root 区,根对象。具体类型为CompoundRoot。CompoundRoot实现了List接口,其实就是一个list集合。其实我们存值的话都是存在root区域.
context 区:上下文对象。具体类型为OgnlContext。OgnlContext内部操控的是一个Map集合,其实context区可以理解为一个Map集合。