OGNL ,作为Struts2 一大亮点,感觉也是Struts2 中相对最难理解的一部分了。所以这里认真的总结学习一下。
值栈分析:
MVC 请求处理流程中,牵涉的数据种类比较多,框架使用ValueStack 数据结构对这些数据结构进行有机的整合,便于统一管理。充分认识ValueStack 数据结构可以便于框架使用者轻松自如获取所需的数据。
ValueStack 由ValueStack Contents 和Stack Context 两部分构成
每个请求都会生成一个对应的Action 实例,每一个动作在执行相应方法( 默认execute 方法) 之前,都会创建一个ValueStack 的对象。ValueStack 用来保存这个动作对象和其他对象( 请求处理所涉及的数据);
ValueStack 对象相当于一个栈,它贯穿整个Action 的生命周期,每个Action 类的对象实例都会拥有一个ValueStack 对象。
当Struts2 接受到一个*.action 请求后,并不是直接调用Action 方法,而是先将Action 类的相应属性放到ValueStack 对象的顶层节点
值栈也是位于内存中,它也是和parameters 、request 、session 、application 、attr 对象放在一起的。值栈属于ONGLContext 里面的根对象。也就是说它位于整个内存中最最重要的地方,所以叫根对象
根对象和另外五个对象是有区别的,根对象可以省写# 号,比如<s:property value="user.username"/> 。值栈的生命周期与request 请求相关,每次请求产生一个值栈。默认所有的Action 会被自动放到值栈里
生命周期内置对象被封装在一个Map 结构中。
在跳转的页面上加一个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标识符:
“$”有两个主要的用途:
1、 用于在国际化资源文件中,引用OGNL表达式:
validation.require=${getText(fileName)} is required
2、 在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") |
2、 用于过滤和投影(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: