JavaWeb 之 OGNL 表达式、Struts2 值栈与OGNL 特殊符号

OGNL 表达式 & Struts2 值栈 & OGNL 特殊符号


OGNL表达式


OGNL表达式概述(了解)

  • OGNL 是 Object Graphic Navigation Language(对象图导航语言)的缩写

    所谓对象图,即以任意一个对象为根,通过 OGNL 可以访问与这个对象关联的其它对象

    通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。

  • Struts2 框架使用 OGNL 作为默认的表达式语言

    OGNL 是一种比 EL 强大很多倍的语言
    xwork 提供 OGNL 表达式
    ognl-3.0.5.jar

  • OGNL 提供五大类功能

    支持对象方法调用
    支持类静态的方法调用和值访问
    访问 OGNL 上下文(OGNLcontext)和 ActionContext
    支持赋值操作和表达式串联
    操作集合对象

  • 测试的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 访问对象的方法
@Test
public void run1() throws OgnlException{
OgnlContext context = new OgnlContext();
// 获取对象的方法
Object obj = Ognl.getValue("'helloworld'.length()", context, context.getRoot());
System.out.println(obj);
}

// 获取OGNL上下文件的对象
@Test
public void run3() throws OgnlException{
OgnlContext context = new OgnlContext();
context.put("name", "美美");
// 获取对象的方法
Object obj = Ognl.getValue("#name", context, context.getRoot());
System.out.println(obj);
}

// 从root栈获取值
@Test
public void demo3() throws OgnlException{
OgnlContext context = new OgnlContext();
Customer c = new Customer();
c.setCust_name("haha");
context.setRoot(c);
String name = (String) Ognl.getValue("cust_name", context, context.getRoot());
System.out.println(name);
}

自己测试的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.renkaigis;

import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import org.junit.Test;

/**
* 演示 OGNL 表达式
*/
public class demo1 {
/**
* 测试
*/
@Test
public void run1() throws OgnlException {
// 上下文对象
OgnlContext context = new OgnlContext();
// 获取到根对象
Object root = context.getRoot();
// 存储数据
context.put("name", "美美");
// 获取值,注意表达式写法
Object value = Ognl.getValue("#name", context, root);
// 调用方法
//Object value = Ognl.getValue("'haha'.length()", context, root);
System.out.println(value);
}
}

在 Struts2 框架中使用 OGNL 表达式

1). Struts2 引入了 OGNL 表达式,主要是在 JSP 页面中获取值栈中的值

2). 具体在 Struts2 中怎么使用呢?如下步骤

需要先在 JSP 页面中引入 Struts2 的标签库

1
<%@ taglib prefix="s" uri="/struts-tags" %>

使用 Struts2 提供的标签中的标签

1
<s:property value="OGNL表达式"/>

3). 在 JSP 页面使用 OGNL 表达式

访问对象方法

1
<s:property value="'hello'.length()"/>

Struts2 框架的值栈


值栈的概述

  • 值栈就相当于 Struts2 框架的数据的中转站,向值栈存入一些数据。从值栈中获取到数据。

  • ValueStack 是 struts2 提供一个接口,实现类 OgnlValueStack —- 值栈对象 (OGNL 是从值栈中获取数据的 )

  • Action 是多例的,有一个请求,创建 Action 实例,创建一个 ActionContext 对象,代表的是 Action 的上下文对象,还会创建一个 ValueStack 对象。

  • 每个 Action 实例都有一个 ValueStack 对象 (一个请求对应一个 ValueStack 对象 )

  • 在其中保存当前 Action 对象和其他相关对象

  • Struts 框架把 ValueStack 对象保存在名为 “struts.valueStack” 的请求属性中,request 中 (值栈对象是 request 一个属性)

1
2
// 不常用
ValueStack vs = (ValueStack)request.getAttribute("struts.valueStack");

值栈的内部结构

值栈由两部分组成

  • 值栈由以下两部分组成:

    root ———— Struts 把动作和相关对象压入 ObjectStack 中–List
    context ———— Struts 把各种各样的映射关系(一些 Map 类型的对象) 压入 ContextMap

  • Struts 会默认把下面这些映射压入 ContextMap(context)中

  • 注意:request 代表的是 Map 集合的 key 值,value 的值其实也是一个 Map 集合。

    parameters:该 Map 中包含当前请求的请求参数 ?name=xxx&password=123
    request:该 Map 中包含当前 request 对象中的所有属性
    session:该 Map 中包含当前 session 对象中的所有属性
    application:该 Map 中包含当前 application 对象中的所有属性
    attr:该 Map 按如下顺序来检索某个属性: request, session, application

  • ValueStack 中存在 root 属性 (CompoundRoot) 、 context 属性 (OgnlContext )

    CompoundRoot 就是 ArrayList
    OgnlContext 就是 Map

  • context 对应 Map 引入 root 对象

    context 中还存在 request、 session、application、 attr、 parameters 对象引用

