Struts2 OGNL表达式、ValueStack

时间:2023-01-01 22:23:08

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的内部结构

Struts2  OGNL表达式、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>。