OGNL 是 Object-Graph Navigation Language 的缩写,它是一种第三方的、功能强大的表达式语言,经过它简单一致的表达式语法,能够存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。html
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 + '}'; } }
@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 */ }
@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 */ }
<%@ 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>
<%@ 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>
<%@ 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 是 Struts 的一个接口,字面意义为值栈,OgnlValueStack 是 ValueStack 的实现类,客户端发起一个请求 Struts2 架构会建立一个 Action 实例同时建立一个 OgnlValueStack 值栈实例,OgnlValueStack 贯穿整个Action 的生命周期,Struts2 中使用 OGNL 将请求 Action 的参数封装为对象存储到值栈中,并经过 OGNL 表达式读取值栈中对象的属性值。java
ValueStack 其实相似一个数据中转站,Struts2 中的数据都保存在值栈中。web
ValueStack 中有两个主要的区域:apache
context 中放置了 web 开发中经常使用对象的引用,例如:session
request:原生 Servlet 请求对象。架构
session:会话对象。app
application:ServletContext对象dom
parameters:请求参数对象。jsp
attr:依次在 request、session、application 寻找匹配值。ide
所说的操做值栈,一般指的是操做 ValueStack 中的 root 区域。
在 request、session、application 中存取值就至关于操做 ValueStack 的 context 区域。
首先请求时会通过核心过滤器,查看核心过滤器的 doFilter 方法:
1 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 2 3 HttpServletRequest request = (HttpServletRequest) req; 4 HttpServletResponse response = (HttpServletResponse) res; 5 6 try { 7 if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { 8 chain.doFilter(request, response); 9 } else { 10 prepare.setEncodingAndLocale(request, response); 11 prepare.createActionContext(request, response); 12 prepare.assignDispatcherToThread(); 13 request = prepare.wrapRequest(request); 14 ActionMapping mapping = prepare.findActionMapping(request, response, true); 15 if (mapping == null) { 16 boolean handled = execute.executeStaticResourceRequest(request, response); 17 if (!handled) { 18 chain.doFilter(request, response); 19 } 20 } else { 21 execute.executeAction(request, response, mapping); 22 } 23 } 24 } finally { 25 prepare.cleanupRequest(request); 26 } 27 }
建立 ActionContext 就在第 11 行,查看 createActionContext 方法:
1 public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) { 2 ActionContext ctx; 3 Integer counter = 1; 4 Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); 5 if (oldCounter != null) { 6 counter = oldCounter + 1; 7 } 8 9 ActionContext oldContext = ActionContext.getContext(); 10 if (oldContext != null) { 11 // detected existing context, so we are probably in a forward 12 ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap())); 13 } else { 14 ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); 15 stack.getContext().putAll(dispatcher.createContextMap(request, response, null)); 16 ctx = new ActionContext(stack.getContext()); 17 } 18 request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); 19 ActionContext.setContext(ctx); 20 return ctx; 21 }
直接从 14 行开始看,在 14 行建立了值栈对象 stack ,接着在 16 行将 stack.getContext() 传给了 ActionContext 来建立 ActionContext 实例,而 stack.getContext() 中拥有对值栈的引用,也就是说这部分执行完后在 ActionContext 中是直接能够取到值栈的。
结论:ActionContext 之因此能访问 Servlet 的 API ,是由于在其内部有值栈的引用,而值栈的 context 部分又拥有对 Servlet 经常使用对象(request、session、servletContext)的引用。
经过上一节,已经知道是能够经过 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); } }
看到 21 行,这行用来开始执行 Action,查看 executeAction 方法:
1 public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException { 2 dispatcher.serviceAction(request, response, mapping); 3 }
接着看到 serviceAction 方法:
1 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) 2 throws ServletException { 3 4 Map<String, Object> extraContext = createContextMap(request, response, mapping); 5 6 // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action 7 ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); 8 boolean nullStack = stack == null; 9 if (nullStack) { 10 ActionContext ctx = ActionContext.getContext(); 11 if (ctx != null) { 12 stack = ctx.getValueStack(); 13 } 14 } 15 if (stack != null) { 16 extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); 17 } 18 19 String timerKey = "Handling request from Dispatcher"; 20 try { 21 UtilTimerStack.push(timerKey); 22 String namespace = mapping.getNamespace(); 23 String name = mapping.getName(); 24 String method = mapping.getMethod(); 25 26 ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy( 27 namespace, name, method, extraContext, true, false); 28 29 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); 30 31 // if the ActionMapping says to go straight to a result, do it! 32 if (mapping.getResult() != null) { 33 Result result = mapping.getResult(); 34 result.execute(proxy.getInvocation()); 35 } else { 36 proxy.execute(); 37 } 38 39 // If there was a previous value stack then set it back onto the request 40 if (!nullStack) { 41 request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); 42 } 43 } catch (ConfigurationException e) { 44 logConfigurationException(request, e); 45 sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e); 46 } catch (Exception e) { 47 if (handleException || devMode) { 48 sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); 49 } else { 50 throw new ServletException(e); 51 } 52 } finally { 53 UtilTimerStack.pop(timerKey); 54 } 55 }
直接看 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
默认状况下,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(); } }
<%@ 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>
咱们已经知道了如何在 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(); } }
<%@ 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>
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>
已经知道如何操做值栈,如今咱们看一下如何在页面中获取到值栈中的数据。
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 + '}'; } }
<?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>
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(); } }
<%@ 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>
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(); } }
<%@ 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>
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(); } }
<%@ 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>
获取 root 区域中数据直接使用对象属性名便可,若是是 map 则使用 key;获取 context 中属性需在 key 前加上 ‘#’。
获取值栈数据的方式除了上面经过 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(); } }
<%@ 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>
咱们知道,EL 表达式原本就只能获取 11 个隐式对象中的数据,为何在这里还能获取值栈中的数据呢?固然是 Struts2 作了手脚,依旧从核心过滤器开始查看源码:
1 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 2 3 HttpServletRequest request = (HttpServletRequest) req; 4 HttpServletResponse response = (HttpServletResponse) res; 5 6 try { 7 if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { 8 chain.doFilter(request, response); 9 } else { 10 prepare.setEncodingAndLocale(request, response); 11 prepare.createActionContext(request, response); 12 prepare.assignDispatcherToThread(); 13 request = prepare.wrapRequest(request); 14 ActionMapping mapping = prepare.findActionMapping(request, response, true); 15 if (mapping == null) { 16 boolean handled = execute.executeStaticResourceRequest(request, response); 17 if (!handled) { 18 chain.doFilter(request, response); 19 } 20 } else { 21 execute.executeAction(request, response, mapping); 22 } 23 } 24 } finally { 25 prepare.cleanupRequest(request); 26 } 27 }
看到第 13 行,经过 prepare.wrapRequest(request) 将原生 request 进行了包装,查看 wrapRequest 方法:
1 public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException { 2 HttpServletRequest request = oldRequest; 3 try { 4 // Wrap request first, just in case it is multipart/form-data 5 // parameters might not be accessible through before encoding (ww-1278) 6 request = dispatcher.wrapRequest(request); 7 ServletActionContext.setRequest(request); 8 } catch (IOException e) { 9 throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e); 10 } 11 return request; 12 }
继续进入 dispatcher.wrapRequest 方法:
1 public HttpServletRequest wrapRequest(HttpServletRequest request) throws IOException { 2 // don't wrap more than once 3 if (request instanceof StrutsRequestWrapper) { 4 return request; 5 } 6 7 String content_type = request.getContentType(); 8 if (content_type != null && content_type.contains("multipart/form-data")) { 9 MultiPartRequest mpr = getMultiPartRequest(); 10 LocaleProvider provider = getContainer().getInstance(LocaleProvider.class); 11 request = new MultiPartRequestWrapper(mpr, request, getSaveDir(), provider, disableRequestAttributeValueStackLookup); 12 } else { 13 request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup); 14 } 15 16 return request; 17 }
看 8-14 行,若是不是文件上传类的请求,将会执行第 13 行,也就是说普通状况下请求 Action 该方法返回的 request 就是 StrutsRequestWrapper 的实例,查看该类:
1 package org.apache.struts2.dispatcher; 2 3 import com.opensymphony.xwork2.ActionContext; 4 import com.opensymphony.xwork2.util.ValueStack; 5 6 import javax.servlet.http.HttpServletRequest; 7 import javax.servlet.http.HttpServletRequestWrapper; 8 9 import static org.apache.commons.lang3.BooleanUtils.isTrue; 10 11 public class StrutsRequestWrapper extends HttpServletRequestWrapper { 12 13 private static final String REQUEST_WRAPPER_GET_ATTRIBUTE = "__requestWrapper.getAttribute"; 14 private final boolean disableRequestAttributeValueStackLookup; 15 16 17 public StrutsRequestWrapper(HttpServletRequest req) { 18 this(req, false); 19 } 20 21 22 public StrutsRequestWrapper(HttpServletRequest req, boolean disableRequestAttributeValueStackLookup) { 23 super(req); 24 this.disableRequestAttributeValueStackLookup = disableRequestAttributeValueStackLookup; 25 } 26 27 28 public Object getAttribute(String key) { 29 if (key == null) { 30 throw new NullPointerException("You must specify a key value"); 31 } 32 33 if (disableRequestAttributeValueStackLookup || key.startsWith("javax.servlet")) { 34 return super.getAttribute(key); 35 } 36 37 ActionContext ctx = ActionContext.getContext(); 38 Object attribute = super.getAttribute(key); 39 40 if (ctx != null && attribute == null) { 41 boolean alreadyIn = isTrue((Boolean) ctx.get(REQUEST_WRAPPER_GET_ATTRIBUTE)); 42 43 if (!alreadyIn && !key.contains("#")) { 44 try { 45 // If not found, then try the ValueStack 46 ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.TRUE); 47 ValueStack stack = ctx.getValueStack(); 48 if (stack != null) { 49 attribute = stack.findValue(key); 50 } 51 } finally { 52 ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.FALSE); 53 } 54 } 55 } 56 return attribute; 57 } 58 }
能够看到这个类其实就是将 getAttribute 方法进行了重写,当经过该方法获取一个值时,若是经过原生 request 未获取到,则继续从值栈中寻找这个 key 对应的值并返回。
而咱们经过 EL 表达式获取值实际上也会调用 request.getAttribute 方法,此时 Struts2 对该方法进行了包装加强,这就是使用 EL 能获取到值栈数据的缘由。