Struts2的Ognl表达式的理解与使用

在Struts中,我们可以通过Ognl表达式操作ActionContext上下文和ValueStack栈中的数据。
表现形式为:
在Java代码中,我们可以使用第三方jar包的Ognl对象来操作ActionContext和ValueStack,
将ActionContext作为Ognl的上下文,将ValueStack的Root作为Ognl的根。
当然,Struts默认就是通过这种方式访问上下文和Stack栈的。

在页面Jsp中,我们可以使用s标签库通过Ognl表达式的格式来访问这些数据。

ActionContext与ValueStack的关系图如下:
Struts2的Ognl表达式的理解与使用_第1张图片

需要注意的是,ValueStack中对上下文的引用,不是直接引用的ActionContext对象,而是上下文对象中具体存储数据的那个Map。

Ognl操作理解:

1、理解Ognl概念
Ognl,Object Graphic Navigation Language,对象图导航语言
意思就是每个对象都能根据其内部是属性和属性的嵌套,来生成一个该对象的对象图:
例如:
有地址类Address:

public class Address {

    private String province;
    private String city;
    private String detail;
    private boolean def;
    //省略getter/setter
}

有学校类School,关联地址类:

public class School {

    private String name;
    private Address addr;
    //省略getter/setter方法
}

那么对于Address,它的对象图导航就是:

Address
    --provice     --city     --detail     --def

School的对象图导航:

School
    --name     --addr         --province         --city         --detail         --def

Ognl表达式就是根据这种对象图导航的方式,来便捷的访问到类对象的属性,从而操作属性值。
比如,要访问school对象的详细地址,那表达式就是 “addr.detail”。

其实在之前的EL表达式中,我们也是使用这种方式来访问对象内容的(只是我们没拿出这个概念来说)

2、通过Ognl表达式访问数据

根据数据在不同的地方,Ognl表达式的写法不同,如果数据在上下文ActionContext的Map中,那么表达式要以#开头,如果在ValueStack中,那么可以直接写表达式。

例如,在Java中使用Ognl对象,访问ActionConext中 或 ValueStack中的name参数

访问ActionContext:
String s1 = (String) Ognl.getValue(“#name”, vs.getContext(), vs.getRoot());

访问ValueStack:
String s1 = (String) Ognl.getValue(“name”, vs.getContext(), vs.getRoot());

3、调用ValueStack的set(“名”,”值”),会向ValueStack栈中压入一个Map,Map中的数据就是新添加的键值对

4、我们可以操作ValueStack对象,比如向栈中添加数据等,但是我们一般不这么做,在页面与Action之间传参,我们一般通过上下文来操作。

5、[N]语法
Ognl表达式中的[N]语法,根据其根数据的类型不同,展现出不同的结果。

如果根数据是栈,比如ValueStack中的Root栈,那么[N]语法会返回一个集合,集合内容为从第N个元素开始一直到栈底的数据。

例如:
 Ognl.getValue("[1].username",vs.getContext(), vs.getRoot());
 表示,跳过栈顶元素,从第二个元素开始,寻找对象的username属性的值。而不是就找第二个元素的username属性。

如果我们就要瞄准栈中的第几个元素,那么就要使用[N].top,也就是需要结合top关键字。

如果根的数据类型是数组/有序集合,那么[N]语法就会返回数组或集合中的第几个元素。

例如:
List<Adrress>  list= new ArrayList();
//省略数据的充填
Ognl表达式:
Ognl.getValue("[1].province",vs.getContext(), list); 
表示获取第二个Address对象的省份信息

这两中不同的展现是由于类的内部对下标的实现逻辑有区别。

6、上下文中的域 与 requst对象中的域

向域中添加参数name,值为“Jack”,可以有以下两种方式:
1)
Map req = ActionContext.getContext().get(“request”);
req.put(“name”,”Jack”);

2)
HttpServletRequest req = ServletActionContext.getRequest();
req.setAttribute(“name”,”Jack”);

第一种方法是通过上下文获取、操作Request域,第二种方式是获取Request对象,然后自己操作对象的域,这两种方式都可以实现我们的目的。
但是我们要讨论的是,Request对象中的域 和 ActionContext上下文中的request域是同一个对象吗,可以混用吗??
显然,他们不是同一个Map,至于使用,我们可以看一下代码:

ValueStack vs = ActionContext.getContext().getValueStack();
HttpServletRequest req = ServletActionContext.getRequest();
//通过request对象添加域数据
req.setAttribute("name", "req-name");
req.setAttribute("name2", "req-name2");
req.setAttribute("name3", "req-name3");