操作值栈默认指操作 root 元素

OGNL 获取值

如果从 root 栈中获取值,OGNL 表达式默认情况下不能写 # 号:

1
<s:property value="表达式"/>

如果从 context 栈中获取值,OGNL 表达式默认需要加 # 号(访问 request、 session、application、 attr、 parameters 对象数据必须写):

1
<s:property value="#表达式"/>

值栈的创建和 ActionContext 对象的关系

  • 值栈对象是 请求时创建

  • ActionContext 是绑定到当前的线程上,那么在每个拦截器或者 Action 中获取到的 ActionContext 是同一个。

  • ActionContext 中存在一个 Map 集合,该 Map 集合和 ValueStackcontext 是同一个地址。

  • ActionContext 中可以获取到 ValueStack 的引用,以后再开发,使用 ActionContext 来获取到值栈对象

获取值栈对象

  • 获得值栈对象,有三种方法:
1
ValueStack vs1 = (ValueStack) ServletActionContext.getRequest().getAttribute("struts.valueStack");
1
ValueStack vs2 = (ValueStack) ServletActionContext.getRequest().getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);

★重要

1
2
// 获取值栈对象,首先要获取 ActionContext 对象
ValueStack vs3 = ActionContext.getContext().getValueStack();

向值栈中保存数据

向值栈保存数据 (主要针对 root 栈)

push 方法

1
2
// 向栈顶压入对象
valueStack.push(Object obj);
  • push 方法的底层调用 root 对象的 push 方法(把元素添加到 0 位置)

set 方法

1
2
// 向栈顶压入 map 集合,把 key 和 obj 存入到 map 集合中
valueStack.set(String key, Object obj);
  • 源码获取 map 集合(map 有可能是已经存在的,有可能是新创建的),把 map 集合压入到栈顶,再把数据存入到 map 集合中。

在 jsp 中,通过 <s:debug/> 查看值栈的内容

从值栈中获取值

一些小细节

访问 root 中的数据不需要 #
访问 context 中的对象数据,要加 #
如果向 root 中存入对象的话,优先使用 push 方法。
如果向 root 中存入集合的话,优先要使用 set 方法。

在 OgnlContext 中获取数据

1
2
3
4
5
request:<s:property value="#request.username"/>
session:<s:property value="#session.username"/>
application:<s:property value="#application.username"/>
attr:<s:property value="#attr.username"/>
parameters:<s:property value="#parameters.cid"/>

代码演示

前提 struts.xml

1
2
3
4
5
6
7
8
<struts>
<package name="demo2" namespace="/" extends="struts-default">
<!--压栈和取值-->
<action name="save" class="com.renkaigis.demo2.ValueStack1Action" method="save">
<result name="success">/demo2/vs.jsp</result>
</action>
</package>
</struts>

注意:jsp 中首先要引入标签库 <%@taglib prefix="s" uri="/struts-tags" %>,在 jsp 中使用 <s:debug></s:debug> 可以查看值栈的内容。

以下代码演示 压栈和取值

push 字符串

Action 代码:

1
2
3
4
5
// 获取值栈
ValueStack vs = ActionContext.getContext().getValueStack();
// 压栈
vs.push("小灰");
return SUCCESS;

jsp 代码:

1
2
<%--获取栈顶的值--%>
<s:property value="[0].top"/>

set 字符串

Action 代码:

1
vs.set("msg", "小瓜");

jsp 代码:

1
2
<%--栈顶是 map 集合,通过 key 来获取值--%>
<s:property value="[0].top.msg"/>

获取对象(push)

首先需要创建一个 JavaBean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.renkaigis.demo2;

