OGNL
概述
OGNL 是 Object-Graph Navigation Language 的缩写,它是一种第三方的、功能强大的表达式语言,通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。
入门
java工程下使用
package com.zze.bean; import java.util.Date; public class User { private String name; private Integer age; private Date birthday; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", birthday=" + birthday + '}'; } }
com.zze.bean.User
@Test public void test1() throws OgnlException { // 获得 context OgnlContext context = new OgnlContext(); // 获得根对象 Object root = context.getRoot(); Object value = Ognl.getValue("'hello Struts2'.length()", context, root); System.out.println(value); /* 13 */ }
例:调用对象方法
@Test public void test2() throws OgnlException { // 获得 context OgnlContext context = new OgnlContext(); // 获得根对象 Object root = context.getRoot(); // 执行表达式:@类名@方法名 Object value = Ognl.getValue("@java.lang.Math@random()", context, root); System.out.println(value); /* 0.6367826736345159 */ }
例:调用静态方法
@Test public void test3() throws OgnlException { // 获得 context OgnlContext context = new OgnlContext(); User user = new User(); user.setName("张三"); user.setAge(12); context.setRoot(user); // 表达式直接写放入 root 中对象的属性名称即可取到对应属性名的值 Object name = Ognl.getValue("name", context, context.getRoot()); Object age = Ognl.getValue("age", context, context.getRoot()); System.out.println(name); System.out.println(age); System.out.println(age.getClass()); /* 张三 12 class java.lang.Integer */ }
例:访问 Root 中的数据
@Test public void test4() throws OgnlException { // 获得 context OgnlContext context = new OgnlContext(); User user = new User(); user.setName("张三"); user.setAge(12); context.put("user", user); // 表达式直接写放入 context 中 #key 即可取到对应值 Object name = Ognl.getValue("#user.name", context, context.getRoot()); Object age = Ognl.getValue("#user.age", context, context.getRoot()); System.out.println(name); System.out.println(age); /* 张三 12 */ }
例:访问 context 中的数据
Struts中使用
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Test</title> </head> <body> <%--访问对象方法--%> <s:property value="'hello Struts2'.length()"/> <hr> <%-- 访问静态方法 需要设置常量:struts.ognl.allowStaticMethodAccess = true --%> <s:property value="@java.lang.Math@random()"/> </body> </html>
例:访问对象方法 & 静态方法
特殊符号
#
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Test</title> </head> <body> <% request.setAttribute("str", "托马斯的小货车"); %> <s:property value="#request.str"/> </body> </html>
'#' 号可以用来获取值栈中 context 区域的数据
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Test</title> </head> <body> <%--构建 List--%> <s:iterator var="letter" value="{'a','b','c'}"> <s:property value="letter"/>|<s:property value="#letter"/> </s:iterator> <hr> <%--构建 Map--%> <s:iterator var="entry" value="#{1:'aa',2:'bb',3:'cc'}"> <s:property value="key"/>|<s:property value="#entry.key"/> <s:property value="value"/>|<s:property value="#entry.value"/> <br> </s:iterator> </body> </html>
'#' 号可以用来构建一个 Map
%
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Test</title> </head> <body> <% request.setAttribute("name", "托马斯"); %> <s:property value="#request.name"/> <br> <%-- 按如下嵌套标签会报错: <s:textfield name="name" value="<s:property value="#request.name"/>" /> 可以利用 %{} 强制解析表达式 --%> <s:textfield name="name" value="%{#request.name}" /> </body> </html>
'%' 号可以用来强制解析字符串为表达式
$
<validators> <field name=”intb”> <field-validator type=”int”> <param name=”min”>10</param> <param name=”max”>100</param> < message>BAction-test校验:数字必须为${min}为${max}之间!</message> </field-validator> </field> </validators>
'$' 号可以用来在配置文件中引用值栈中的值
ValueStack
概述
ValueStack 是 Struts 的一个接口,字面意义为值栈,OgnlValueStack 是 ValueStack 的实现类,客户端发起一个请求 Struts2 架构会创建一个 Action 实例同时创建一个 OgnlValueStack 值栈实例,OgnlValueStack 贯穿整个Action 的生命周期,Struts2 中使用 OGNL 将请求 Action 的参数封装为对象存储到值栈中,并通过 OGNL 表达式读取值栈中对象的属性值。
ValueStack 其实类似一个数据中转站,Struts2 中的数据都保存在值栈中。
值栈的内部结构
ValueStack 中有两个主要的区域:
- root:其实就是一个 ArrayList。
- context:其实就是一个 Map。
context 中放置了 web 开发中常用对象的引用,例如:
request:原生 Servlet 请求对象。
session:会话对象。
application:ServletContext对象
parameters:请求参数对象。
attr:依次在 request、session、application 寻找匹配值。
所说的操作值栈,通常指的是操作 ValueStack 中的 root 区域。
在 request、session、application 中存取值就相当于操作 ValueStack 的 context 区域。
值栈与ActionContext的关系
首先请求时会经过核心过滤器,查看核心过滤器的 doFilter 方法:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); } else { prepare.setEncodingAndLocale(request, response); prepare.createActionContext(request, response); prepare.assignDispatcherToThread(); request = prepare.wrapRequest(request); ActionMapping mapping = prepare.findActionMapping(request, response, true); if (mapping == null) { boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { chain.doFilter(request, response); } } else { execute.executeAction(request, response, mapping); } } } finally { prepare.cleanupRequest(request); } }
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter#doFilter
创建 ActionContext 就在第 11 行,查看 createActionContext 方法:
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) { ActionContext ctx; Integer counter = 1; Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); if (oldCounter != null) { counter = oldCounter + 1; } 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()); } request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); ActionContext.setContext(ctx); return ctx; }
org.apache.struts2.dispatcher.ng.PrepareOperations#createActionContext
直接从 14 行开始看,在 14 行创建了值栈对象 stack ,接着在 16 行将 stack.getContext() 传给了 ActionContext 来创建 ActionContext 实例,而 stack.getContext() 中拥有对值栈的引用,也就是说这部分执行完后在 ActionContext 中是直接可以取到值栈的。
结论:ActionContext 之所以能访问 Servlet 的 API ,是因为在其内部有值栈的引用,而值栈的 context 部分又拥有对 Servlet 常用对象(request、session、servletContext)的引用。
ValueStack的获得
通过上一节,已经知道是可以通过 ActionContext 获取到值栈的引用的。接着看核心过滤器的 doFilter 方法:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); } else { prepare.setEncodingAndLocale(request, response); prepare.createActionContext(request, response); prepare.assignDispatcherToThread(); request = prepare.wrapRequest(request); ActionMapping mapping = prepare.findActionMapping(request, response, true); if (mapping == null) { boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { chain.doFilter(request, response); } } else { execute.executeAction(request, response, mapping); } } } finally { prepare.cleanupRequest(request); } }
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter#doFilter
看到 21 行,这行用来开始执行 Action,查看 executeAction 方法:
public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException { dispatcher.serviceAction(request, response, mapping); }
org.apache.struts2.dispatcher.ng.ExecuteOperations#executeAction
接着看到 serviceAction 方法:
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException { Map<String, Object> extraContext = createContextMap(request, response, mapping); // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); boolean nullStack = stack == null; if (nullStack) { ActionContext ctx = ActionContext.getContext(); if (ctx != null) { stack = ctx.getValueStack(); } } if (stack != null) { extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); } String timerKey = "Handling request from Dispatcher"; try { UtilTimerStack.push(timerKey); String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false); request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); // if the ActionMapping says to go straight to a result, do it! if (mapping.getResult() != null) { Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { proxy.execute(); } // If there was a previous value stack then set it back onto the request if (!nullStack) { request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch (ConfigurationException e) { logConfigurationException(request, e); sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e); } catch (Exception e) { if (handleException || devMode) { sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } else { throw new ServletException(e); } } finally { UtilTimerStack.pop(timerKey); } }
org.apache.struts2.dispatcher.Dispatcher#serviceAction
直接看 41 行,当值栈不为空时,将值栈的引用放入了 request 域。
结论:除了通过 ActionContext 获得值栈,我们还可以通过 request 获取到值栈。
所以在 Action 中我们可以通过如下代码获取值栈:
// 获取值栈方式 1 、通过 ActionContext ValueStack valueStack1 = ActionContext.getContext().getValueStack(); // 获取值栈方式 2 、通过 request // STRUTS_VALUESTACK_KEY = "struts.valueStack"; ValueStack valueStack2 = (ValueStack)ServletActionContext.getRequest().getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); System.out.println(valueStack1 == valueStack2); // true
操作值栈
方法一:在Action中提供属性的get方法
默认情况下,Struts2 会将访问的 Action 对象压入值栈,所以在 Action 中提供的属性会随之存入值栈:
package com.zze.action; import com.opensymphony.xwork2.ActionSupport; public class Test1Action extends ActionSupport { private String name; private Integer age; public String getName() { return name; } public Integer getAge() { return age; } @Override public String execute() throws Exception { this.name = "张三"; this.age = 19; return super.execute(); } }
Action
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Test</title> </head> <body> <s:property value="name"/> <s:property value="age"/> </body> </html>
jsp
方法二:手动调用值栈方法
我们已经知道了如何在 Action 中获取值栈,当然也可以在 Action 中操作值栈:
package com.zze.action; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; public class Test2Action extends ActionSupport { @Override public String execute() throws Exception { ActionContext.getContext().getValueStack().set("name","张三"); ActionContext.getContext().getValueStack().set("age",20); return super.execute(); } }
Action
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Test</title> </head> <body> <s:property value="name"/> <s:property value="age"/> </body> </html>
jsp
查看值栈数据
Struts2 为方便我们调试,给我们提供了一个标签,我们用这个标签可以直接查看到值栈中的数据:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Test</title> </head> <body> <s:debug/> </body> </html>
jsp
标签获取值栈数据
已经知道如何操作值栈,现在我们看一下如何在页面中获取到值栈中的数据。
Struts2 为简易我们在页面中获取值栈数据的操作,给我们提供了一些标签,看如下示例:
准备
package com.zze.bean; import java.util.Date; public class User { private String name; private Integer age; private Date birthday; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", birthday=" + birthday + '}'; } }
com.zze.bean.User
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <constant name="struts.devMode" value="true"/> <package name="test" extends="struts-default" namespace="/"> <action name="*" class="com.zze.action.{1}Action"> <result>/index.jsp</result> </action> </package> </struts>
struts.xml
例1:root中获取JavaBean对象
package com.zze.action; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; import com.zze.bean.User; public class Test1Action extends ActionSupport { @Override public String execute() throws Exception { User user = new User(); user.setName("张三"); user.setAge(29); // 将 user 压入栈顶 ActionContext.getContext().getValueStack().push(user); return super.execute(); } }
com.zze.action.Test1Action
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Test</title> </head> <body> <%--可直接访问栈顶对象属性--%> <s:property value="name"/> <s:property value="age"/> <s:debug/> </body> </html>
index.jsp
例2:root中获取JavaBean对象集合
package com.zze.action; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; import com.zze.bean.User; import java.util.ArrayList; import java.util.List; public class Test2Action extends ActionSupport { @Override public String execute() throws Exception { User user1 = new User(); user1.setName("张三"); user1.setAge(29); User user2 = new User(); user2.setName("李四"); user2.setAge(30); List<User> userList = new ArrayList<>(); userList.add(user1); userList.add(user2); ActionContext.getContext().getValueStack().set("userList",userList); return super.execute(); } }
com.zze.action.Test2Action
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Test</title> </head> <body> <s:property value="userList[0].name"/> <s:property value="userList[0].age"/> <s:property value="userList[1].name"/> <s:property value="userList[1].age"/> <s:debug/> </body> </html>
index.jsp
例3:context中获取域字段
package com.zze.action; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; public class Test3Action extends ActionSupport { @Override public String execute() throws Exception { ActionContext.getContext().put("name","张三"); ActionContext.getContext().getSession().put("name","李四"); ActionContext.getContext().getApplication().put("name","王五"); return super.execute(); } }
com.zze.action.Test3Action
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Test</title> </head> <body> <s:property value="#request.name"/> <s:property value="#session.name"/> <s:property value="#application.name"/> <s:debug/> </body> </html>
index.jsp
获取 root 区域中数据直接使用对象属性名即可,如果是 map 则使用 key;获取 context 中属性需在 key 前加上 ‘#’。
EL获取值栈数据
获取值栈数据的方式除了上面通过 Struts2 提供的标签的方式,还可以通过 EL 表达式获取,例如:
package com.zze.action; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; import com.zze.bean.User; public class Test4Action extends ActionSupport { @Override public String execute() throws Exception { User user = new User(); user.setName("托马斯"); ActionContext.getContext().getValueStack().push(user); return super.execute(); } }
com.zze.action.Test4Action
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="s" uri="/struts-tags" %> <html> <head> <title>Test</title> </head> <body> ${name} <s:debug/> </body> </html>
index.jsp
我们知道,EL 表达式本来就只能获取 11 个隐式对象中的数据,为什么在这里还能获取值栈中的数据呢?当然是 Struts2 做了手脚,依旧从核心过滤器开始查看源码:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); } else { prepare.setEncodingAndLocale(request, response); prepare.createActionContext(request, response); prepare.assignDispatcherToThread(); request = prepare.wrapRequest(request); ActionMapping mapping = prepare.findActionMapping(request, response, true); if (mapping == null) { boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { chain.doFilter(request, response); } } else { execute.executeAction(request, response, mapping); } } } finally { prepare.cleanupRequest(request); } }
org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter#doFilter
看到第 13 行,通过 prepare.wrapRequest(request) 将原生 request 进行了包装,查看 wrapRequest 方法:
public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException { HttpServletRequest request = oldRequest; try { // Wrap request first, just in case it is multipart/form-data // parameters might not be accessible through before encoding (ww-1278) request = dispatcher.wrapRequest(request); ServletActionContext.setRequest(request); } catch (IOException e) { throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e); } return request; }
org.apache.struts2.dispatcher.ng.PrepareOperations#wrapRequest
继续进入 dispatcher.wrapRequest 方法:
public HttpServletRequest wrapRequest(HttpServletRequest request) throws IOException { // don't wrap more than once if (request instanceof StrutsRequestWrapper) { return request; } String content_type = request.getContentType(); if (content_type != null && content_type.contains("multipart/form-data")) { MultiPartRequest mpr = getMultiPartRequest(); LocaleProvider provider = getContainer().getInstance(LocaleProvider.class); request = new MultiPartRequestWrapper(mpr, request, getSaveDir(), provider, disableRequestAttributeValueStackLookup); } else { request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup); } return request; }
org.apache.struts2.dispatcher.Dispatcher#wrapRequest
看 8-14 行,如果不是文件上传类的请求,将会执行第 13 行,也就是说普通情况下请求 Action 该方法返回的 request 就是 StrutsRequestWrapper 的实例,查看该类:
package org.apache.struts2.dispatcher; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.util.ValueStack; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import static org.apache.commons.lang3.BooleanUtils.isTrue; public class StrutsRequestWrapper extends HttpServletRequestWrapper { private static final String REQUEST_WRAPPER_GET_ATTRIBUTE = "__requestWrapper.getAttribute"; private final boolean disableRequestAttributeValueStackLookup; public StrutsRequestWrapper(HttpServletRequest req) { this(req, false); } public StrutsRequestWrapper(HttpServletRequest req, boolean disableRequestAttributeValueStackLookup) { super(req); this.disableRequestAttributeValueStackLookup = disableRequestAttributeValueStackLookup; } public Object getAttribute(String key) { if (key == null) { throw new NullPointerException("You must specify a key value"); } if (disableRequestAttributeValueStackLookup || key.startsWith("javax.servlet")) { 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)); 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; } }
org.apache.struts2.dispatcher.StrutsRequestWrapper
可以看到这个类其实就是将 getAttribute 方法进行了重写,当通过该方法获取一个值时,如果通过原生 request 未获取到,则继续从值栈中寻找这个 key 对应的值并返回。
而我们通过 EL 表达式获取值实际上也会调用 request.getAttribute 方法,此时 Struts2 对该方法进行了包装增强,这就是使用 EL 能获取到值栈数据的原因。