使用Struts2的OGNL表达式和ValueStack深入理解
OGNL简介
OGNL,即Object-Graph Navigation Language,对象视图导航语言,是一种数据访问语言,比EL表达式更加强大:
- EL只能从11个内置对象中取值,且只能获取属性,不能调用对象的方法。
- OGNL可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图。
OGNL是可以单独使用的。OGNL并不属于Struts2,只不过Struts2觉得OGNL不错,把OGNL给整合进来了。
Struts2的8个核心jar包中已经包含了OGNL的jar包,不需要我们再导包。
ValueStack(值栈) 简介
ValueStack是Struts2的一个接口,用于存储Struts中的数据。ValueStack接口有一个实现类OgnlValueStack。
创建一个Action实例时,会自动为这个Action实例创建一个ActionContext实例,创建ActionContext实例时,又会自动创建一个ValueStack(OgnlValueStack)的实例,将OgnlValueStack的引用放到ActionContext中,将ActionContext的引用放到Action中。
这三者的生命周期是一致的。
ActionContext存储数据,实际是用ActionContext中的ValueStack来存储数据。
ValueStack的内部结构
ValueStack由2部分组成:root、context。
root部分是一个CompoundRoot对象,extends ArrayList,内部维护了一个列表。
context部分是一个OgnlContext对象,implements Map,内部维护了一个Map,用来存放常用对象的引用,其中也包括root部分的引用,所以OgnlContext其实是可以访问到所有的数据的。
Ognl表达式使用的数据就是ValueStack中的数据。
ActionContext能获取到域对象(Map)、获取到原生的Servlet对象(request、response、session),就是因为ActionContext内部有ValueStack的引用,可以访问ValueStack中的数据(对象)。
可以在jsp中用<s:debug>标签显示ValueStack的调试信息。
平时说的操作值栈,是指操作root部分,因为context中这些常用对象的引用由struts自动管理,无需我们操作。
ValueStack,顾名思义,是一个栈,ValueStack的栈指的是root部分,栈是通过ArrayList实现的。
获取ValueStack对象的2种方式
ValueStack valueStack = ActionContext.getContext().getValueStack(); Object attribute = ServletActionContext.getRequest().getAttribute("struts.valueStack"); Object attribute1 = ServletActionContext.getRequest().getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
获取ValueStack对象有2种方式:通过ActionContext对象、通过request对象。
AtionContext中有ValueStack的引用,能获取到;
Struts2对request进行了包装、增强(拦截器|过滤器),request中也有ValueStack的引用,也能获取到。
但request是以属性获取,返回值是Object,需要强转,且字符串常量不好记,通过ActionContext获取更方便。
字符串常量 ServletActionContext.STRUTS_VALUESTACK_KEY 即 "struts.valueStack"。
一个Action实例中,只有一个对应的ActionContext对象,只有一个对应的ValueStack对象。
ValueStack的数据存取
1、将数据设置为Action的成员变量,并提供getter方法
public class LoginAction extends ActionSupport{ private String name; private User user; public String getName() { return name; } public User getUser() { return user; } @Override public String execute() throws Exception { name = "chy"; user = new User("张三", 19); return "index"; } }
root部分是栈,Struts默认会把当前Action的实例压入栈中,所以能从ValueStack中取到此Action实例的属性。
取Action的属性,底层是调用此Action的getter方法,所以需要提供对应的getter方法。
在jsp中取值:
<s:property value="name" /> <s:property value="user.name" />
<s:property>是struts2的标签,value中写ognl表达式。
user.name底层是调用user对象的getter方法,所以User类需要提供对应的getter方法。
Struts2对request对象进行了增强,request中也有ValueStack的引用,所以request中也能获取到ValueStack中的数据。
${requestScope.name} ${requestScope.user.name} <%=request.getAttribute("name") %> <%=(User)request.getAttribute("user") %> <% String name = (String) request.getAttribute("name"); out.print(name); User user = (User)request.getAttribute("user"); out.print(user.getName()); %>
EL表达式可以不指定requestScope:
${name}
${user.name}
默认先在requestScope中找,找不到就到ValueStack中找。
此种方式是将数据存到ValueStack的root部分。
如果存储的数据个数较多,Action中会有大量的成员变量、getter方法,很冗杂,一般不用这种。
2、直接操作ValueStack
public class LoginAction extends ActionSupport{ @Override public String execute() throws Exception { ValueStack valueStack = ActionContext.getContext().getValueStack(); String name = "chy"; User user = new User("张三", 18); valueStack.set("user",user); valueStack.set("name",name); return "index"; } }
JSP中取值:
<s:property value="name" /> <s:property value="user.name" />
此种方式是将数据都放到一个Map中,将此Map压入栈顶,在JSP直接通过Map的key来获取对应的value。
或者使用push():
public class LoginAction extends ActionSupport{ @Override public String execute() throws Exception { ValueStack valueStack = ActionContext.getContext().getValueStack(); User user = new User("张三", 18); valueStack.push(user); return "index"; } }
<s:property value="name" />
直接将push的对象压入栈顶,JSP中通过属性名来获取值。
底层是调用对象的getter方法来获取属性值,所以对象需要提供getter方法。
一次push就向栈顶压入一个对象,取对象属性的时候不能带对象名,是从栈顶开始向栈底寻找这个属性,找到就返回。
valueStack.push(user1);
valueStack.push(user2);
比如push2次, <s:property value="name" /> ,取到的是靠近栈顶的user2的name属性。
int、String、Array、List、Map等对象push到栈中,能获取到length、size这些属性值,但获取不到这些对象本身,也获取不到数组、集合中的元素,因为这些数组、集合中的元素不是属性,没有属性名。
set()是将数据都放到一个Map中,将此Map压入栈顶,数据都在栈顶。
set()根据Map中的key来取元素,能获取到对象本身,如果对象是数组、集合,也能获取到数组、集合中的元素。
public class LoginAction extends ActionSupport{ @Override public String execute() throws Exception { ValueStack valueStack = ActionContext.getContext().getValueStack(); ArrayList<String> list = new ArrayList<>(); list.add("刘"); list.add("关"); list.add("张"); valueStack.set("list",list); return "index"; } }
<s:property value="list" /> <s:property value="list[0]" />
可通过key获取对象本身,也可以通过下标获取Array、List的某个元素,可通过key获取Map对象中某个键值对的value。
set()比push()更优,推荐使用。
既然是栈,操作的自然是root部分,存取的是root部分的数据。
3、存取context中的数据
public class LoginAction extends ActionSupport{ @Override public String execute() throws Exception { HttpServletRequest request = ServletActionContext.getRequest(); request.setAttribute("user",new User("chy",18)); request.setAttribute("group","vip"); return "index"; } }
<s:property value="#request.group" /> <s:property value="#request.user" /> <s:property value="#request.user.name" />
需要加#,#表示是从context(几个域对象)中取数据。
获取原生的Servlet对象,setAttribute()存入数据,在JSP从对应的对象中取出来即可。
请求参数封装在request对象的parameters对象中,不是直接封装在request对象中,request中直接封装的是setAttribute()存入的数据。
把request作为一个大Map,里面还有一个小Map,小Map用于封装请求参数。 Map<String, String[]> parameterMap = request对象.getParameterMap(); 。
比如表单字段: 用户名:<input type="text" name="user" />
Object obj=request对象.getAttribute("user"); 获取的是setAttribute()存入request中的数据。
String user = request对象.getParameter("user"); 获取的才是请求参数(从小Map中查找数据)。
在JSP中获取请求参数:
<s:property value="#parameters.user" /> <s:property value="#request.parameters.user" /> <%=request.getParameter("user")%>
<s:property value="#request.user" /> 获取的不是请求参数,而是setAttribute()存入request中的数据。
OGNL表达式
OGNL是一种数据访问语言,可直接使用(写在Java代码中,即可获取值,也能设置值),也可配合Struts2标签使用,写在Struts2标签中叫做OGNL表达式,用于获取值。
访问ValueSatck中的数据:
<s:property value="name" /> <s:property value="user.name" /> <s:property value="#parameters.user" />
不带#的是从root中取值,带#的是从context中取值。
特殊字符#:从context中取值,构建Map。
遍历List:
<s:iterator var="item" value="{'刘备','关羽','张飞'}"> <s:property value="item" /> </s:iterator>
var指定临时变量,表示一项,value指定Array、List。<s:iterator>的元素体即循环体。
不能 <s:property value="hello+item" /> 或者 <s:property value="hello"+"item" /> 这样来指定格式,这样解析不了临时变量。
可以配合其他标签来指定格式,比如:
hello <s:property value="item" />!
<div class=""> <p>
hello <s:property value="item" />
</p> <img src="" /> </div>
构建Map:
<s:iterator var="entry" value="#{'name':'刘备','age':40,'group':'蜀'}"> <s:property value="entry" />
<s:property value="entry.key" />
<s:property value="entry.value" /> </s:iterator> <s:iterator value="#{'name':'刘备','age':40,'group':'蜀'}"> <s:property value="key" />:<s:property value="value" /> </s:iterator>
#表示这玩意儿是Map。Map默认var为key、value,所以可缺省 var="key,value" 。
如果指定了var="entry",可通过entry.key,entry.value来引用属性。
IDEA下可能会报红,这是IDEA的问题,代码本身没有错误。
单选按钮:
<s:radio list="{'男','女'}" name="gender" label="性别" /> <s:radio list="#{'male':'男','female':'女'}" name="gender" label="性别" />
使用List时,<input type="radio" />的value、选项文字都是list中的元素。
使用Map时,Map的key作为<input type="radio" />的value,Map的value作为选项文字。
构建Map时,key必须引,value是字符、字符串才引。
特殊符号%:强制解析、不解析OGNL表达式
有的Struts标签默认会解析OGNL表达式,有的Struts标签默认不会解析OGNL表达式。
比如<s:textfield />默认不会解析OGNL表达式,会将#session.user作为字符串原样显示。
<% session.setAttribute("user","chy"); %> <s:textfield name="user" value="#session.user" label="用户名" />
将OGNL表达式放在%{ }中,会作为OGNL表达式处理,强制解析。
<s:textfield name="user" value="%{#session.user}" label="用户名" />
有的Struts标签默认会解析OGNL表达式,如果不想解析OGNL表达式:
%{'#session.user'}
放在%{' '}中即可,会作为字符串处理。
特殊符号$:在配置文件中使用OGNL表达式取值
将OGNL表达式放在${ }中即可。
.properties配置文件:
user=${#session.user.name}
xml配置文件:
<param>${#session.user.name}</param>
使用OGNL表达式访问成员属性、调用成员方法
<%
User user=new User("曹操",40);
session.setAttribute("user",user);
%>
<s:property value="#session.user.name" />
<s:property value="#session.user.getName()" />
<s:property value="#session.user.setName('chy')" />
<s:property value="'hello'.length()" />
访问成员变量,底层其实是调用对应的getter方法。
如果方法有返回值,会用返回值替换原来的OGNL表达式。
使用OGNL表达式访问静态成员(static)
<s:property value="@java.lang.Math@PI" /> <s:property value="@java.lang.Math@abs(-10)" />
以 @全限定类名@静态变量|静态方法 的方式调用,必须是全限定类名,可以是java自带的类,也可以是自定义的类。
访问静态变量不用进行常量配置,但调用静态方法必须在struts.xml中进行常量配置:
<struts> <constant name="struts.ognl.allowStaticMethodAccess" value="true"></constant> <package name="action" namespace="/" extends="struts-default"> </package> </struts>
允许静态方法调用,输入ognl就出来了,将value设置true(默认为false)。
和EL表达式一样,OGNL表达式没有空指针异常、没有数组越界,如果没有指定的变量、方法,不会出现异常。但OGNL比EL更强大。
OGNL可以单独使用,但OGNL表达式常常需要配合Struts标签使用,在html标签中单独使用OGNL表达式无效,比如<p>#request.name</p>会原样显示OGNL表达式,不会解析OGNL表达式,加上%也不会强制解析:<p>%{#request.name}</p>。
上一篇: 在Struts2中如何运用OGNL表达式
下一篇: OGNL表达式
推荐阅读
-
深入理解 GMP:使用GNU多精度库进行高精度计算-4. GMP的优势和应用
-
Java 类加载器的作用 - 简介:类加载器是 Java™ 中一个非常重要的概念。类加载器负责将 Java 类的字节码加载到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模型、加载类的具体过程和线程上下文类加载器等。然后介绍了如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™ 中的应用。 类加载器是 Java 语言的一项创新,也是 Java 语言广受欢迎的重要原因之一。它允许将 Java 类动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 开始出现,最初是为了满足 Java Applets 的需求而开发的,Java Applets 需要从远程位置下载 Java 类文件并在浏览器中执行。现在,类加载器已广泛应用于网络容器和 OSGi。一般来说,Java 应用程序的开发人员不需要直接与类加载器交互;Java 虚拟机的默认行为足以应对大多数情况。但是,如果遇到需要与类加载器交互的情况,而您又不太了解类加载器的机制,就很容易花费大量时间调试异常,如 ClassNotFoundException 和 NoClassDefFoundError。本文将详细介绍 Java 的类加载器,帮助读者深入理解 Java 语言中的这一重要概念。下面先介绍一些基本概念。 类加载器的基本概念 顾名思义,类加载器用于将 Java 类加载到 Java 虚拟机中。一般来说,Java 虚拟机以如下方式使用 Java 类:Java 源程序(.java 文件)经 Java 编译器编译后转换为 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码并将其转换为 java.lang 实例。每个实例都用来表示一个 Java 类。通过该实例的 newInstance 方法创建该类的对象。实际情况可能更加复杂,例如,Java 字节代码可能是由工具动态生成或通过网络下载的。 基本上,所有类加载器都是 java.lang.ClassLoader 类的实例。下面将详细介绍这个 Java 类。 java.lang.ClassLoader 类简介 java.lang.ClassLoader 类的基本职责是根据给定类的名称为其查找或生成相应的字节码,然后根据这些字节码定义一个 Java 类,即 java.lang.Class 类的实例。除此之外,ClassLoader 还负责加载 Java 应用程序所需的资源,如图像文件和配置文件。不过,本文只讨论它加载类的功能。为了履行加载类的职责,ClassLoader 提供了许多方法,其中比较重要的方法如表 1 所示。下文将详细介绍这些方法。 表 1.与加载类相关的 ClassLoader 方法
-
深入理解和使用 C 语言中的 Break 语句
-
使用Struts2的OGNL表达式和ValueStack深入理解
-
比较和理解OGNL表达式与EL表达式的异同
-
深入理解并剖析OGNL表达式的详尽指南
-
ognl和el表达式有什么区别-struts2 调用OGNL表达式使用方法(EL废弃)
-
深入理解OGNL表达式:基础语法和使用方法详析
-
理解并运用 OGNL 表达式:深入解析OGNL 的表达式功能
-
深入理解并剖析OGNL表达式的详尽指南