//通过上下文中的request域模型,打印域中的数据
Map map = vs.getContext();
Map req_map = (Map) map.get("request");
System.out.println(req_map);
//{struts.actionMapping=ActionMapping@789778, name2=req-name2, name3=req-name3, name=req-name, struts.valueStack=com.opensymphony.xwork2.ognl.OgnlValueStack@3c9ff82c}
//发现上下文中的request域中有通过request对象添加的参数,即两者的内容目前一致

//接着第二次向request对象域中添加数据
req.setAttribute("name3", "req-name3-2");

//第二次打印上下文中的request域数据
System.out.println(req_map);
//{struts.actionMapping=ActionMapping@789778, name2=req-name2, name3=req-name3, name=req-name, struts.valueStack=com.opensymphony.xwork2.ognl.OgnlValueStack@3c9ff82c}
//发现上下文中的request域中的数据并没有更改,“name3”的值还是旧值“req-name3”

通过以上测试,我们发现request对象的域attrMap,与上下文中的request域,数据并不是实时同步一致的,所以,在应用中,我们不要要保证前后逻辑使用的域对象一致,不要混用。

在上面的实验中,如果接着向上下文的request域中添加/删除数据,你会发现两个域中的数据又会同步,变为相同。这其中的逻辑可以看一下Struts中关于上下文中request域对象的实现源码:org.apache.struts2.dispatcher.RequestMap

7、#request.name能访问的数据的范围

先说结论:
会先查找request域中是否有name参数,然后查找ValueStack(Root栈中是否有对象具有name属性),然后查找ActionContext的Map中,是否有Key为name的键值对。

对于第一步查找request域,我们不会有任何疑问,那么为什么会有后面对ValueStack栈以及contextMap的查找呢?

这个还得回到根本问题上:request.name,到底调用了什么方法?
我们知道#号表示访问ActionContext上下文的Map,
#request 获取上下文中的request域,类型是Map,
#request.name,”.”点相当于调用 Map的get方法,即request.get(“name”)

上下文中的request域对象,是经过struts封装的一个Map,它的准确类型是RequestMap,
我们来看一下该对象:

public class RequestMap extends AbstractMap implements Serializable {

    private static final long serialVersionUID = -7675640869293787926L;

    private Set entries;
    private HttpServletRequest request;

    //...省略
    public Object get(Object key) {
        return request.getAttribute(key.toString());
    }
 
  

get方法的实现中,调用了request对象的getAtrribute方法,看起来貌似没有什么问题,但是此处的request也不是我们之前认识的那个request对象,而是Struts又一次封装的对象,其类型为:StrutsRequestWrapper,我们看一下该对象的getAttribute方法的实现:

 /** * Gets the object, looking in the value stack if not found !!!!注意 * * @param key The attribute key */
    public Object getAttribute(String key) {
        if (key == null) {
            throw new NullPointerException("You must specify a key value");
        }

        if (disableRequestAttributeValueStackLookup || key.startsWith("javax.servlet")) {
            // don't bother with the standard javax.servlet attributes, we can short-circuit this
            // see WW-953 and the forums post linked in that issue for more info
            return super.getAttribute(key);
        }

        ActionContext ctx = ActionContext.getContext();
        Object attribute = super.getAttribute(key);!!!!注意

        if (ctx != null && attribute == null) {!!!!注意
            boolean alreadyIn = isTrue((Boolean) ctx.get(REQUEST_WRAPPER_GET_ATTRIBUTE));

            // note: we don't let # come through or else a request for
            // #attr.foo or #request.foo could cause an endless loop
            if (!alreadyIn && !key.contains("#")) {
                try {
                    // If not found, then try the ValueStack
                    ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.TRUE);
                    ValueStack stack = ctx.getValueStack();
                    if (stack != null) {
                        attribute = stack.findValue(key);!!!!注意
                    }
                } finally {
                    ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.FALSE);
                }
            }
        }
        return attribute;
    }

注意一下,我做“!!!”符号标记的地方,该类的getAttribute方法重写了父类的方法,修改了查找逻辑。当在本对象中找不到时,会调用valuestack的findValue方法(该方法会查找栈和contextMap)
通过源代码,我们就明白为什么#request.name的查找范围会那么广!
同样,在Struts的jsp页面中使用EL表达式${name},也会依次查找request本域、valuestack栈、contextMap,其背后的原因也是EL表达式默认调用request对象的getAttribute方法。

你可能感兴趣的:(Ognl表达式,ValueStack,struts2)