郑重申明: 本文文字部分主要参照了Struts2技术内幕,代码主要为OGNL源码中的,从书上抄写的,自己原创的。
无论是不是原创的,绝大多数内容都是Fans同学自己手敲的。
提示:阅读本文前最好先阅读日志--
MVC模式中--数据流转和数据访问的困境及解决方案
。
OGNNL(Object Graph Navigation Language)是一个开源的表达式引擎。通过使用OGNL ,我们能够通过表达式存取Java对象树中的任意属性和调用Java对象树的方法等。也就是说, 如果我们把表达式看成是一个带有语义的字符串,那么OGNL就是这个语义字符串与Java
对象之间沟通的催化剂。通过OGNL,我们就可以轻松解决在数据流转过程中所遇到的各种问题。
1.深入OGNL的API
ognl.Ognl.java
//通过传入的OGNL表达式,从root对象里取值
public static Object getValue(String expression, Map context, Object root)
throws OgnlException {
return getValue(expression, context, root, null);
}
//通过传入的OGNL表达式,在给定的上下文环境中,往root对象里写值
public static void setValue(String expression, Map context, Object root,
Object value) throws OgnlException {
setValue(parseExpression(expression), context, root, value);
}
在初步浏览了OGNL的API之后,我们可以编写一个单元测试来测试一下上面列出的OGNL静态方法接口,
实现代码如下。
BasicOgnlTest.java
package org.leiwen.struts2.ognl;
import java.util.HashMap;
import java.util.Map;
import junit.framework.TestCase;
import ognl.Ognl;
import ognl.OgnlException;
import org.junit.Test;
import org.leiwen.struts2.ognl.entity.User;
public class BasicOgnlTest extends TestCase {
@Test
public void testGetValue() throws OgnlException {
// 创建root对象
User user = new User();
user.setId(1);
user.setName("fans");
// 创建上下文环境
Map<String, String> context = new HashMap<String, String>();
context.put("introduction", "My name is ");
// 测试从root对象中进行表达式计算并获取结果
Object name = Ognl.getValue(Ognl.parseExpression("name"), user);
assertEquals("fans", name);
// 测试从上下文环境中进行表达式计算并获取结果
Object contextValue = Ognl.getValue(
Ognl.parseExpression("#introduction"), context, user);
assertEquals("My name is ", contextValue);
// 测试同时从 将root对象和上下文环境中作为表达式的一部分 进行计算
Object hi = Ognl.getValue(Ognl.parseExpression("#introduction+name"),
context, user);
assertEquals("My name is fans", hi);
}
@Test
public void testSetValue() throws OgnlException {
// 创建root对象
User user = new User();
user.setId(1);
user.setName("fans");
// 对root对象进行写值操作
Ognl.setValue("group.name", user, "dev");
Ognl.setValue("age", user, "18");
assertEquals("dev", user.getGroup().getName());
assertEquals(Integer.valueOf(18), user.getAge());
}
}
我们可以看到,通过简单的API就能够完成对各种对象树的“取值”和“写值”操作。而 “取值”和“写值”是我们日后所有工作的基础,如果需要深入了解OGNL的细节,就需要对 传入OGNL的这3个参数进行研究。这3个参数,我们称之为OGNL的三要素。OGNL的API极其简单,
无论何种复杂的功能,OGNL会将其最终映射到OGNL的三要素中,通过调用引擎完成计算。
2. OGNL 三要素
2.1.表达式
表达式(Expression)是整个OGNL的核心,所有OGNL操作都是针对表达式解析后进行的。表达式 会规定此次ONGL操作到底要“干什么”。因此,表达式其实是一个带有语法含义的字符串,这个 字符串将规定操作的类型和操作的内容。
OGNL支持大量的表达式语法,不仅支持“链式”描述对象访问路径,还支持表达式中进行简单的计算, 甚至还能够支持复杂的Lambda表达式等。
2.2.Root对象
OGNL的Root对象可以理解为OGNL的操作对象。当OGNL表达式规定了“干什么”之后,我们还需要 指定“对谁干”。OGNL的Root对象实际上是一个Java对象,是所有OGNL操作的实际载体。这就意味着, 如果有一个OGNL的表达式,那么我们实际上需要针对Root对象来进行OGNL表达式的计算并返回结果。
3.3.上下文环境
有了表达式和Root对象,我们已经可以使用OGNL的基本功能了。例如,根据表达式针对Root对象 进行“取值”或者“写值”操作。不过,事实上,在OGNL的内部,所有的操作都会在一个特定的 数据环境中运行,这个数据环境就是OGNL的上下文环境(Context)。说的再明白一些,就是这个 上下文环境将规定OGNL的操作“在哪里干”。
OGNL的上下文环境是一个Map结构,称之为OgnlContext。之前我们提到的Root对象,事实上也会被添加到上下文环境中,并且被作为一个特殊的变量进行处理。
3.OGNL的基本操作
3.1对Root对象的访问
针对OGNL的Root对象的对象树的访问是通过使用“点号”将对象的引用串联起来实现的。通过 这种方式,OGNL实际上讲一个树形的对象结构转化成一个链式结构的字符串来表达语义。如下所示:
//获取Root对象中的name属性的值
name
//获取Root对象department属性中的name属性的实际值
department.name
//获取Root对象department属性的manager属性的实际值
department.manager.name
3.2对上下文环境的访问
由于OGNL的上下文是一个Map结构,在OGNL进行计算时可以事先在上下文环境中设置一些参数,
并让OGNL将这些参数带入进行计算。有时候也需要对上下文环境中的参数进行访问,需要通过
#符号加上链式表达式来进行,从而表示与访问Root对象的区别。如下所示:
//获取OGNL上下文环境中名为introduction对象的值
#introduction
//获取OGNL上下文环境中名为parameters的对象中user对象中名为name的属性的值
#parameters.user.name
3.3对静态变量的访问
在OGNL中,对于静态变量或者静态方法的访问,需要通过@[class]@[field/method]的表达式语法
来进行。如下所示:
//访问com.example.Resource的ENABLE属性的值
@com.example.Resource#ENABLE
//调用com.example.Resource get方法
@com.example.Resource@get()
3.4方法调用
在OGNL中调用方法,可以直接通过类似Java的方法调用形式进行,也就是通过点号加上方法名称
完成方法调用,甚至可以传递参数。如下所示:
//调用Root对象的group属性中的users的size()方法
group.users.size()
//调用Root对象中group中的containsUser的方法,并将上下文环境中名为requestUser的值作为参数传入
group.containsUser(#requestUser)
3.5使用操作符进行简单计算
OGNL表达式中能使用的操作符基本与Java里的操作符一样,除了能使用+、 -、 *、 /、 ++、 -、 ==等操作符之外,
还能使用mod、in、not in等。如下所示:
2+4//加
'hello' +'world'//字符串叠加
5-3//减
9/2 //除
9 mod 2//取模
foo++//递增
foo == bar//等于判断
foo in list//是否在容器中
3.6对数组和容器的访问
OGNL表达式可以支持对数组按照数组下标的顺序进行访问。同样的方法可以用于有序的容器,如ArrayList、LinkedHashSet等。对于Map结构,OGNL支持根据键值进行访问。如下所示:
//访问Root对象的group属性中users的第一个对象的name属性的值
group.users[0].name
//访问OGNL上下文中名为sessionMap的Map对象中key为currentLoginUser的值
#sessionMap['currentLoginUser']
3.7 投影与选择
OGNL支持类似于数据库中的投影(projection)和选择(selection)功能。
投影是指选出集合中每个元素的相同属性组成新的集合,类似于关系数据库的字段操作。投影操作语法
为collection.{XXX},其中XXX是这个集合中每个元素的公共属性。
选择就是过滤满足selection条件的集合元素,类似于关系数据库的结果集操作。选择操作的语法为
collection.{X YYY},其中X是一个选择操作符,后面则是选择用的逻辑表达式。选择操作符有三种:
?选择满足条件的所有元素
^ 选择满足条件的第一个元素
$ 选择满足条件的最后一个元素
如下所示:
//返回root对象的group属性的user这个集合中所有元素的name构成的集合
group.users.{name}//新的以name为元素的集合
//返回root对象的group中users这个集合所有元素中name不为null的元素构成的集合
group.user.{? #this.name != null}//过滤后的users集合
3.8构造对象
OGNL支持直接通过表达式来构造对象。构造的方式主要包括3种:
构造List:使用{}中间使用逗号隔开元素的方式来表达列表
构造Map:使用#{},中间使用逗号隔开键值对,并使用冒号隔开key和value来构造Map
构造对象:直接使用已知对象的构造函数来构造对象
如下所示:
//构造一个List
{"green","red","blue"}
//构造一个Map
#{"key1":"value1","key2":"value2","key3":"value3"}
//构造一个对象
new java.util.URL(" http://localhost")
构造对象对于表达式语言来说是一个非常强大的功能,OGNL不仅能够直接对容器对象构造提供语法
层面的支持,还能够对任意的Java对象提供支持。这样以来就使得OGNL不仅仅具备了数据运算这一简单
的功能,同时还被赋予了潜在的逻辑计算功能。
在初步浏览了OGNL的基本操作之后,我们可以编写一个类来测试一下OGNL支持的基本操作,
特别说明:示例程序是以应用程序-打印的形式来测试的,而非单元测试。示例程序和上述例子,
有少量不同。因为例子是书上的,代码是Fans同学自己写的。
AdvancedOgnlTest.java如下所示:
package org.leiwen.struts2.ognl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ognl.Ognl;
import ognl.OgnlException;
import org.leiwen.struts2.ognl.entity.Group;
import org.leiwen.struts2.ognl.entity.User;
public class AdvancedOgnlTest {
private static Map<String, Object> context = new HashMap<String, Object>();
public static User user = new User();
// 静态初始化
static {
// 上下文
context.put("introduction", "My name is ");
// 根对象
user.setName("fans");
user.setAge(23);
user.getGroup().setName("Fans同盟");
Group group = new Group();
List<User> users = new ArrayList<User>();
for (int index = 0; index < 4; index++) {
// 构造几个name为null的User
User user = new User();
user.setAge(index + 22);
user.setId(index);
users.add(user);
}
// 构造一个name不为null的User
User hasName = new User();
hasName.setName("I hava name.");
users.add(hasName);
group.setUsers(users);
// 保存group到根对象中
user.setGroup(group);
}
/**
* @param args
* @throws OgnlException
*/
public static void main(String[] args) throws OgnlException {
testVisitRoot();
testVisitContext();
testVisitStaticVariable();
testMethodInvocation();
testOperatorSign();
testArrayAndCollection();
testProjectionAndSelection();
testConstructObject();
}
// 测试构造对象
private static void testConstructObject() throws OgnlException {
System.out.println("***********测试构造对象*************");
Object arrayList = Ognl.getValue("new java.util.ArrayList()", context,
user);
if (arrayList instanceof ArrayList) {
System.out.println("arrayList = new java.util.ArrayList()");
}
// String newMapInvalid = "#{"key1":"value1","key2":"value2"}";//语法错误
String newMap = "#{'key1':'value1','key2':'value2'}";
Object map = Ognl.getValue(newMap, context, user);
if (map instanceof Map) {
System.out.println("map size:" + ((Map) map).size());
}
}
// 测试投影和选择运算
private static void testProjectionAndSelection() throws OgnlException {
System.out.println("***********测试投影和选择运算*************");
String expression = "group.users.{name}";
// 投影运算
List<String> names = (List<String>) Ognl.getValue(expression, context,
user);
for (String name : names) {
System.out.print(name + ",\t");
}
System.out.println();
// 选择运算
String selectionExp = "group.users.{? #this.name != null}";
List<User> users = (List<User>) Ognl.getValue(selectionExp, context,
user);
for (User user : users) {
System.out.print("users(name不为null)" + ") size =" + users.size());
}
System.out.println();
}
// 测试数组和容器
private static void testArrayAndCollection() throws OgnlException {
System.out.println("************测试数组和容器************");
Object result = Ognl.getValue("group.users[1].id", context, user);
System.out.println("group.users[1].id=" + result);
}
// 测试操作符
private static void testOperatorSign() throws OgnlException {
System.out.println("**********测试操作符**************");
String expression = "5+2 -12 /4 +3/2-8%3";
Object result = Ognl.getValue(expression, context, user);
System.out.println("5+2 -12 /4 +3/2-8%3=" + result);
}
// 测试实例方法调用
private static void testMethodInvocation() throws OgnlException {
System.out.println("**********测试实例方法调用**************");
String groupName = "Hi,fans!";
context.put("groupName", groupName);
// 调用带参数的方法
Ognl.getValue("group.setName(#groupName)", context, user);
Object result = Ognl.getValue("group.getName()", context, user);
System.out.println("user.groupName=" + result);
}
// 测试静态变量和静态方法
private static void testVisitStaticVariable() throws OgnlException {
System.out.println("***********测试静态变量和静态方法*************");
Object defaultName = Ognl.getValue(
"@org.leiwen.struts2.ognl.entity.User@DEFAULT_NAME", context,
user);
Object defaultName2 = Ognl.getValue(
"@org.leiwen.struts2.ognl.entity.User@getDefaultName()",
context, user);
System.out.println("defaultName=" + defaultName + ",defaultName2="
+ defaultName2);
}
// 测试 访问上下文
private static void testVisitContext() throws OgnlException {
System.out.println("*********测试 访问上下文***************");
Object introduction = Ognl.getValue("#introduction", context, user);
System.out.println("introduction=" + introduction);
// Ognl.getValue("#introduction", context, null);
// 提示有歧义:The method getValue(Object, Map, Object) is ambiguous for the
// type Ognl
}
// 测试 访问根对象
private static void testVisitRoot() throws OgnlException {
// 打印提示信息
System.out.println("***********测试 访问根对象*************");
Object name = Ognl.getValue(Ognl.parseExpression("name"), user);
Object groupName = Ognl.getValue("group.name", user);
System.out.println("user.name=" + name + ",user.group.name="
+ groupName);
}
}
4.深入this指针
我们知道OGNL表达式是以点来进行串联的一个链式字符串表达式。而这个表达式在进行计算的时候,
从左到右,表达式每一次计算返回的结果成为一个临时的“当前对象”,并在此临时对象之上继续
进行计算,直到得到计算结果。而这个临时的“当前对象”会被存储在一个叫做this的变量中,
这个this变量就称为this指针。
在OGNL表达式中的this指针,无疑指向了当前计算的“调用者”对象的实例。如果从“调用者”这个角度来理解this指针,那么这个概念就能够被消化和理解了。需要注意的是,如果试图在表达式中使用this指针,需要在this之前加上#号,示例如下:
//返回group中users这个集合中所有age比3大的元素构成的集合
users.{? #this.age >3}
//返回group中users这个集合的大小+1的值
group.users.size().(#this+1)
//返回root对象中的group中users这个集合所有元素中name不为null的元素构成的集合
group.users.(? #this.name != null)
5.有关#符号的三种用途
在之前的表达式范例中,我们已经了解了# 操作符的几种不同用途。这是一个非常容易混淆的地方。
加在普通OGNL表达式前面,用于访问OGNL上下文中的变量
使用#{}语法动态构造Map
加载this指针之前表示对this指针的引用