• 1. 第四章:Struts 2中的OGNL 主讲:刘雷
  • 2. 学习目标掌握OGNL表达式的使用 掌握值栈的概念 掌握struts2对OGNL表达式的增强
  • 3. OGNL简介Struts2的一个关键特性就是它可以对Action携带的数据进行读写访问,例如在前面我们在表单中使用user.username指定数据传递给Action的user对象的username属性,在元素中使用user.username来获取用户的名字,这是通过表达式语言(Expression Language,EL)来实现的,这种表达式语言就是OGNL。 OGNL的全称是Object Graph Navigation Language(对象图导航语言),它是一种强大的表达式语言,让你通过简单一致的表达式语法来读取和设置Java对象的属性值,调用对象的方法,遍历整个对象的结构图,实现字段类型转换等功能。
  • 4. 为什么要使用OGNL视图层的表达式语言通常是用来简化数据的访问操作,取代Java脚本代码,提供更清晰的视图层实现。例如,在JSP页面中使用JSP2.0内置的表达式语言获取user对象的username属性,可以简写为${user.username},如果换作Java脚本代码,则需要写为: <%@ page language="java" import="java.util.*,com.bim.pojo.User" pageEncoding="gbk"%> <% User user = (User)request.getAttribute("user"); String username = user.getUsername(); out.print(username); %> 哪一种方法更为简捷,相信大家已经一目了然了。 既然JSP2.0已经内置了一种表达式语言,那么为什么还要使用OGNL呢? 相对于其它的表达式语言而言,OGNL的功能更为强大,它提供了很多高级而必须的特性,例如强大的类型转换功能,静态或实例方法的执行,跨集合投影(projection),以及动态lambda表达式定义等。
  • 5. OGNL基础OGNL表达式的计算都是围绕OGNL上下文来进行的,OGNL上下文实际上就是一个Map对象,由ognl.OgnlContext类(实现了java.util.Map接口)来表示。OGNL上下文可以包含一个或多个JavaBean对象,在这些对象中有一个是特殊的,这个对象就是上下文的根(root)对象。如果在写表达式的时候,没有指定使用上下文中的哪一个对象,那么根对象将被假定为表达式所依据的对象。 计算OGNL表达式的示例代码(一):
  • 6. 计算OGNL表达式的示例代码(二)
  • 7. 计算OGNL表达式的示例代码(三)
  • 8. 说明OgnlContext就是一个Map,在Map中保存值,需要指定键(key),你在写表达式的时候使用的就是键名,而不是对象名,这一点一定要注意。 在Ognl上下文中,只能有一个根对象,如果你访问根对象,那么在写表达式的时候,直接写对象属性(property)就可以了;否则,你需要使用”#key”前缀,例如表达式:#manager.name. 另外需要注意的是,OGNL表达式中的属性(property)是JavaBean的属性,而不是类中的实例变量。
  • 9. 再看一个简单示例请看下面的需求,假设有如下用户对象模型: Java代码 public interface User { public String getName(); public Date getRegisterDate(); public Customer getCustomer(); } public interface Customer { public String getId(); public String getName(); public boolean isVip(); } public interface EntCustomer extends Customer { public String getTrustId(); // 组织机构代码证号 }  
  • 10. 问题及解决方案(一)对于给定的用户jack,且该用户所属客户是企业客户,那么我们如何获取该用户的姓名?如何获取用户所属客户的名称?如何判断该用户所属客户是否是VIP客户?如何取jack所属企业的组织机构代码证号? * 采用java代码的方式,我们可以用如下的API调用得到所需信息: Java代码 jack.getName(); jack.getCustomer().getName(); jack.getCustomer().isVip(); ((EntCustomer)jack.getCustomer()).getTrustId();  
  • 11. 问题及解决方案(二)* 但我们现在在讲述OGNL,因此通过采用OGNL,我们可以用如下方式取得我们所需要的信息: Java代码 jack.name jack.customer.name jack. customer.vip jack.customer.trustId   由此我们可以看到OGNL的表达方式与java表达方式有以下几点不同: ** 不需关注对象类型,不需进行类型转换 ** 表达方式更简短和直观 OGNL表达式最大的优点就是:*简单* 和 *直观*,你不这样认为吗? 如果你觉得上面的表达式还不够简单和直观,那我们再来看: Java代码 name   这也是一个OGNL表达式,也就是取姓名!简单吗?至少足够直观了吧
  • 12. OGNL基本概念 我们前面看到了OGNL的一个最简单的例子,事实上OGNL确实很简单,如果能理解上面那个例子的用法,那么我们就掌握了OGNL的80%的用法了。 上面的例子虽然简单,但其中却含有OGNL的两个最基本的概念:*表达式(expression)* 和 *上下文(context)*,我们先看*表达式*。 表达式 OGNL就是表达式!它能让我们用简洁直观的语法表达我们的想法,如同上面的例子一般。简洁直观就是表达式的最大优点!我们知道表达式总是有一个结果,也就是说表达式总是会求值出一个结果,这个结果可能是一个字符串(如名称、组织机构代码证号等),或者是一个布尔值(如是否是VIP客户等),至于这个结果要怎么使用,那就是我们自己来决定的了。 上下文(context) 表达式的概念,我相信很好理解,但什么是上下文(context)?简单来说上下文就是环境,表达式求值的环境!还是不理解吗?我们来看一个例子: 还是上面最后那个例子:Java代码 name   细心的你是否会问,这个表达式要取谁的姓名呢?OK,很好!这就是环境,"谁"就存在于环境之中,也就是存在上下文之中。对于不同的环境/上下文,相同的表达式会有不同的结果!而环境/上下文的实质是什么呢?就是一组带名称的对象集合。
  • 13. OGNL上下文概念详解 OGNL上下文概念详解 我们前面说上下文就是一组名称-对象对的集合,如下图所示就是一个简单的上下文: Java代码 user ---> User(name:"jack", ...) request ---> HttpServletRequest(header: ...) 那么在上面的环境中,我们可以有如下的OGNL表达式: Java代码 #user.name // 取用户的姓名 #user.age // 取用户年龄 #user.birthday // 取用户生日 #user.customer.name // 取用户所属客户的名称 #request.parameters // 取请求参数   请注意上面表达式中的"#user"和"#request"的用法,"#"表示访问环境/上下文中的对象。
  • 14. OGNL上下文概念详解现在可以很方便地访问环境中的对象了,那么如果你比较懒惰的话(记住:在程序员群体,懒惰是褒义词!),你是否觉得访问用户的姓名,年龄,生日,等等其它属性如果全部要使用“#user”来访问会不会太麻烦了呢?OK,OGNL的设计者早就考虑了这个问题,我们可以指定user为环境中的特权对象,访问该对象可以不需要使用#user的方式,如下所示代码与上面的完全等价,当然,前提是要预先指定user为特权对象: Java代码 name // 取用户的姓名 age // 取用户年龄 birthday // 取用户生日 customer.name // 取用户所属客户的名称 #request.parameters // 取请求参数   我们上面所说的"特权对象"在OGNL中称为"根对象"(root) 综上所述,理解OGNL表达式的关键是理解其上下文的概念,因为OGNL的上下文概念中引入了“根对象”的概念 。
  • 15. struts2中的OGNL上下文 struts2中的OGNL上下文 struts2对OGNL上下文的概念又做了进一步扩充,在struts2中,OGNL上下文通常如下所示: Java代码                   |-- application                   |                   |-- session                   | context map---|--OgnlValueStack(root) [ user, action, OgnlUtil, ... ]                   |                   |-- request                   |                   |--parameters                   |                   |-- attr   
  • 16. 我们可以使用"#request"访问HttpServletRequest对象, "#session"访问HttpSession对象,但请注意"根对象"是什么?是ValueStack! 那么ValueStack是什么?值栈。也就是一组对象的堆栈。也就是说,在struts2中,根对象不是我们通常的一个对象,而是一组对象。我们可以push新的对象到值栈中,也可以弹出值栈的栈顶对象。假设我们将user对象push到值栈中,那么如下的表达式将与之前我们见过的表达式一样,具有相同的结果: Java代码 name // 取用户的姓名 age // 取用户年龄 birthday // 取用户生日 customer.name // 取用户所属客户的名称 #request.parameters // 取请求参数   也就是说,我们使用name这个表达式的时候,ONGL会取"根对象"的name属性,但现在根对象是ValueStack!那么访问ValueStack的name属性意味着什么呢?这意味着: ValueStack会先查看栈顶元素是否有name属性,如果有就返回该属性值,否则取出栈顶下的元素,继续查看,直到栈底为止。 以上就是OGNL表达式的核心概念,你理解了吗?下一步,你需要了进一步了解OGNL的语法,以发掘其更强大的功能!
  • 17. 理解Struts2中的ValueStack ValueStack实际是一个接口,在Struts2中利用OGNL时,实际上使用的是实现了该接口的OgnlValueStack类,这个类是Struts2利用OGNL的基础。OgnlValueStack类的主要属性关系图如下                                                        |--application                                                        |                                                        |--session context map (OgnlValueStack属性)--|                                                        |--value stack (OgnlValueStack的root属性,实际是个ArrayList)                                                        |                                                        |--request                                                        |                                                        |--parameters                                                        |                                                        |--attr (searches page, request, session, then application scopes) OgnlValueStack类包含两个重要的属性,一个root和一个context。其中root本质上是一个ArrayList,而context是一个Map(更确切的说是一个OgnlContext对象)。在这个OgnlContext对象(context)中,有一个默认的顶层对象root,OGNL访问context中这个默认顶层对象中的元素时,是不需要#号的,直接通过元素的名称来进行访问,而访问其他对象时,如request、session、attr等,则需要#号引用。Struts2将OgnlValueStack的root对象赋值给了context中的root对象,在OgnlValueStack的root对象中,保存着调用Action的实例,因此,在页面上通过Struts2标签访问Action的属性时,就不需要通过#号来引用。 当系统创建了Action实例后,该Action实例已经被保存到ValueStack中,故无须书写#即可访问Action属性。
  • 18. OgnlValueStack与ActionContext的关系OgnlValueStack与ActionContext的关系 在Struts2中,OgnlValueStack是ActionContext的基础,在ActionContext中,有一个Map类型的属性context,而这个context就是OgnlValueStack的context对象,ActionContext中的getSession()、getApplication()等方法,底层都是通过context.get()来实现的。
  • 19. 分清ActionContext 、ValueStack 、Stack Context ActionContext 一次Action调用都会创建一个ActionContext 调用:ActionContext context = ActionContext.getContext() ValueStack 由OGNL框架实现 可以把它简单的看作一个栈(List) 。 Stack Object:放入stack中的对象,一般是action。 Stack Context(map):stack上下文,它包含一系列对象,包括request/session/attr/application map等。 EL:存取对象的任意属性,调用对象的方法,遍历整个对象结果… ActionContext是Action上下文,可以得到request, session ,application ValueStack是值栈 存放表单中的值 Stack Context 栈上下文 也是用来存值的
  • 20. Struts2表达式OGNL支持的常量1.字符串常量(string literal) 以单引号或双引号括起来的字符串,例如:‘Hello World’和”Hello World”.在JAVA中,不能用单引号来界定字符串常量,而在OGNL中是可以的。不过特别要注意的是:如果是单个字符的字符串常量,则必须使用双引号来界定,例如:“S”。OGNL的字符串也支持转义序列,例如:要在JSP页面中输出”You said ,”Hello World”.”,那么可以使用标签,如下: 注意:不要忽略了用于界定整个常量字符串的单引号。如果没有写单引号,那么整个字符串将不会被看成是字符串常量,而是被当作根对象的属性名,从而导致错误。 字符常量(Character literal) 以单引号括起来的字符。例如,‘H’。注意,不能使用双引号,否则将被看成是字符串常量。 数值常量(Numeric literal) 除了Java中的int,long,float,double外,OGNL还让你用“B”或“b”后缀指定BigDecimal常量,用“H”或”h”后缀指定BigInteger常量,例如,123(int常量),123l(long常量),123.35f(float常量),123b(BigDecimal常量),123h(BigInteger常量)。 布尔常量(Boolean literal)true和false null常量(null literal).
  • 21. struts2 中 OGNL表达式的使用 Struts 2默认的表达式语言是OGNL,原因是它相对其它表达式语言具有下面几大优势: 支持对象方法调用,如xxx.doSomeSpecial(); 支持类静态的方法调用和值访问,表达式的格式为@[类全名(包括包路径)]@[方法名 | 值名],例如:@java.lang.String@format('foo %s', 'bar')或@tutorial.MyConstant@APP_NAME; 支持赋值操作和表达式串联,如price=100, discount=0.8, calculatePrice(),这个表达式会返回80; 访问OGNL上下文(OGNL context)和ActionContext; 操作集合对象。
  • 22. #、%和$: “#”的用途 访问OGNL上下文和Action上下文 ①#相当于ActionContext.getContext();下表有几个ActionContext中有用的属性: parameters:包含当前HTTP请求参数的Map,#parameters.id[0]作用相当于request.getParameter("id") request:包含当前HttpServletRequest的属性(attribute)的Map,#request.userName相当于request.getAttribute("userName") session:包含当前HttpSession的属性(attribute)的Map, #session.userName相当于session.getAttribute("userName") application:包含当前应用的ServletContext的属性(attribute)的Map,#application.userName相当于application.getAttribute("userName") attr:用于按request > session > application顺序访问其属性(attribute),#attr.userName相当于按顺序在以上三个范围(scope)内读取userName属性,直到找到为止 ②用于过滤和投影(projecting)集合,如books.{?#this.price<100}; ③构造Map,如#{'foo1':'bar1', 'foo2':'bar2'}。
  • 23. #、%和$: “%”的用途 “%”符号的用途是在标志的属性为字符串类型时,计算OGNL表达式的值。例如在Ognl.jsp中加入以下代码:

    %的用途

  • 24. #、%和$: “$”的用途“$”有两个主要的用途: 用于在国际化资源文件中,引用OGNL表达式 例如国际化资源文件中的代码:reg.agerange=国际化资源信息:年龄必须在${min}同${max}之间。     在Struts 2框架的配置文件中引用OGNL表达式,例如下面的代码片断所示:                             10             100             BAction-test校验:数字必须为${min}为${max}之间!             Struts 2配置文件中,引用OGNL表达式,如 ListPhotos.action?albumId=${albumId}
  • 25. OGNL的应用场景 标签中:   配置文件中: /main.jsp?name=${name}
  • 26. OGNL访问属性 访问方法 访问静态属性和方法 访问构造方法 访问数组 访问集合 – 投影、选择(? ^ $) Lambda :[…]
  • 27. OGNL访问举例调用值栈中属性: 调用Action中的静态方法: 调用JDK中的类的静态方法: 等同于: 调用值栈中对象的普通方法: 调用普通类中的静态属性: 注意属性不能声明为私有的即private 调用普通类的构造方法: 获取List: 获取List某一元素: 获取Set: 获取Map: 获取Map中某一元素: 注意:在OGNL中可以使用如上形式:@[className]@[field or method]调用静态变量或方法。 Set由于是无序的,所以不能使用下标获取数据。 访问静态方法 注意,两个@是约定..也就是必须这么写.第一个@后面跟的是类的全名.第二个@后面跟的是对应的方法名.当然,这个方法必须是静态的  这个访问静态方法在struts2.1以后的版本里面需要设置一个属性,否则系统默认是不支持访问静态方法的(struts2.0版本默认是支持访问静态方法的).具体的方法是在struts.xml里面添加这么一句  
  • 28. 集合的伪属性 OGNL能够引用集合的一些特殊的属性,这些属性并不是JavaBeans模式,例如size(),length()等等. 当表达式引用这些属性时,OGNL会调用相应的方法,这就是伪属性.Collection Special Properties Collection(inherited by Map, List & Set) size ,isEmpty List iterator Map keys , values Set iterator Iterator next , hasNext Enumeration next , hasNext , nextElement , hasMoreElements
  • 29. OGNL中#的使用: #可以取出堆栈上下文中的存放的对象. 名称 作用 例子 parameters 包含当前HTTP请求参数的Map #parameters.id[0]作用相当于request.getParameter("id") request 包含当前HttpServletRequest的属性(attribute)的Map #request.userName相当于request.getAttribute("userName") session 包含当前HttpSession的属性(attribute)的Map #session.userName相当于session.getAttribute("userName") application 包含当前应用的ServletContext的属性(attribute)的Map #application.userName相当于application.getAttribute("userName") attr 用于按request > session > application顺序访问其属性(attribute) #application.userName相当于application.getAttribute("userName")
  • 30. OGNL引用Action的属性 OGNL是一个对象,属性的查询语言。在OGNL中有一个类型为Map的Context(称为上下文),在这个上下文中有一个根元素(root),对根元素的属性的访问可以直接使用属性名字,但是对于其他非根元素属性的访问必须加上特殊符号#。 因为Action实例被放在Value Stack中,而Value Stack又是根元素(root)中的一个,所以对Action中的属性的访问可以不使用标记#,而对其他的访问都必须使用#标记。 ActionContext中的其他非根(root)元素的属性可以按照如下的方式访问: or or Action类可以使用ActionContext中的静态方法来访问ActionContext。 ActionContext.getContext().getSession().put("mySessionPropKey", mySessionObject);
  • 31. OGNL与Collection(Lists,Maps,Sets) 生成List的语法为: {e1,e2,e3}. 上面的代码生成了一个HTML Select对象,可选的内容为: name1,name2,name3,默认值为:name2。 生成Map的语法为:#{key1:value1,key2:value2}. 上面的代码生成了一个HTML Select对象,foo名字表示的内容为:foovalue,bar名字表示的内容为:barvalue。 对于集合,OGNL提供了两个元素符:in和not in,其中in判断某个元素是否在指定集合中;not in则用于判断某个元素是否不在指定集合中。
  • 32. 判断一个对象是否在List内存在:    muhahaha    boo    muhahaha    boo 取得一个List的一部分: ? – 所有满足选择逻辑的对象 ^ -   第一个满足选择逻辑的对象 $ -   最后一个满足选择逻辑的对象 例如: person.relatives.{? #this.gender == 'male'} 上述代码取得这个人(person)所有的男性(this.gender==male)的亲戚(relatives)
  • 33. Lambda 表达式OGNL支持简单的Lambda表达式语法,使用这些语法可以建立简单的lambda函数。 例如: Fibonacci: if n==0 return 0; elseif n==1 return 1; else return fib(n-2)+fib(n-1); fib(0) = 0 fib(1) = 1 fib(11) = 89 OGNL的Lambda表达式如何工作呢? Lambda表达式必须放在方括号内部,#this表示表达式的参数。例如: #fib =:[#this==0 ? 0 : #this==1 ? 1 : #fib(#this-2)+#fib(#this-1)]定义了一个Lambda表达式 #fib(11) 调用了这个表达式。 所以上述代码的输出为:89 声明一个使用递归来计算阶乘的函数, #fact =:[#this<=1?1:#this*#fact(#this-1)],#fact(30H)
  • 34. 注意在JSP2.1中#被用作了JSP EL(表达式语言)的特殊记好,所以对OGNL的使用可能导致问题, 一个简单的方法是禁用JSP2.1的EL特性,这需要修改web.xml文件:           *.jsp       true    
  • 35. Struts2在OGNL基础上的增强 值栈(ValueStack)    Struts2将OGNL上下文设置为Struts2中的ActionContext(内部使用的仍然是OgnlContext),并将值栈设为OGNL的根对象。   我们知道,OGNL上下文中的根对象可以直接访问,不需要使用任何特殊的“标记”,而引用上下文中的其他对象则需要使用“#”来标记。由于值栈是上下文中的根对象,因此可以直接访问。那么对于值栈中的对象该如何访问呢?Struts2提供了一个特殊的OGNLPropertyAccessor,它可以自动查找栈内的所有对象(从栈顶到栈底),直接找到一个具有你所查找的属性的对象。也就是说,对于值栈中的任何对象都可以直接访问,而不需要使用 “#”。    假设值栈中有两个对象:student和employee,两个对象都有name属性,student有学号属性number,而 employee有薪水属性salary。employee先入栈,student后入栈,位于栈顶,那么对于表达式name,访问的就是student 的name属性,因为student对象位于栈顶;表达式salary,访问的就是employee的salary属性。正如你所见,访问值栈中的对象属性或方法,无须指明对象,也不用“#”,就好像值栈中的对象都是OGNL上下文中的根对象一样。这就是Struts2在OGNL基础上做出的改进。
  • 36. OGNL-----[N]语法[N]语法  如上所述,如果想要访问employee的name属性,应该如何写表达式呢?我们可以使用[N].xxx(N是从0开始的整数)这样的语法来指定从哪一个位置开始向下查找对象的属性,表达式[1].name访问的就是employee对象的name属性。
  • 37. OGNL-----[N]语法在使用[N].xxx语法时,要注意位置序号的含义,它并不是表示“获取栈中索引为N的对象”,而是截取从位置N开始的部分栈。假设栈中有三个对象:Object0,Object1和Object2都有name属性,如下图所示: 表达式name访问的是Object0的name属性,在,而[1].name访问的则是Object2的name属性,因为[1]是一个包含了Object1和Object2的部分栈,而且只有Object2有name属性。
  • 38. OGNL-----top关键字top关键字 Struts2引入了一个新的关键字:top用于获取栈顶的对象. 对于上张PPT所示的栈,表达式top获取的就是Object0这个对象本身, 结合[N].xxx语法,我们就可以获取栈中任意位置的对象。 例如:[0].top获取Object0(等同于top), [1].top获取Object1 [2].top获取Object2, [2].top.name访问Object2中的name属性 访问静态成员 除了使用标准的OGNL表达式访问静态字段和静态方法外,Struts2还允许你不指定完整的类名,而是通过“vs”前缀来调用保存在栈中的静态字段和静态方法。    @vs@FOO_PROPERTY    @vs@someMethod()    @vs1@someMethod() vs表示ValueStack,如果只有vs,那么将使用栈顶对象的类;如果在vs后面跟上一个数字,那么将使用栈中指定位置处的对象类。
  • 39. 值栈中的Action实例 Struts2框架总是把Action实例放在栈顶。因为Action在值栈中,而值栈又是OGNL中的根,所以引用Action的属性可以省略“#”标记,这也是为什么我们在结果页面中可以直接访问Action的属性的原因。 Struts2中的命名对象 Struts2还提供了一些命名对象,这些对象没有保存在值栈中,而是保存在ActionContext中,因此访问这些对象需要使用“#”标记。这些命名对象都是Map类型。 parameters 用于访问请求参数。如:#parameters['id']或#parameters.id,相当于调用了HttpServletRequest对象的getParameter()方法。 注意,parameters本质上是一个使用HttpServletRequest对象中的请求参数构造的Map对象,一量对象被创建(在调用Action实例之前就已经创建好了),它和HttpServletRequest对象就没有了任何关系。 request 用于访问请求属性。如:#request['user']或#request.user,相当于调用了HttpServletRequest对象的getAttribute()方法。 session 用于访问session属性。如:#session[‘user’]或#session.user,相当于调用了HttpSession对象的getAttribute()方法。 application 用于访问application属性。如:#application['user']或#application.user,相当于调用了ServletContext的getAttribute()方法。 attr 如果PageContext可用,则访问PageContext,否则依次搜索request、session和application对象。例如,#session[‘user’]=“zhangsan”,假定pageContext不可用,#attr[‘user’](或attr.user)将依次搜索request、session、application对象,最终在session中找到user属性。
  • 40. 谢谢!