<%@ page import="org.sunxin.ch03.model.User" %><% User user = (User)request.getAttribute("user"); String username = user.getUsername(); out.print(username);%>
哪一种写法更为简捷,相信读者已经一览无余了。
相对付其他的表达式措辞而言,OGNL的功能更为强大,它供应了很多高等而必需的特性,例如强大的类型转换功能、静态或实例方法的实行、跨凑集投影(projection),以及动态lambda表达式定义等。
OGNL根本OGNL表达式的打算都是环绕OGNL高下文来进行的,OGNL高下文实际上便是一个Map工具,由ognl.OgnlContext类(实现了java.util.Map接口)来表示。OGNL高下文可以包含一个或多个JavaBean工具,在这些工具中有一个是分外的,这个工具便是高下文的根(root)工具。如果在写表达式的时候,没有指定利用高下文中的哪一个工具,那么根工具将被假定为表达式所依据的工具。
下面我们给出一个例子程序,看看在OGNL中如何根据高下文来打算表达式的值。
例1 打算OGNL表达式的示例代码
Employee.javapackage org.sunxin.ch07.ognl;public class Employee{private String name;public String getName(){return name;}public void setName(String name){this.name = name;}} Manager.javapackage org.sunxin.ch07.ognl;public class Manager{private String name;public String getName(){return name;}public void setName(String name){this.name = name;}} OgnlExpression.javapackage org.sunxin.ch07.ognl;import ognl.Ognl;import ognl.OgnlContext;import ognl.OgnlException;/ OgnlExpression类用于封装OGNL表达式的解析 /public class OgnlExpression{private Object expression;public OgnlExpression(String expressionString) throws OgnlException{super();expression = Ognl.parseExpression(expressionString);}public Object getExpression(){return expression;}public Object getValue(OgnlContext context, Object rootObject)throws OgnlException{return Ognl.getValue(getExpression(), context, rootObject);}public void setValue(OgnlContext context, Object rootObject, Object value) throws OgnlException{Ognl.setValue(getExpression(), context, rootObject, value);}public static void main(String[] args) throws OgnlException{Employee employee = new Employee();employee.setName("employee");Manager manager = new Manager();manager.setName("manager");//创建OGNL高下文OgnlContext context = new OgnlContext();//将employee工具放到OGNL高下文中context.put("employee", employee);//将manager工具放到OGNL高下文中context.put("manager", manager);//将employee工具设置为OGNL高下文的根工具context.setRoot(employee);//表达式name,将实行employee.getName(),由于employee工具是根工具OgnlExpression expr = new OgnlExpression("name");System.out.println("根工具的名字是:"+expr.getValue(context,context. getRoot()));//表达式#employee.name,将实行employee.getName()expr = new OgnlExpression("#employee.name");System.out.println("employee工具的名字是:"+expr.getValue(context, context.getRoot()));//表达式#manager.name,将实行manager.getName(),//如果你访问的不是OGNL高下文中的根工具, //那么必须在前面加上一个名称空间,例如#managerexpr = new OgnlExpression("#manager.name");System.out.println("manager工具的名字是:"+expr.getValue(context, context.getRoot()));}}
读者紧张看OgnlExpression类的main()方法,在这个方法中笔者已经给出了详细的注释,这里就不再赘述了。
前面已经先容过,OgnlContext便是一个Map,在Map中保存值,须要指定键(key),你在写表达式的时候利用的是键名,而不是工具名,这一点须要读者把稳。在OGNL高下文中,只能有一个根工具,如果你访问根工具,那么在写表达式的时候,直接写工具的属性(property)就可以了;否则,你须要利用“#key”前缀,例如表达式:#manager.name。
其余须要提醒读者的是,OGNL表达式中的属性(property)是JavaBean的属性,而不是类中的实例变量。
提示:在实际运用中,并不须要编写如例1所示的代码去解析OGNL表达式。在Spring MVC框架中,表达式的解析是自动进行的,我们唯一要做的便是书写精确的表达式。
OGNL表达式OGNL表达式的根本单元便是导航链(navigation chain),常日简称为“链(chain)”。最大略的链由下列部分组成:
属性名例如,例1中的name和manager.name。
方法调用例如,manager.hashCode(),返回manager工具的散列码。
数组索引例如,emails[0],返回当前工具的邮件列表中的第一个邮件地址。
所有OGNL表达式的打算都是在当前工具的高下文中,一个链大略地利用链中先前链接的结果作为下一步打算确当前工具。我们看如下所示的链:
name.toCharArray()[0].numericValue.toString()
这个表达式按照下列的步骤进行打算:
(1)获取根工具的name属性;
(2)在String结果上调用toCharArray()方法;
(3)从char数构成果中提取第一个字符;
(4)从提取的字符工具上得到numericValue属性(这个字符被表示为Character工具,Character类有一个名为getNumericValue()的方法);
(5)在Integer工具结果上调用toString()方法。
这个表达式的终极结果是末了的toString()方法调用返回的字符串。
读者在学习后面的OGNL表达式的内容时,可以利用上一节编写的OgnlExpression类,对各种表达式进行测试,看看表达式的输出结果,以更好地理解OGNL表达式的用法。例如,你要测试静态方法的调用,可以在OgnlExpression类的main()方法的末了编写如下代码:
OgnlExpression expr2 = new OgnlExpression("@@max(5,3)");System.out.println("@@max(5,3):" + expr2.getValue(context,context.getRoot()));
如果调用涌现缺点,先不要焦急寻求帮助,静下心来查看缺点提示,仔细检讨你的代码,想想你的表达式语法是否写精确了。
常量OGNL支持的常量除了涵盖Java措辞的常量类型外,还供应了自有的常量类型,以方便OGNL的利用。
OGNL支持的所有常量类型如下所示:
字符串常量(String literal)以单引号或双引号括起来的字符串,例如:'Hello World',"Hello World"。在Java中,不能用单引号来界定字符串常量,而在OGNL中是可以的。不过要把稳的是,如果是单个字符的字符串常量,则必须利用双引号来界定,例如:"S"。OGNL的字符串也支持转义序列,例如,要在JSP页面中输出“You said, "Hello World".”,那么可以利用<s:property>标签,如下:
<s:property value="'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常量)、123.35(double常量)、123b(BigDecimal常量)、123h(BigInteger常量)。
布尔常量(Boolean literal)true和false。
null常量(null literal)操作符OGNL支持所有的Java操作符,并供应了一些特有的操作符。与Java相同的操作符我们就不先容了,下面看一下OGNL特有的操作符。
逗号(,)或序列操作符OGNL的逗号操作符是从C措辞中借鉴而来的。逗号被用于分隔两个或多个独立的表达式,全体表达式的值是末了一个子表达式的值。例如表达式:name, #manager.name。
第一个表达式name和第二个表达式#manager.name依次被打算,全体表达式的值是第二个表达式的值。
花括号({})操作符花括号({})操作符用于创建列表。利用花括号将元素括起来,元素之间利用逗号分隔,例如表达式{"zhangsan", "lisi", "wangwu"},创建了带有三个元素的列表。
in和not in操作符in和not in操作符用于判断一个值是否在凑集中,例如:name in {null,"Untitled"} || name。
访问JavaBean的属性最常用的表达式便是访问JavaBean属性的表达式。我们看下面的代码:
public class Address{ private String country; private String city; private String street; ... //省略getter和setter方法 }public class Employee{ private String name; private Address address; ... //省略getter和setter方法 }
假如有一个employee工具作为OGNL高下文的根工具,那么对付下列的表达式:
name对应的Java代码是employee.getName()。
address.country对应的Java代码是employee.getAddress().getCountry()。
调用方法从技能层面上来说,访问JavaBean的属性便是进行方法调用(调用属性的getter方法),不过,并不是所有的方法都符合getXxx()和setXxx()形式,OGNL供应了调用任何方法的能力。例如,你要将employee工具的name属性的值转换为大写形式,可以利用表达式:name.toUpperCase()。
调用静态方法和静态字段OGNL支持调用类中的静态方法和静态字段,可以利用如下的语法格式:
@class@method(args)//调用静态方法@class@field//访问静态字段
个中class必须给出完全的类名。例如:@java.lang.String@valueOf(5)。如果省略class,那么默认利用的类是java.lang.Math,这让你可以很随意马虎地调用Map类中的静态成员。例如:
@@min(5,3)//调用java.lang.Math的min方法@@max(5,3) //调用java.lang.Math的max方法@@PI //访问java.lang.Math的PI静态字段
如果你有一个类的实例,你也可以通过工具来调用类的静态方法,就彷佛这个方法是一个实例方法一样。
调用布局方法OGNL支持对布局方法的调用,从而创建一个新的工具。你可以像在Java中一样利用new操作符来创建一个工具,不同的是,你必须利用完全的限定类名(带包名的类名),例如,new java.util.ArrayList(),而不是new ArrayList()。
索引访问OGNL支持多种索引办法的访问。
数组和列表索引在OGNL中,数组和列表可以大致算作是一样的。用于数组的索引访问办法也适用于访问列表中的元素,例如array[0],list[0]。表达式:{'zhangsan', 'lisi', 'wangwu'}[1],访问列表中的第2个元素,即'lisi',对应的Java代码为list.get(1)。
与Java中一样,如果索引超出了数组和列表的范围,那么将抛出IndexOutOfBounds Exception非常。
2. JavaBean的索引属性
JavaBean支持索引属性的观点,索引属性便是获取和设置数组时利用的属性。要利用索引属性,须要供应两对getter/setter方法,一对用于数组,一对用于数组中的元素。语法格式如下:
public PropertyType[] getPropertyName()public void setPropertyName(PropertyType[] values)public PropertyType getPropertyName(int index)public void setPropertyName(int index, PropertyType value)
例如,有一个索引属性interest,它的getter/setter方法如下:
private String[] interest;public String[] getInterest(){ return interest; } public void setInterest(String[] interest){ this.interest=interest; }public String getInterest(int i){ return interest[i]; }public void setInterest(int i, String newInterest) { interest[i]= newInterest; }
对付表达式:
interest[2]
OGNL可以精确阐明这个表达式,调用getInterest(2)方法,如果是设值的情形下,调用setInterest(2, value)方法。
3. OGNL工具索引属性
JavaBean的索引属性只能利用整型作为索引,OGNL扩展了索引属性的观点,可以利用任意的工具来作为索引。
当创造一个可能的工具索引属性时,OGNL按照下列的方法署名查找方法样式:
public PropertyType getPropertyName(IndexType index)public void setPropertyName(IndexType index, PropertyType value)PropertyType和IndexType必须在对应的get和set方法中相互匹配。
一个实际的利用工具索引属性的例子便是在Servlet API中,Session工具有两个用于获取和设置属性(attribute)的方法:
public Object getAttribute(String name)public void setAttribute(String name, Object value)
下面的OGNL表达式可以在Session中获取或者设置foo属性(attribute)。
session.attribute["foo"]
对凑集进行操作
OGNL与其他表达式措辞比较,其紧张特性之一便是它对Java凑集API供应了非常好的支持,创建凑集并对其进行操作是OGNL的一个基本特性。下面我们看一下创建并访问凑集的办法,以及如何根据凑集的内容进行投影和选择。
创建凑集由于数组和列表的操作办法比较相似,以是常日也把数组归为凑集,本节中也会包含数组的创建和访问。
(1)创建列表
在先容OGNL操作符的时候,我们已经先容过了列表的创建,即利用花括号将元素括起来,元素之间利用逗号分隔。例如,表达式:
{"zhangsan", "lisi", "wangwu"}
创建了一个带有3个元素的List工具,元素的类型是String。对应的Java代码如下:
List list = new ArrayList(3);list.add("zhangsan");list.add("lisi");list.add("wangwu");return list;
列表元素通过索引来访问,例如list[0],{"zhangsan", "lisi", "wangwu"}[1]。
(2)创建数组
OGNL中创建数组与Java措辞中创建数组是类似的。例如,表达式:new int[] { 1, 2, 3 },创建了一个int类型的数组,由3个整型值1、2、3组成;表达式:new int[5],创建了可容纳5个元素的数组,数组中的元素都初始化为0。
数组元素通过索引来访问,array[0],new int[] { 1, 2, 3 }[1]。Java中的数组有一个分外的属性length,在OGNL中也可以直接访问,例如array.length。
(3)创建Map
Map利用分外的语法来创建,如下所示:
#{"key1" : "value1", "key2" : "value2", …, "keyN" : "valueN" }
例如,表达式:
#{"first" : "zhangsan", "second" : "lisi", "third" : "wangwu" }
创建了一个带有3个元素的Map工具,对应的Java代码如下:
Map map = new HashMap(3);map.put("first", "zhangsan");map.put("second", "lisi");map.put("third", "wangwu");return map;
如果你想要指定创建的Map类型,可以在左花括号之前指定Map实现类的类名。例如:
#@java.util.LinkedHashMap@{"first" : "zhangsan", "second" : "lisi", "third" : "wangwu" }
这个例子将创建LinkedHashMap类的实例,以确保插入元素的顺序能够被保存。
Map元素通过key来访问,例如map["first"],#{"first" : "zhangsan", "second" : "lisi", "third" : "wangwu" }['third'],或者map.first,#{"first" : "zhangsan", "second" : "lisi", "third" : "wangwu" }.third。
2. 凑集的伪属性
Java凑集API中有很多常用的方法,例如size()、isEmpty()等,这些方法并不符合JavaBean对付属性(property)访问器方法的命名哀求(即getXxx()和setXxx()),因此不能像访问JavaBean属性一样来调用这些方法。为了简化对Java凑集API的方法调用,OGNL供应了一些伪属性,使得可以按照属性的访问办法来调用凑集中的方法。
OGNL供应的凑集伪属性如表1所示。
表1 分外的凑集伪属性
凑集类型
伪 属 性
OGNL表达式
Java代码
List、Set、Map
size,isEmpty
list.size,set.isEmpty
list.size(),set.isEmpty()
List
iterator
list.iterator
list.iterator()
Map
keys,values
map.keys,map.values
map.keySet(),map.values()
Set
iterator
set.iterator
set.iterator()
Iterator
next,hasNext
iter.next,iter.hasNext
iter.next(),iter.hasNext()
Enumeration
next,hasNext,nextElement,
hasMoreElements
enum.next,enum.hasNext
enum.nextElement(),
enum.hasMoreElements()
解释:Enumeration的两对伪属性是同义的,即next等价于nextElement,hasNext等价于hasMoreElements。
3. 投影(projection)
OGNL供应了一种大略的办法在一个凑集中对每一个元素调用相同的方法,或者抽取相同的属性,并将结果保存为一个新的凑集。这称之为“投影”,以数据库术语来说,便是从一张表中选择列的子集。例如,假设employees是一个包含了employee工具的列表,下列表达式:
#employees.{name}
将返回所有雇员名字的列表。
在投影期间,利用#this变量来引用迭代中确当前元素。例如:
objects.{ #this instanceof String ? #this : #this.toString()}
上面的表达式将工具列表中的元素作为字符串值产生一个新的元素列表。
4. 选择(selection)
OGNL供应了一种大略的办法来利用表达式从凑集中选择某些元素,并将结果保存到新的凑集中。这称之为“选择”,以数据库的术语来说,便是从一张表中选择行的子集。例如,假设employees是一个包含了employee工具的列表,下面表达式:
#employees.{?#this.salary > 3000}
返回雇员薪水大于3000的所有雇员的列表。
为了从匹配的元素列表中得到第一个匹配的元素,你可以利用索引。例如:
#employees.{?#this.salary > 3000} [0]
然而,这可能会导致涌现非常,由于如果这个匹配没有返回任何结果(或者这个结果列表是空的),你将得到一个ArrayIndexOutOfBoundsException非常。
将选择语法中的“?”换成“^”,可以从凑集中只选择第一个匹配的元素,并将其作为一个列表返回。如果任何元素都没有匹配成功,那么结果将是一个空列表。
下面的表达式:
#employees.{^#this.salary > 3000}
从employees中取出雇员薪水大于3000的第一个元素,将其作为一个列表返回。
将选择语法中的“?”换成“$”,可以从凑集中选择末了一个匹配的元素,并将其作为一个列表返回。如果任何元素都没有匹配成功,那么结果将是一个空列表。
下面的表达式:
#employees.{$#this.salary > 3000}
从employees中取出雇员薪水大于3000的末了一个元素,将其作为一个列表返回。
下面我们对选择表达式中的3个分外字符(?、^、$)的用法做一个总结。
?——选取匹配选择逻辑的所有元素。
^——选取匹配选择逻辑的第一个元素。
$——选取匹配选择逻辑的末了一个元素。
lambda表达式OGNL供应了一个简化的lambda表达式语法,让你可以写一个大略的函数,定义lambda表达式的语法是::[…]。OGNL中的lambda表达式只能利用一个参数,这个参数可以通过#this变量来引用。OGNL将lambda表达式看作是常量。
我们看两个例子。
(1)声明一个利用递归来打算阶乘的函数,然后调用它:
#fact = :[#this<=1? 1 : #this#fact(#this-1)], #fact(30H)
把稳:lambda表达式是方括号([])中的部分。#this变量代表这个表达式的参数,它的初始值是30H(后缀H表示这是一个BigInteger常量),每一次递归调用表达式都将参数的值减1。全体OGNL表达式是一个利用了逗号(,)操作符的表达式,它的值便是调用lambda表达式的值。
(2)声明一个打算斐波那契数列的函数,然后调用它:
#fib =:[#this==0 ? 0 : #this==1 ? 1 : #fib(#this-2)+#fib(#this-1)], #fib(11)
同样,lambda表达式是方括号([])中的部分,#this变量代表表达式的参数,它的初始值为11。
OGNL的类型转换OGNL还供应了强大的类型转换功能,可以将一个工具转换为各种不同的类型。在表达式打算过程中,OGNL可以根据须要自动对某个工具进行类型转换。例如,要将工具转换为Boolean类型,如果工具是Number类型,则用它的双精度浮点值和0比较,非0为true,0为false;如果工具是Character类型,那么字符值非0即为true,否则为false;如果是一样平常工具,只要这个工具不为null,那么便是true,否则为false。
OGNL支持将一个工具转换为Boolean、Number、Integer和凑集类型。至于详细的转换过程,在实际利用中我们可以不去考虑。
我是专注于软件开拓和IT教诲的孙鑫老师,喜好我的文章欢迎关注、转发、评论、点赞和收藏,我会常常与大家分享IT技能、编程措辞的文章和传授教化视频。目前已发布完全的《Vue.js从入门到实战》一书传授教化视频,正在发布《Java无难事》一书传授教化视频。