要想全面地理解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方法)。
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容器。
- 方式一(直接使用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
- 方式二(使用实现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标签输出调试信息或许会比较好理解:
由上图可知,一个请求对应一个值栈,而每个请求都是交由一个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中。
值栈可以存储什么
- 值栈是一组对象,按照提供的顺序存储以下这些对象:
序号 | 对象和说明 |
---|---|
1 | Temporary对象,实际中存在各种在页面执行期间创建的temporary对象。例如,JSP标签循环集合的当前迭代值。 |
2 | Model对象,如果在struts应用程序中使用Model对象,则当前Model对象放在值堆栈上的action之前。 |
3 | Action对象,这是指正在执行的当前action对象。 |
4 | 命名对象,这些对象包括#application,#session,#request,#attr和#parameters以及所引用的相应的servlet作用域。 |
例子
下图为一个登陆页面点击提交后所显示的调试信息,LoginAction使用了模型驱动(在前一章Struts2 基础篇),可以看出其与上面的序号2、3中叙述的吻合。
或许你注意到了上图中还有一个属性名叫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。
评论区