要想全面地理解Struts2中数据的存取,就必须要结合值栈和OGNL表达式一起来理解,而OGNL存取数据的地方就是值栈,值栈是整个Struts界面视图和后台数据交互的中转站。

OGNL表达式

全称为Object-Graph Navigation Language(对象图导航语言),一种功能强大的开源表达式语言,OGNL源码下载。它可以存取java对象的任意属性,调用对象的方法,同时能够实现必要的类型转换(这点下次会讲),是Struts2默认的表达式语言,需结合Struts2 标签来使用。在上一章所介绍的标签中,属性value用到的集合或Map就是通过OGNL来动态构建的,而其中还可以使用表达式,然后通过OGNL的解析去值栈中存取值(EL表达式对应于JSTL JSP标准标签库)。

补充:Ognl动态建立集合(同样放到值栈中)

<%--Ognl表达式可以取值,也可以构建动态集合--%>
  <br/>1.构建list集合<br/>
  <s:iterator var="str" value="{'a','b','c'}">
      <s:property value="#str"/><!-- 可以不加#,但加#可提高效率,-->
  </s:iterator>

%符号用法

<!-- 值类型的标签,value值默认就是值类型,不支持ognl表达式,
需通过%符号提供一个ognl表达式运行环境-->
<s:textfield name="msg" value="%{user.password}"></s:textfield>

$符号用法

<action name="downLoad" class="com.livejq.action.DownLoadAction">
    <result type="stream">
        <param name="contentType">${contentType}</param>
        <param name="contentDisposition">attachment;filename=${filename}</param>
        <param name="inputName">downloadFile</param>
        <param name="bufferSize">1024</param>
    </result>
</action>

由于值栈分为对象栈和Map栈,所以OGNL的取值方式也有所不同。

配置

首先添加ognl-xx.jar包,然后在struts.xml中添加常量struts.ognl.allowStaticMethodAccess=true(下面需要用Ognl调用静态方法,高版本的struts默认拒绝访问)。

对象方法调用

一个Person类,一个Group类和一个Branch类,依次作为前一个类的属性(配置相应的getter和setter方法)。
root_structure.png

package com.livejq.domain;

import java.util.HashMap;
import java.util.Map;

import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;

public class TestOgnl {
    public static void main(String args[]) throws OgnlException{

        Branch b = new Branch("BRANCH");
        Group g = new Group();
        g.setBranch(b);
        g.setName("rj172");
        Person user = new Person();
        user.setName("zhl");
        user.setGroup(g);
        System.out.println("java方式:"+user.getGroup().getBranch().getBranchId());
        System.out.println("Ognl方式:"+(String)Ognl.getValue("group.branch.branchId", user));
        System.out.println("Ognl调用对象方法:"+(String)Ognl.getValue("getName()",user));
        System.out.println("Ognl关联其它对象调用方法:"+(String)Ognl.getValue("getGroup().getName()",user));
        }
}

输出为:

java方式:BRANCH
Ognl方式:BRANCH
Ognl调用对象方法:zhl
Ognl关联其它对象调用方法:rj172

分析:由于每个对象都是一个domain(属性+getter/setter)类,Struts2内置的拦截器自动将这些相关对象压入ObjectStack对象栈中,而后通过root根对象来进行相关对象的访问。

通过Context上下文

由上图可知,这是个Map栈,是OgnlContext对象实现了Map接口,OGNL上下文其实就是一个Map容器。

  1. 方式一(直接使用Map):
Map<String,Person> context = new HashMap<String, Person>();
        context.put("u", user);
        System.out.println("Ognl上下文取值:"+(String)Ognl.getValue("#u.name", context, user));

输出:

Ognl上下文取值:zhl
  1. 方式二(使用实现Map的OgnlContext):

    OgnlContext context = new OgnlContext();
        Person user = new Person();
        Person user2 = new Person();
        user.setName("liveJQ");
        user2.setName("zhl");
        context.put("u", user);
        context.put("u2", user2);
        context.setRoot(user2);
        System.out.println("直接通过user获取Context中的数据:" + (String)Ognl.getValue("#u.name", context, user));
    
        // 先构建一个Ognl表达式,再解析表达式
        Object ognl = Ognl.parseExpression("#u2.name");// 构建Ognl表达式
        Object value = Ognl.getValue(ognl, context, context.getRoot());// 解析表达式
        System.out.println("通过设置的root获取Context中的数据:" + value);

    输出:

    直接通过user获取Context中的数据:liveJQ
    通过设置的root获取Context中的数据:zhl

    上面也可以直接构造一个上转型的Map来创建context,只是它不能设置root对象:

    Map context = new HashMap();

调用静态方法

package com.livejq.domain;

public class TestOgnlStatic {

    public static final String name="测试";
    public static void Test(int x, int y) {
        int z = x*y;
        System.out.print("测试ognl静态方法调用:" + z);
    }
}
<h2>==============Ognl静态方法调用===============</h2><br><br>
    <span>静态属性:</span><s:property value="@com.livejq.domain.TestOgnlStatic@name"/><br>
    <span>静态方法调用:</span><s:property value="@com.livejq.domain.TestOgnlStatic@Test(2, 3)"/>
    <s:debug></s:debug>
