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 能獲取到值棧數據的緣由。