值桟&OGNL
OGNL概述
- 之前web阶段,学习过EL表达式,EL 表达式在JSP中获取域对象里面的值,不能在HTML页面使用
- OGNL 是一种更强大的表达式
- 主要用途 在Struts 中,操作值桟数据
- 一般把OGNL在Struts2 操作,和Struts2标签一起使用
- OGNL不是Struts2的一部分,它是单独的一部分.
什么是OGNL
全称是 对象图导航语言 (Object-Graph Navigation Language),它是一种强大的开源表达式语言,使用这种表达式语言,可以通过某种语法,存取java对象的任意属性,调用属性的时候,可以自动实现类型的转换.
OGNL作用:
ognl 的三要素
- 表达式
表达式是核心,OGNL会根据表达式去对象中取值,所有的OGNL操作都是针对表达式解析后进行的
- 根对象 root
OGNL的操作对象。可以以任意一个对象为根,通过OGNL可以访问与这个对象有关联的其他对象
- Context对象
实际上,OGNL的取值还需要一个上下文环境,设置了root对象,OGNL可以对root对象进行取值写值操作,上下文规定了OGNL的操作在哪里进行。上下文Context是一个Map类型的对象,表达式中,加上“#”加上对象名称
使用Struts2的标签
使用jstl的时候,导入jar包以后,在JSP页面中,引入标签库
使用Struts标签的时候,也要在JSP中 引入标签库
<%@ taglib uri="/struts-tags" prefix="s" %>
<s:property value=""/> //value写的是表达式
入门案例
<!--字符串的长度-->
<s:property value="'我的长度'.length()"/>
值桟
-
之前在web阶段,在servlet 里面进行操作,把数据放到域对象里面,在页面中使用el表达式取值,域对象在一定范围内,存值和取值
-
在Struts中,提供了一种存储机制,类似于域对象,是值桟,可以存值和取值
- 在action中,可以把数据放到值栈里面,在页面中获得值栈数据
-
servlet和action 的区别
- servlet: 默认在第一次访问的时候创建,只创建一次(单实例对象)
- Action: 访问的时候创建,每次访问的时候,都会创建action对象,创建多次(多实例对象)
- 通过构造函数 输出内容,看看是否创建多次
什么是 值栈:
值栈 ValueStack 是Struts的一个接口,OgnlValueStack是它的实现类,客户端发起一个请求的时候,创建一个action,实例的同时,创建一个值栈实例,这个实例贯穿整个action的生命周期,Struts用OGNL将请求action的参数封装为对象,储存到值栈中,通过表达式,取其中的值
位置在action 中,有且只有一个
获取值栈对象:
- ActionContext类里面的方法得到值栈对象
ActionContext context = ActionContext.getContext();
ValueStack stack = context.getValueStack();
- 每个action中只有一个值栈对象,get 两个的话,两个地址也是相等的
值栈的内部结构
- 值栈分为两个部分
- 第一部分:root 结构为list结构 一般都是执行root结构
- 第二部分:context 结构为Map集合
context 结构: 存储对象的引用
key 固定 | value |
---|---|
request | request对象的引用 |
Session | HttpSession对象的引用 |
application | ServletContext对象的引用 |
parameters | 传递的相关参数 |
attr | 获取三个域中相同key的最小域的值 |
向值栈中放标签
<s:debug>
访问action,执行action方法的返回值,配置返回值到JSP页面中,在JSP页面中 使用这个标签,开发时候应用
在页面中,会生成[debug]
的链接。点进去后,会看到两部分内容
代码实现
通过JSP页面,发送action请求,访问OgnlAction的execute方法,然后跳转到ognl页面
通过页面的<s:debug></s:debug> 查看debug 信息(值栈对象)
<!-- jsp 代码-->
<s:debug></s:debug>
<!-- struts.xml 代码内容-->
<package name="ognl" extends="struts-default" namespace="/">
<action name="debuga" class="cn.lenks.action.OgnlAction">
<result>/jsp/ognl.jsp</result>
</action>
</package>
I.Value Stack Contents — 我们一般用到的是这里面的内容(root部分,list集合)
Object 的名字为包名+action类名 ::空action的object(本次debug栈顶元素)具体内容如下:
Property Name Property Value container There is no read method for container errorMessages [] actionErrors [] actionMessages [] fieldErrors {} texts null locale zh_CN errors {} action对象里面有值栈对象
值栈对象里面有action对象的引用
II.Stack Context (context map集合)
向值栈中放数据的方式
1、获取值栈对象,调用对象的set方法
2、获取值栈对象,调用对象里面的push方法
3、在action里面定义变量,生成变量的get方法----(最常用的方法)
代码实现:
// 第三种方法,定义变量。生成get方法。在执行的方法里面为变量赋值。
private String name ;
public String getName() {
return name;
}
@Override
public String execute() throws Exception {
// 获取值栈对象里面的set方法
// 获取值栈对象
ActionContext context = ActionContext.getContext();
ValueStack stack = context.getValueStack();
// 页面显示 Object----- Property Name----- Property Value
// 方法一:调用Set方法
// 此方法是生成一个map对象,key为java.util.HashMap
// 两次set只有一个map生成,对应的是: empty---null ???原因待定
stack.set("username", "lenks");
stack.set("password", "124");
// 方法二:调用push方法
// 此方法,生成两个String对象,对应的内容为:bytes---[B@44324010
// empty---false
stack.push("kenksss");
stack.push("kenk124");
// 方法三:没有生成新的对象,只是在action对象里面多了内容: name---123
name = "123";
return SUCCESS;
}
向值栈中放入对象:
放入对象实现步骤:
- 定义或者声明对象变量
- 生成变量的get方法
- 在执行的方法里面对对象设定值
- 代码实现 同上。省略
- 效果:
同上面的数据一样
在action对象中,生成内容: 对象名字—对象.tostring()
放入list集合方法
- 定义或者声明list变量
- 生成变量的get方法
- 在执行的方法里面向list集合设置值
- 效果同上.生成内容:list集合名—[集合内对象,集合内对象,…]
从值栈中获取数据
a.获取字符串
-
在action中,里面,设置该变量的get方法。
-
JSP标签:
<s:property value="字符串名称">
就是前面说的Property Value
b.获取对象
- 在action里面,设置对象的get方法
- JSP标签
<s:property value="对象名.属性名">
c.获取list集合
I.第一种方法
- 在action里面,设置集合的get方法
- JSP标签
<s:property value="集合名[0].属性名">
- 上述方法针对明确list集合的数量的时候使用
II.第二种方法
- 在action里面,设置集合的get方法
- JSP标签
<s:iterator value="list集合名字">
<!-- 遍历list集合的对象-->
<!--不同于foreach标签 c:foreach item="users" var="user" ... ${user.username}-->
<s:property value="属性名"/>
<s:property value="属性名"/>
</s:iterator>
这里注意:JSP中的注释里面如果有标签的话,那个标签不会被注释,容易报错。用<%----%>
III.第三种方法
- 在action里面,设置集合的get方法
- JSP标签
<s:iterator value="list集合名字" var="user">
<!--
遍历list,得到每个User对象后,
机制:遍历得到的一个user 存到context中,key值为var的User名,value为引用
作用:充分利用空间,提高速度和效率
获取context的数据特点:写ognl表达式
使用特殊符号# 表示是从context中读取
-->
<s:property value="#user.username"/>
<s:property value="#user.username"/>
</s:iterator>
set和push方法放进去的值得取值方法
<s:property value="key名称">
因为push方法放数据,因为没有key-value 机制。只是放进去了值
只能通过这种方法取值.top语法。这里的[0,1,2,3,4] 代表的是从栈顶开始的第几层元素
<s:property value="[0].top"/>
<s:property value="[1].top"/>
<s:property value="[2].top."/>
<s:property value="[3].top"/>
<s:property value="[4].top"/>
很少用到
具体详解请参考Struts2相关书籍
使用el表达式和foreach标签获取list数据
先导入两个jar包 jstl.jar standard.jar
引入标签 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" perfix="c" %>
<c:foreach items="${list对象名,这里是users}" var="user">
${user.username}
</c:foreach>
重点:为什么可以获取到
- el 表达式,获取域对象
- 向域对象放值,使用setAttribute()方法
- 底层增强request对象里面的getAttribute()方法
- 首先从request域中获取值,如果获取到,直接返回
- 如果获取不到值,把值栈中的数据获取出来,放到域对象中
- 不建议这么做。性能比较低
查看源代码
进入org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
在StrutsPrepareAndExecuteFilter中找到doFilter(ServletRequest req, ServletResponse res, FilterChain chain)方法
进入request = prepare.wrapRequest(request);
再进入
request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup);
public class StrutsRequestWrapper extends HttpServletRequestWrapper
通过继承HttpServletRequestWrapper增强request方法
public Object getAttribute(String key) {
if (key == null) {
throw new NullPointerException("You must specify a key value");
}
if (disableRequestAttributeValueStackLookup || key.startsWith("javax.servlet")) {
// don't bother with the standard javax.servlet attributes, we can short-circuit this
// see WW-953 and the forums post linked in that issue for more info
return super.getAttribute(key);
}
ActionContext ctx = ActionContext.getContext();
Object attribute = super.getAttribute(key);
if (ctx != null && attribute == null) {
boolean alreadyIn = isTrue((Boolean) ctx.get(REQUEST_WRAPPER_GET_ATTRIBUTE));
// note: we don't let # come through or else a request for
// #attr.foo or #request.foo could cause an endless loop
if (!alreadyIn && !key.contains("#")) {
try {
// If not found, then try the ValueStack
ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.TRUE);
ValueStack stack = ctx.getValueStack();
if (stack != null) {
attribute = stack.findValue(key);
}
} finally {
ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.FALSE);
}
}
}
return attribute;
}
# 和% 使用
#的使用
- #可以获取context中的值
因为context中,有request,Session 等key名称,可以通过他们的引用,获取他们的对象
<s:property value="#request.放入域中的名称"/>
%的使用
场景
在Struts2标签中使用ognl表达式,如果直接在表达式中使用标签,不会识别,只有加了%才会识别
表单项使用时,想在输入窗口显示username的值得时候
<s:textfield name="username" value="%{#request.username}">
如果不加%{},会显示那个字符串
更新时间 2018.11.02