OGNL ,做爲Struts2 一大亮點,感受也是Struts2 中相對最難理解的一部分了。因此這裏認真的總結學習一下。javascript
值棧分析:html
MVC 請求處理流程中,牽涉的數據種類比較多,框架使用ValueStack 數據結構對這些數據結構進行有機的整合,便於統一管理。充分認識ValueStack 數據結構能夠便於框架使用者輕鬆自如獲取所需的數據。java
ValueStack 由ValueStack Contents 和Stack Context 兩部分構成web
每一個請求都會生成一個對應的Action 實例,每個動做在執行相應方法( 默認execute 方法) 以前,都會建立一個ValueStack 的對象。ValueStack 用來保存這個動做對象和其餘對象( 請求處理所涉及的數據);sql
ValueStack 對象至關於一個棧,它貫穿整個Action 的生命週期,每一個Action 類的對象實例都會擁有一個ValueStack 對象。數據庫
當Struts2 接受到一個*.action 請求後,並非直接調用Action 方法,而是先將Action 類的相應屬性放到ValueStack 對象的頂層節點apache
值棧也是位於內存中,它也是和parameters 、request 、session 、application 、attr 對象放在一塊兒的。值棧屬於ONGLContext 裏面的根對象。也就是說它位於整個內存中最最重要的地方,因此叫根對象瀏覽器
根對象和另外五個對象是有區別的,根對象能夠省寫# 號,好比<s:property value="user.username"/> 。值棧的生命週期與request 請求相關,每次請求產生一個值棧。默認全部的Action 會被自動放到值棧裏服務器
生命週期內置對象被封裝在一個Map 結構中。session
在跳轉的頁面上加一個struts 標籤-<s:debug/> 能夠看到ValueStack 結構。
服務器跳轉時共用值棧:
假設從一個Action1 經過服務器跳轉到Action2 的話,就意味着這兩個Action 是共享一個值棧的,由於一次請求只使用一個值棧。這時內存中狀況是這樣的:首先接收到Action1 請求後,會產生一個值棧,在棧頂存放Action1對象以及它全部的屬性,而後通過服務器跳轉到Action2 ,這時就會把Action2 對象壓入值棧的棧頂位置,此時Action1 對象以及它的全部屬性就位於棧底了。
取值過程(原則:後進先出):
棧的特徵是後進先出。因此首先到棧頂的對象裏查找是否存在這個屬性,若是棧頂的Action2 對象中不存在這個屬性的話,它就會繼續向下尋找直至棧底對象,一直查找是否存在這個屬性,若是最後找到該屬性的話,那麼就會在JSP 頁面中經過<s:property value="username"/> 輸出屬性值。若是在Action2 和Action1 都有一個同名的同類型的username 屬性的話,那麼將輸出Action2 中的屬性值。由於它是先從棧頂開始尋找屬性的,值棧的特徵就是後進先出 ,但有個前提:請求過程是經過服務器跳轉的。
三個語法:
假設此時想要獲取Action11 中的username 屬性的話,就可使用值棧的Top 語法 或者N 語法 。
1 、使用Top 語法 獲取值棧中的第二個對象的屬性:
<s:property value="[1].top.username"/>
2 、使用N 語法 獲取值棧中的第二個對象的屬性:<s:property value="[1].username"/>
3 、@ 語法 ,例如使用@ 語法調用Action 中的靜態方法:<s:property value="@vs @getVOMethod()"/>
@vs @get() 等價於@vs1@getVOMethod() ,指的是棧頂對象的靜態getVOMethod() 方法
同理@vs2@getVOMethod() 就是取值棧中第二個對象的靜態getVOMethod() 方法
客戶端跳轉時使用各自的值棧:
假如中間某一個步驟中出現了客戶端跳轉的話,那麼兩個Action 所使用的就是兩個不一樣的值棧了。因此在Action2中就不能再使用Action1 中的屬性了,在最後跳轉到的JSP 頁面中也就沒法獲取Action1 的屬性了。即從Action2 跳轉到JSP 頁面時使用的是redirect 的話,那麼最後值棧中是沒有任何的Action 對象的。這個時候咱們能夠經過連接傳參,好比<result type="redirect">test.jsp?netname=${username}</result>
意思就是取出Action2 中的username 屬性做爲參數,經過瀏覽器地址欄傳遞到JSP 頁面中
而後使用OGNL 中的# 號 獲取Paraments 對象的屬性,即<s:property value="#parameters.netname"/> 就能夠取到值了。
手工向值棧中壓入對象:
正 常狀況下值棧保存的是Action 對象,而咱們也能夠直接往值棧中添加其它對象,這時能夠在Action 中添加以下代碼向值棧中添加對象:
ActionContext.getContext.getValueStack().push(new Student("xdwang",23));
並且咱們手工往值棧中添加的Student 對象會位於棧頂 。這是由於Struts2 會首先初始化Action ,而後才能調用它的方法。初始化Action 的時候,便把Action 放到值棧中了,而後在執行它的execute() 方法時,就又往值棧中添加了Student 對象。
OGNL 歷史:
OGNL 最初是爲了可以使用對象的屬性名來創建 UI 組件 (component) 和 控制器 (controllers) 之間的聯繫,簡單來講就是:視圖與控制器之間數據的聯繫 。後來爲了應付更加複雜的數據關係。
什麼是OGNL :
OGNL 是 Object-Graph Navigation Language 的縮寫, 對象圖形導航語言 , ,從語言角度來講:它是一種操做複雜數據結構的經常使用語言 ,它旨在提供一個更高抽象度語法來對 java 對象圖進行導航。
能夠存取對象的任意屬性,調用對象的方法,遍歷整個對象的結構圖,實現字段類型轉化等功能。它使用相同的表達式去存取對象的屬性;
對象經過OgnlContext上下文對象來管理。
OGNL可讓咱們用很是簡單的表達式訪問對象層。
OGNL 應用:
1 、做爲 GUI 元素(textfield,combobox, 等)到模型對象的綁定語言;
2 、數據庫表到 Swing 的 TableModel 的數據源語言;
3 、web 組件和後臺 Model 對象的綁定語言 (WebOGNL,Tapestry,WebWork,WebObjects) ;
4 、做爲 Jakarata Commons BeanUtils 或者 JSTL 的表達式語言的一個更具表達力的替代語言;
爲何須要表達式語言 (EL)
表達式語言(EL)本質上被設計爲:幫助你使用簡單的表達式來完成一些「經常使用」的工做。一般狀況下,ELs 能夠在一些框架中找到,它被是用來簡化咱們的工做。例如:你們熟知的 Hibernate,使用 HQL(Hibernate Query Language) 來完成數據庫的操做,HQL 成了開發人員與複查的 SQL 表達式之間的一個橋樑。 在 web 框架下,表達式語言起到了類似的目的。它的存在消除了重複代碼的書寫。例如:當沒有 EL 的時候,爲了從 session 中獲得購物車而且將 ID 在網頁上呈現出來,當直接在 jsp 中使用 java 代碼來完成的時候,通常是:
- <%
- ShoppingCart cart = (ShoppingCart) session.get("cart");
- int id = cart.getId();
- %>
- <%= id%>
你也能夠將這些 code 壓縮成一句,以下,可是如今代碼就很不直觀,且不可讀。另外,雖然變成了一句,可是與上面的原始的例子同樣,也包含了一樣的表達式。例如:類型轉換:轉換 成 ShoppingCart 。這裏只不過是將原來的三個表達式變成了一句,其複雜度是沒有獲得簡化的。
- <%= ((ShoppingCart) session.get("cart")).getId() %>
當在 web 框架中使用表達式語言的時候,則能夠有效的處理這種代碼的複雜性。而不須要你,調用 servelet API,類型轉換,而後再調用 getter 方法,多數的 Els 均可將這個過程簡化爲相似於:#session.cart.id 這中更可讀的表達式。 表達式:#session.cart.id 與 java 代碼不同的是:沒有 java 代碼的 get 方法調用和類型轉換。由於這些操做是很是「經常使用」的,這時候使用 EL 就瓜熟蒂落了,使用 EL 能夠「消除」這些代碼。
OGNL標識符:
「$」有兩個主要的用途:
一、 用於在國際化資源文件中,引用OGNL表達式:
validation.require=${getText(fileName)} is required
二、 在Struts 2配置文件中,引用OGNL表達式:
- <action name="AddPhoto" class="addPhoto">
- <result type="redirect">
- ListPhotos.action?albumId=${albumId}
- </result>
- </action>
「# 」, 通常用於取出堆棧上下文中存放的對象和過濾、投影集合。這裏咱們能夠將 # 理解爲ActionContext.getContext()
1 、取出堆棧上下文:
名稱 |
做用 |
Demo |
attr |
用於按 request>>session>>application順序訪問其屬性 |
#attr.userName 至關於按順序從三個範圍讀取 userName 屬性直到找到爲止 |
request |
包含當前 HttpServletRequest 的屬性的Map |
#request.userName 至關於request.getAttribute("userName") |
session |
包含當前 HttpSession 的屬性的 Map |
#session.userName 至關於session.getAttribute("userName") |
application |
包含當前應用的 ServletContext 的屬性的Map |
#application.userName 至關於application.getAttribute("userName") |
parameters |
包含當前 HTTP 請求參數的 Map |
#parameters.id[0] 至關於request.getParameter("id") |
二、 用於過濾和投影(projecting)集合
如person.{?#this.age>20}
? -- 獲取集合中全部知足選擇邏輯的對象( 拿sql 來作比例就是"select * from xxx where age>20")
^ -- 獲取集合中第一個知足選擇邏輯的對象( 拿sql 來作比例就是"select top(1) from xxx where age>20")
$ -- 獲取集合中最後一個知足選擇邏輯的對象
%:用途是在標識的屬性爲字符串類型時,計算OGNL表達式的值,即將本來的文本屬性解析爲ognl,對於原本就是ognl的屬性不起做用
- <s:url value="test.jsp?age=#userlist['admin']">→test.jsp?#userlist['admin']---可見當字符串與OGNL表達式串起來時,只會被看成字符串對待,並不執行
- <s:url value="test.jsp?age=%{#userlist['admin']}">→test.jsp?age=44---使用了該符號,就可使得OGNL表達式被執行
- <s:set name="hello" value="5"></s:set>
- <s:property value="#hello" />
- <s:property value="%{#hello}" />
- <s:if test="%{#hello==5}">你好</s:if>
- <s:if test="#hello==5">你好</s:if>
-
- <s:set name="china" value="'你好'"></s:set>
- <s:property value="#china" />
- <s:property value="%{#china}" />
- <s:if test="%{#china=='你好'}">你好1</s:if>
- <s:if test="#china=='你好'">你好2</s:if>
注意上面的你好須要加引號,不然沒有值;
獲取Action 中的屬性值或Action 對象的某某屬性值
利用 Struts2 標籤的 <s:property> 能夠直接獲取 Action 中的引用類型 user 裏面的 name 屬性。一樣能夠經過 user.address.addr 獲取 user 中引用類型 address 中的 addr 屬性的值
像這種一層一層往下傳遞的訪問方式 , 即所謂的導航 , 也就是一步步的往下調用
調用Action 的對象裏面的普通方法:
默認的會把 Action 放到值棧裏面 , 而值棧在訪問的時候 , 並不須要值棧的名字
當咱們調用 <s:property value="user.getVOMethod()"/> 的時候。它會自動到值棧裏面查找Action對象裏面有沒有user對象,而後它就發現有user。而後它就再找user裏面有沒有getVOMethod()方法,而後它發現有,因而調用getVOMethod()。實際上調用User中的getVOMethod()方法的過程與獲取表單中的姓名密碼的方式都是相同的,都是到值棧裏面查找 ,找是否存在user對象,若是存在,接着查找user中是否存在某某屬性或方法。
調用Action 中的靜態方法:
一樣咱們也能夠在 JSP 頁面中寫一個 OGNL 表達式調用 Action 中的靜態方法 , 調用 Action 中的靜態方法時, 與調用 user 對象的 getVOMethod() 方法的過程 , 是大相徑庭的。此時 value 的寫法是固定的 , 以 @ 開頭, 後面跟上具體的包名 , 而後 @ 加上靜態方法 ,好比 <s:property value="@com.jadyer.action.LoginAction@getStatic()"/> 。 另外 user 對象是 LoginAction 中的一個屬性 ,這個屬性會自動的放到值棧裏面, 而值棧調用的時候 , 不用加上 @ 或者包名等等 , 因此直接user.getVOMethod() 就能夠了。
調用JDK 類中的靜態方法:
可 以使用 <s:property value="@@floor(46.58)"/> 輸出 floor() 的執行結果。這就意味着 若是不在@@中指定類 的話, 默認的就表示java.lang.Math類 ,當前大多數狀況下,咱們都不會省略這個類,都會寫全了的,而後在後面加上靜態方法。
集合的僞屬性:
OGNL 可以引用集合的一些特殊的屬性 , 這些屬性並非 JavaBean 模式 , 例如 size() 、 length() 。當表達式引用這些屬性時,OGNL會調用相應的方法,這就是僞屬性
好比獲取List的大小: <s:property value="testList.size"/>
List的僞屬性: size、isEmpty、iterator
Set的僞屬性: size、isEmpty、iterator
Map的僞屬性: size、isEmpty、keys、values
Iterator的僞屬性: next、hasNext
Enumeration僞屬性: next、hasNext、nextElement、hasMoreElements
說明:
1 、獲取集合中元素的實質就是調用它的toString() 方法;
2 、當OGNL 取不到值的時候,它是不會報錯的,而是什麼都不顯示出來;
3 、<s:property value="[0]"/> 返回的是ValueStack 中從上至下的全部的Object ,<s:property value="[1]"/>返回的是ValueStack 中從上至下的第二個Object ;
4 、<s:property value="[0].username"/> 返回的是成員變量username 的值。假設ValueStack 中存在兩個Action 的話,若是第一個Action 若是沒有username 變量,那麼它會繼續找第二個Action 。那麼在什麼狀況下ValueStack 中會存在兩個Action 呢?答案是在struts.xml 中配置的是從一個Action 經過<result type="chain"> 跳轉到另外一個Action ;
5 、<constant name="struts.ognl.allowStaticMethodAccess" value="true"/>
在Struts2.1.6 、Struts2.2 中必須設置struts.ognl.allowStaticMethodAccess 爲true 以後才容許使用OGNL 訪問靜態方法。而在Struts2.0.11 則無需設置,便可直接訪問;
經過下面的Demo發現2.2版本中若是不在Struts.xml中配置< constant name ="struts.ognl.allowStaticMethodAccess" value = "true" /> 會出現普通類中的靜態屬性和方法能夠訪問,而Action中的靜態屬性和方法以及jdk中的靜態屬性方法都沒法訪問。
Demo :
web.xml :
- <?xml version="1.0" encoding="UTF-8"?>
- <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
- id="WebApp_ID" version="2.5">
- <display-name>OGNLDemo</display-name>
- <filter>
- <filter-name>struts2</filter-name>
- <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>struts2</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <welcome-file-list>
- <welcome-file>login.jsp</welcome-file>
- </welcome-file-list>
- </web-app>
struts.xml :
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE struts PUBLIC
- "-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN"
- "http://struts.apache.org/dtds/struts-2.1.7.dtd">
- <struts>
- <constant name="struts.ognl.allowStaticMethodAccess" value="true"/>
- <package name="ognl" extends="struts-default">
- <action name="login" class="com.iflytek.action.LoginAction">
- <result name="input">/login.jsp</result>
- <result name="success">/loginSuc.jsp?netname=xdwang</result>
- <!-- <result name="success" type="redirect">/loginSuc.jsp?netname=xdwang</result>
- <result name="success" type="redirect">/loginSuc.jsp?netname=${user.username}</result> -->
- </action>
- </package>
- </struts>
Address.java:
- package com.iflytek.entity;
-
- /**
- * @author xdwang
- *
- * @ceate 2012-6-27 上午12:29:38
- *
- * @description Address實體
- *
- */
- public class Address {
- public static final String TIPS = "xdwang";
-
- private String addr;
-
- public String getAddr() {
- return addr;
- }
-
- public void setAddr(String addr) {
- this.addr = addr;
- }
- }
Student.java:
- package com.iflytek.entity;
-
- /**
- * @author xdwang
- *
- * @ceate 2012-6-27 上午12:31:07
- *
- * @description Student實體
- *
- */
- public class Student {
-
- private String username;
- private int grade;
-
- public Student() {
- }
-
- public Student(String username, int grade) {
- this.username = username;
- this.grade = grade;
- }
-
- public String getUsername() {
- return username;
- }
-
- public void setUsername(String username) {
- this.username = username;
- }
-
- public int getGrade() {
- return grade;
- }
-
- public void setGrade(int grade) {
- this.grade = grade;
- }
-
- @Override
- public String toString() {
- // 若是不重寫它的toString()方法的話,默認調用toString()將輸出【類型+@+內存地址的哈希值】
- return "{學生姓名:" + username + ",成績:" + grade + "}";
- }
-
- }
User.java:
- package com.iflytek.entity;
-
- /**
- *
- * @author xdwang
- *
- * @ceate 2012-6-27 上午12:29:11
- *
- * @description User實體
- *
- */
- public class User {
- private String username;
- private String password;
- private Address address;
-
- public String getUsername() {
- return username;
- }
-
- public void setUsername(String username) {
- this.username = username;
- }
-
- public String getPassword() {
- return password;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- public Address getAddress() {
- return address;
- }
-
- public void setAddress(Address address) {
- this.address = address;
- }
-
- public String getVOMethod() {
- return "這是User類中的一個普通方法";
- }
- }
LoginAction.java:
- package com.iflytek.action;
-
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
-
- import org.apache.struts2.interceptor.RequestAware;
- import org.apache.struts2.interceptor.SessionAware;
-
- import com.iflytek.entity.Student;
- import com.iflytek.entity.User;
- import com.opensymphony.xwork2.ActionSupport;
-
- /**
- * @author xdwang
- *
- * @ceate 2012-6-27 上午12:33:18
- *
- * @description 登錄Action
- *
- */
- @SuppressWarnings("serial")
- public class LoginAction extends ActionSupport implements RequestAware,
- SessionAware {
- private User user;
- private List<String> testList = new ArrayList<String>();
- private Set<String> testSet = new HashSet<String>();
- private Map<String, String> testMap = new HashMap<String, String>();
- private List<Student> stus = new ArrayList<Student>();
-
- private Map<String, String> request;
- private Map<String, String> session;
-
- public void setRequest(Map request) {
- this.request = request;
- }
-
- public void setSession(Map session) {
- this.session = session;
- }
-
- public static String getStatic() {
- return "這是LoginAction中的一個靜態方法";
- }
-
- public String getCommon() {
- return "這是LoginAction中的一個普通方法";
- }
-
- @Override
- public String execute() throws Exception {
- if (user.getUsername().trim().equalsIgnoreCase("xdwang")
- && user.getPassword().equals("1111")) {
- testList.add("list11");
- testList.add("list22");
- testList.add("list33");
- testList.add("list44");
- testList.add("list55");
-
- testSet.add("set11");
- testSet.add("set22");
- testSet.add("set33");
- testSet.add("set22");
- testSet.add("set11");
-
- testMap.put("m11", "map11");
- testMap.put("m22", "map22");
- testMap.put("m33", "map33");
- testMap.put("m44", "map44");
- testMap.put("m55", "map55");
-
- stus.add(new Student("張三", 88));
- stus.add(new Student("李四", 77));
- stus.add(new Student("王五", 66));
- stus.add(new Student("馬六", 55));
-
- request.put("req", "這是經過OGNL中的#號獲取的request屬性範圍的值");
- session.put("ses", "這是經過OGNL中的#號獲取的session屬性範圍的值");
- request.put("BB", "這是經過OGNL中的#號獲取的request屬性範圍的BB");
- session.put("BB", "這是經過OGNL中的#號獲取的session屬性範圍的BB");
- return SUCCESS;
- } else {
- return INPUT;
- }
- }
-
- public User getUser() {
- return user;
- }
-
- public void setUser(User user) {
- this.user = user;
- }
-
- public List<String> getTestList() {
- return testList;
- }
-
- public void setTestList(List<String> testList) {
- this.testList = testList;
- }
-
- public Set<String> getTestSet() {
- return testSet;
- }
-
- public void setTestSet(Set<String> testSet) {
- this.testSet = testSet;
- }
-
- public Map<String, String> getTestMap() {
- return testMap;
- }
-
- public void setTestMap(Map<String, String> testMap) {
- this.testMap = testMap;
- }
-
- public List<Student> getStus() {
- return stus;
- }
-
- public void setStus(List<Student> stus) {
- this.stus = stus;
- }
-
- }
login.jsp:
- <%@ page language="java" contentType="text/html; charset=UTF-8"
- pageEncoding="UTF-8"%>
- <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <meta http-equiv="pragma" content="no-cache">
- <meta http-equiv="cache-control" content="no-cache">
- <meta http-equiv="expires" content="0">
- <title>OGNL Demo</title>
- </head>
- <body>
- <h1>這是測試OGNL使用的登陸頁面</h1>
- <h3>
- <font color="red">提示:</font>程序設定的用戶名和密碼各爲<font color="blue"><strong>xdwang</strong></font>和<font
- color="blue"><strong>1111</strong></font>
- </h3>
- <h3>
- <font color="red">注意:</font>用戶名和密碼不正確時將停留在頁面不動
- </h3>
- <form action="<%=request.getContextPath()%>/login.action" method="POST">
- <%--這裏user.username匹配的是LoginAction中的引用類型user裏面的username屬性--%>
- <%--查看標籤庫說明的話,就知道name中指定的是對象。這裏它不是字符串,而是OGNL表達式--%>
- 姓名:<input type="text" name="user.username"><br> 密碼:<input
- type="text" name="user.password"><br> 地址:<input
- type="text" name="user.address.addr"><br> <input
- type="submit" value="測試OGNL的輸出">
- </form>
- </body>
- </html>
loginSuc.jsp: