Struts2进阶:OGNL表达式与值栈

发布于 2020-04-12  210 热度


OGNL表达式

OGNL:对象视图导航语言. ${user.addr.name} 这种写法就叫对象视图导航.
OGNL不仅仅可以视图导航.支持比EL表达式更加丰富的功能.

OGNL的三要素:

一、表达式:

表达式(Expression)是整个OGNL的核心内容,所有的OGNL操作都是针对表达式解析后进行的。通过表达式来告诉OGNL操作到底要干些什么。因此,表达式其实是一个带有语法含义的字符串,整个字符串将规定操作的类型和内容。OGNL表达式支持大量的表达式,如“链式访问对象”、表达式计算、甚至还支持Lambda表达式。

二、Root对象:

OGNL的Root对象可以理解为OGNL的操作对象。当我们指定了一个表达式的时候,我们需要指定这个表达式针对的是哪个具体的对象。而这个具体的对象就是Root对象,这就意味着,如果有一个OGNL表达式,那么我们需要针对Root对象来进行OGNL表达式的计算并且返回结果。

三、上下文环境:

有个Root对象和表达式,我们就可以使用OGNL进行简单的操作了,如对Root对象的赋值与取值操作。但是,实际上在OGNL的内部,所有的操作都会在一个特定的数据环境中运行。这个数据环境就是上下文环境(Context)。OGNL的上下文环境是一个Map结构,称之为OgnlContext。Root对象也会被添加到上下文环境当中去。

工作原理

graph LR;
subgraph OGNL Context
    Root[Root:放置任何对象]
    Context[Context:Map]
end
OGNL表达式--取值不用#号-->Root
OGNL表达式--取值须加#号-->Context

使用OGNL准备工作

导包

struts2 的包中已经包含了.所以不需要导入额外的jar包

OGNL使用示例

对Root对象访问

@Test
public void testOgnl()
{
    User user = new User("rcx", "123");
    Address address = new Address("110003", "沈阳市和平区");
    user.setAddress(address);
    try
    {
        System.out.println(Ognl.getValue("name", user));
        System.out.println(Ognl.getValue("address", user));
        System.out.println(Ognl.getValue("address.port", user));

        //输出结果:
        //rcx
        //com.rcx.ognl.Address@dda25b
        //110003
    }
    catch (OgnlException e)
    {
        e.printStackTrace();
    }
}

对Context对象访问

通过加#号可以访问Context。

@Test
public void testOgnl1()
{
    //创建一个Ognl上下文对象
    OgnlContext context = new OgnlContext();
    User user = new User("rcx", "123");
    Address address = new Address("110003", "沈阳市和平区");
    user.setAddress(address);
    Map<String, Object> context = new HashMap<String, Object>();
    context.put("init", "hello");
    context.put("user", user);
    try
    {
        System.out.println(Ognl.getValue("#init", context, user));
        System.out.println(Ognl.getValue("#user.name", context, user));
        System.out.println(Ognl.getValue("name", context, user));
        //输出结果:
        //hello
        //rcx
        //rcx
    }
    catch (OgnlException e)
    {
        e.printStackTrace();
    }
}

访问静态方法

通过加@符号可以访问静态方法

@Test
public void testOgnl3() throws Exception{
    //创建一个Ognl上下文对象
    OgnlContext context = new OgnlContext();
    //Ognl表达式语言,调用类的静态方法
    Object ognl = Ognl.parseExpression("@Math@floor(10.9)");
    Object value = Ognl.getValue(ognl, context, context.getRoot());
    System.out.println(value);
}

访问非静态方法

@Test
public void testOgnl()
{
    User user = new User("rcx", "123");
    try
    {
        System.out.println(Ognl.getValue("name.length()", user));

        //输出结果:3
    }
    catch (OgnlException e)
    {
        e.printStackTrace();
    }
}

Struts2中的值栈

ValueStack是Struts的一个接口,OgnlValueStack是ValueStack的实现类,客户端发起一个请求Struts2架构会创建一个Action实例,同时创建一个OgnlValueStack值栈实例,OgnlValueStack贯穿整个Action的生命周期,Struts2中使用OGNL将请求Action的参数封装为对象存储到值栈中,并通过OGNL表达式读取值栈中的对象属性值。

内部结构(Root和Context)

Struts2进阶:OGNL表达式与值栈

Root对象栈(CompondRoot默认存储Action实例对象)

Root栈中存储了Action实例,CompondRoot继承了ArrayList实现压栈和出栈功能。Struts2对原OGNL作出的改进就是Root使用CompondRoot(自定义栈),使用CompondRoot的findValue方法可以在CompondRoot中从栈顶向栈底查找对象的属性值。
Struts2进阶:OGNL表达式与值栈
CompondRoot作为CompondRoot的Root对象,并且在CompondRoot中Action实例位于栈顶,当读取Action的属性值时会先从栈顶对象中找对应的属性,如果找不到则继续找栈中的其他对象,如果找到则停止查找。