</center>

页面输出:

静态属性:测试
静态方法调用:

控制台输出:

测试ognl静态方法调用:6

总结:Ognl取根元素不用#号,取非根元素要使用#号

深入分析

Ognl为一个抽象类,其中封装了许多静态方法,getValue(xx)是其中的一个,常用的两个为:

  • 通过root对象:
public static Object getValue(String expression, Object root)
            throws OgnlException
    {
        return getValue(expression, root, null);
    }

public static Object getValue(String expression, Object root, Class resultType)
            throws OgnlException
    {
        return getValue(parseExpression(expression), root, resultType);
    }

上面出现了两个方法的嵌套,调用的都是本地方法。单纯的通过解析字符串,然后到root下寻找;只是这里
可以决定是否使用转换器做类型转换。

  • 通过OgnlContext的Map容器:
public static Object getValue(ExpressionAccessor expression, OgnlContext context, Object root)
    {
        return expression.get(context, root);
    }

结合上面的实例:
ExpressionAccessor为一个接口,实现这个接口的类将会解析输入的表达式字符串,然后根据OgnlContext,通过解析得到的key来找到对应的值,而root无论设置的是哪个对象都没差。你肯定会想,那干脆去掉算了,但结果为不行,会抛出异常,source is null for getProperty(null, “name”),由此可以推测此方法还是会索引到root对象,根据解析得到的属性name寻找,若在root下没找到才根据OgnlContext中Map到其它对象中寻找。

值栈

基本说明

Struts2为每个到来的请求创建一个新的值栈,值栈封装了一次请求所需要的所有数据,值栈与请求一一对应,保证了线程安全。

使用s:debug标签输出调试信息或许会比较好理解:
request_struts_ValueStack.png

由上图可知,一个请求对应一个值栈,而每个请求都是交由一个Action类来处理然后返回一个结果的,所以ValueStack贯穿整个Action的生命周期,每个Action对象的实例都拥有一个ValueStack对象,在其中保存着当前Action对象和一些相关对象。

获取值栈:

由于request中的key值struts.valueStack对应着一个ValueStack,所以可以通过getAttibute()来获取。

  • 通过request

    ValueStack vs = (ValueStack)ServletActionContext.getRequest().getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
  • 通过ActionContext

    ValueStack vs = ActionContext.getContext().getValueStack();

    在核心过滤器StrutsPrepareAndExecuteFilter类中的doFilter方法里有:

    prepare.createActionContext(request, response);

    其为每个到来的请求都先创建一个ActionContext,而在这个方法中有如下:

    ActionContext oldContext = ActionContext.getContext();
         if (oldContext != null) {
             // detected existing context, so we are probably in a forward
             ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
         } else {
             ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
             stack.getContext().putAll(dispatcher.createContextMap(request, response, null));
             ctx = new ActionContext(stack.getContext());
         }

    ctx为ActionContext对象,ValueStack为一个接口,通过分发器初始化ValueStack,为其开辟一块数据存储空间,也就是值栈,然后将它放入ActionContext中。

值栈可以存储什么

  • 值栈是一组对象,按照提供的顺序存储以下这些对象:
  • 序号对象和说明
    1Temporary对象,实际中存在各种在页面执行期间创建的temporary对象。例如,JSP标签循环集合的当前迭代值。
    2Model对象,如果在struts应用程序中使用Model对象,则当前Model对象放在值堆栈上的action之前。
    3Action对象,这是指正在执行的当前action对象。
    4命名对象,这些对象包括#application,#session,#request,#attr和#parameters以及所引用的相应的servlet作用域。

例子:

下图为一个登陆页面点击提交后所显示的调试信息,LoginAction使用了模型驱动(在前一章Struts2 基础篇),可以看出其与上面的序号2、3中叙述的吻合。

debug_stack.png

或许你注意到了上图中还有一个属性名叫model的对应的属性值为一个User地址,所以,可以在取值时借助标签直接用model取值:

<s:property value="model.username"/>

上面亦可用EL表达式(添加jstl-xx.jar包):

${model.username }

虽然EL表达式也可以取出值栈中的数据,但效率较低,因为它先在域对象(page->request->session->application)中找对应的值,若找到则直接返回;由于Struts2中对request做了增强处理,因此若在request域中没找到则直接去值栈中获取;若在请求范围内找不到,则先作为属性在root中找,找不到会作为key在ContextMap中寻找对应的value。

  • Struts2 上传下载

    单文件上传Struts2 框架为依据“基于表单的HTML文件上传”所进行的文件处理上传提供了内置支持。当文件上传时,它通常会存储在临时...

    Struts2 上传下载
  • Struts2 标签库

    简介早期的jsp页面需嵌入大量的Java脚本来显示后台传递的数据,不但大大降低了可读性,而且非常不利于维护。 对于一个MVC框架而言,...

    Struts2 标签库
  • Struts2 基础

    Struts2 概述Struts2 是目前较为普及和成熟的基于MVC设计模式的web应用程序框架,它不仅仅是Struts1 的升级版本...

    Struts2 基础