public class User {
private String username;
private String password;

public User() {
}

public User(String username, String password) {
this.username = username;
this.password = password;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

Action 代码:

1
2
3
4
// 创建 User 对象
User user = new User("小雷", "123");
// 压栈
vs.push(user);

jsp 代码:

1
2
3
4
<%--栈顶放 user 对象--%>
<s:property value="[0].top.username"/>
<%--[0].top 是可以省略的--%>
<s:property value="username"/>

获取对象(set)

Action 代码:

1
vs.set("user", user);

jsp 代码:

1
2
3
<s:property value="[0].top.user.username"/>
<%--省略关键字--%>
<s:property value="user.username"/>

List 集合(push)

Action 代码:

1
2
3
4
5
6
ArrayList<User> ulist = new ArrayList<>();
ulist.add(new User("张三", "123"));
ulist.add(new User("李四", "456"));
ulist.add(new User("王五", "789"));
// 把 ulist 压栈
vs.push(ulist);

jsp 代码:

1
2
<s:property value="[0].top[0].username"/>
<s:property value="[0].top[1].username"/>

List 集合(set)

Action 代码:

1
vs.set("ulist", ulist);

jsp 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<s:property value="ulist[0].username"/>

<%--迭代的标签
属性:
vaule 表示要迭代的集合,需要从值栈中获取
var 迭代过程中,遍历的对象(可写可不写)
* var 编写上,把迭代产生的对象默认压入到 context 栈中,取值要加 #
* var 不编写,默认把迭代产生的对象压入到 root 栈中
--%>
<s:iterator value="ulist" var="user">
<s:property value="#user.username"/>
</s:iterator>

<%--不编写 var--%>
<s:iterator value="ulist">
<s:property value="username"/>
</s:iterator>

从 context 栈中获取值

底层已经封装了 request、session 等对象,操作的就是 map 集合

request

Action 代码:

1
2
HttpServletRequest request = ServletActionContext.getRequest();
request.setAttribute("msg", "小鬼");

jsp 代码:

1
<s:property value="#request.msg"/>
session

Action 代码:

1
request.getSession().setAttribute("msg", "小卡");

jsp 代码:

1
<s:property value="#session.msg"/>
parameters

路径传值:

1
http://localhost:9090/save.action?id=10

jsp 代码:

1
<s:property value="#parameters.id"/>
attr

attr 从最小域开始找

jsp 代码:

1
<s:property value="#attr.msg"/>

EL 表达式也能获取到值栈中的数据

EL 获取值栈的值

获取上面的 ulist:

  1. 首先导包:jstl.jarstandard.jar

  2. 引标签库:<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

1
2
3
4
<%--在 jsp 页面上使用 EL 和 JSTL 标签库来取值--%>
<c:forEach items="${ulist}" var="user">
${user.username} -- ${user.password}
</c:forEach>

为什么 EL 也能访问值栈中的数据?

因为 Struts2 底层使用了装饰者模式,对 getAttribute() 方法进行了增强。

StrutsPreparedAndExecuteFilterdoFilter 代码中 request = prepare.wrapRequest(request);

Request对象进行了包装
StrutsRequestWrapper 增强了 requestgetAttribute() 方法

1
2
3
4
Object attribute = super.getAttribute(s);
if (attribute == null) {
attribute = stack.findValue(s);
}

访问 request 范围的数据时,如果数据找不到,会去值栈中找
request 对象具备访问值栈数据的能力(查找 root 的数据)


OGNL 表达式的特殊符号


# 符号的用法 ★

获得 contextMap 中的数据

1
2
3
4
5
6
<s:property value="#request.name"/>
<s:property value="#session.name"/>
<s:property value="#application.name"/>
<s:property value="#attr.name"/>
<s:property value="#parameters.id"/>
<s:property value="#parameters.name"/>

# 可以构建一个 map 集合

以构建表单为例:

1
2
3
4
5
6
7
8
9
<h3>编写表单</h3>
<form action="" method="post">
性别:<input type="radio" name="sex"/>男<input type="radio" name="sex"/>女
</form>

<h3>使用 Struts2 UI 标签方式</h3>
<s:form action="" method="post">
性别:<s:radio name="sex" list="{'男','女'}"/>
</s:form>

使用 # 构建 map 集合:

1
2
3
4
5
6
7
8
9
<h3>编写表单</h3>
<form action="" method="post">
性别:<input type="radio" name="sex" value="1"/>男<input type="radio" name="sex" value="2"/>女
</form>

<h3>使用 Struts2 UI 标签方式</h3>
<s:form action="" method="post">
性别:<s:radio name="sex" list="#{'1':'男','2:':'女'}"/>
</s:form>

% 符号的用法

强制字符串解析成 OGNL 表达式

例如:在 request 域中存入值,然后在文本框(<s:textfield>)中取值,现在到 value 上。

1
<s:textfield value="%{#request.msg}"/>

{ } 中的值用 ‘’ 引起来,此时不再是 ognl 表达式,而是普通的字符串

1
<s:property value="%{'#request.msg'}"/>

$ 符号的用法

  • 在配置文件中可以使用 OGNL 表达式,例如:文件下载的配置文件。
1
2
3
4
5
6
<action name="download1" class="com.renkaigis.demo2.DownloadAction">
<result name="success" type="stream">
<param name="contentType">${contentType}</param>
<param name="contentDisposition">attachment;filename=${downFilename}</param>
</result>
</action>