Context(OgnlContext,存放常见web开发对象的引用)

即OgnlContext上下文,它是一个map结构,Context数据中心key:request、response、ServletContext、requestScope、sessionScope、applicationScope、params、attrs等等
Struts2进阶:OGNL表达式与值栈

ValueStack(值栈)与ActionContext(Action上下文对象的关系)

当一个请求发送过来的时候,会执行过滤器中的doFilter方法。在这个方法中创建ActionContext对象的过程中,会创建ValueStack对象,并将ValueStack对象传递给ActionContext对象。因此,我们可以通过ActionContext对象获取ValueStack(值栈对象)。

ActionContext之所以能访问Servlet API中域对象中的数据,而不能操作其域中方法。就是ActionContext对象中有ValueStack(值栈)对象的引用。

ValueStack(值栈)的获取

通过ActionContext对象获取

//通过ActionContext对象获取值栈   
ValueStack valueStack = ActionContext.getContext().getValueStack();

通过Request对象中获取

在Struts2内部,会将ValueStack(值栈)向request域中存放一份

//通过Request对象获取值栈
ValueStack valueStack =ServletActionContext.getRequest().getAttribute("struts.valueStack");

//通过Request对象获取值栈
ValueStack valueStack =ServletActionContext.getRequest().getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);

向值栈中存入数据

在Action中提供属性的get方法

默认情况下会将Action压入值栈,因此Action中的属性也可以通过值栈访问到。

public class UserAction extends ActionSupport{

    private User user;
    public User getUser() {
        return user;
    }

    public String addUser() throws Exception {
        //向值栈中存入数据
        user = new User("mark",15);
        return "success";
    }
}

使用ValueStack中本身的方法

Root(put(Object)/set(key,Object))

public class UserAction extends ActionSupport{

    public String addUser() throws Exception {
        //通过ActionContext对象获取值栈   
        ValueStack valueStack = ActionContext.getContext().getValueStack();
        User user = new User("mark",18);
        //将User对象压入栈顶
        valueStack.push(user);
        //创建Map集合将数据压入栈顶
        valueStack.set("username","mark");
        return "success";
    }
}

向Context中存入数据

public class UserAction extends ActionSupport{

    public String addUser() throws Exception {
       //向Context中存入数据 
      ServletActionContext.getRequest().setAttribute("name","mark")
    }
}

从值栈中读取数据

el表达式取值栈中的数据:
${user.username }
${user.userage } <br/>
利用ognl表达式取值栈中的值:
<s:property value="user.username"/>
<s:property value="#name"/>

输出值栈调试信息

在JSP中加入s:debug标签可以输出值栈的调试信息。

<s:debug></s:debug>

OGNL中的特殊字符

#

获取Context对象

<% request.setAttribute("name", "kobe");%>
<s:property value="#request.name"/>

构建list集合

<s:iterator var="i" value="{'aa','bb','cc'}">
    方式1:<s:property value="i"/><br/>
    方式2:<s:property value="#i"/><br/>
</s:iterator>

构建map集合

<s:iterator var="entry" value="#{'aa':'123','bb':'456','cc':'789'}">
    方式1:<s:property value="key"/>--<s:property value="value"/><br/>
    方式2:<s:property value="#entry.key"/>--<s:property value="#entry.value"/><br/>
</s:iterator>

list和map集合的其他示例

构建list集合时可用可不用#号,构建map集合时通常需要使用#号

<!-- list示例 -->
<s:radio list="{'男','女'}" name="sex" label="性别"/><br/>
<!-- map示例 -->
<s:radio list="#{'1': '男','2':'女'}" name="sex2" label="性别"/>

%

强制解析OGNL(数据回显)

当在s标签里使用OGNL表达式的时候,如果直接写表达式通常会报错,需要使用%{OGNL表达式}来对OGNL强制解析。

<% request.setAttribute("name", "kobe");%>
姓名:<s:textfield name="name" value="%{#request.name}"/>

强制不解析OGNL(基本不用)

在OGNL表达式外面加上%{'EL表达式'}可以避免OGNL被解析。

姓名:<s:textfield name="name" value="%{'#request.name'}"/>

$

属性文件(比如国际化)

Message_zh_CN.properties

User.login=登录
User.welcome=欢迎,${#session.user.username}

Message_en_US.properties

User.login=Login
User.welcome=welcome,${#session.user.username}

xml文件(比如文件下载)

<action name="download" class="xxx.DownloadAction">
    <result type="stream">
        <param name="Content-Type">文件类型</param>
        <param name="Content-Disposition">attachment;filemane=${文件名}</param>
    </result>
</action>

我一直在开辟我的天空