OGNL详解及其解析源码分析
保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/58/ )。
OGNL(Object Graph Navigation Language,中文名为:对象图导航语言)是一种功能强大的表达式语言(Expression Language,简称EL)。通过它,能够完成从集合中选取对象、读写对象的属性、调用对象和类的方法、表达式求值与判断等操作。
OGNL应用十分广泛,例如同样是获取Map中某个对象的属性,用Java语言表示出来如下:
userMap.get("user2").getName();
而使用OGNL表达式则为:
#user2.name
除了简单清晰以外,OGNL有着更高的环境适应性。我们可以将OGNL表达式应用在配置文件、XML文件等处,而只要在解析这些文件时使用OGNL进行解析即可。例如,下面所示的一段XML配置中,test
条件的判断就使用了OGNL表达式。
<select id="selectUsersByNameOrSchoolName" parameterMap="userParam01" resultType="User">
select * from `user`
<where>
<if test="name != null">
name = #{name}
</if>
<if test="schoolName != null">
AND schoolName = #{schoolName}
</if>
</where>
</select>
那Java程序如何解析OGNL中的表达式呢?
OGNL有Java工具包,只要引入它我们便可以在Java中使用OGNL的功能。这样我们就可以使用Java来解析引入了OGNL的各种文档。在介绍OGNL用法之前,我们先介绍OGNL解析时要接触的三个重要概念:
- 表达式(expression):是一个带有语法含义的字符串,是整个OGNL的核心内容。通过表达式来确定需要进行的OGNL操作。
- 根对象(root):可以理解为OGNL的被操作对象。表达式中表示的操作就是针对这个对象展开的。
- 上下文(context):整个OGNL处理时的上下文环境,该环境是一个Map对象。在进行OGNL处理之前,我们可以传入一个初始化过的上下文环境。
例如在下面代码中,Java便使用OGNL完成了根对象信息的读写操作、上下文信息的读写操作。并且,整个操作过程中也支持逻辑表达式的判断等。
User user01 = new User(1, "易哥", 18);
User user02 = new User(2, "莉莉", 15);
Map<String, User> userMap = new HashMap<>();
userMap.put("user1", user01);
userMap.put("user2", user02);
// 使用表达式读写根对象中信息的示例
// 该示例中要用到的OGNL函数:
// getValue(String expression, Object root) :对root内容执行expression中的操作,并返回结果
// 读取根对象的属性值
Integer age = (Integer) Ognl.getValue("age", userMap.get("user1"));
System.out.println("读取根对象属性,得到age:" + age);
// 设置根对象的属性值
Ognl.getValue("age = 19", userMap.get("user1"));
age = (Integer) Ognl.getValue("age", userMap.get("user1"));
System.out.println("设置根对象属性后,得到age:" + age);
// 使用表达式读写环境中信息的示例
// 该示例中要用到的OGNL函数:
// getValue(String expression, Map context, Object root) :在context环境中对root内容执行expression中的操作,并返回结果
// 读取环境中的信息
String userName2 = (String) Ognl.getValue("#user2.name", userMap, new Object());
System.out.println("读取环境中的信息,得到user2的name:" + userName2);
// 读取环境中的信息,并进行判断
Boolean result = (Boolean) Ognl.getValue("#user2.name != '丽丽'", userMap, new Object());
System.out.println("读取环境中的信息,并进行判断,得到:" + result);
// 设置环境中的信息
Ognl.getValue("#user2.name = '小华'", userMap, new Object());
String newUserName = (String) Ognl.getValue("#user2.name", userMap, new Object());
System.out.println("设置环境中的信息后,得到user2的name:" + newUserName);
上述的运行结果如下所示。
OGNL不仅可以读写信息,还能调用对象、类中的方法。例如运行下面代码:
// 调用对象方法
Integer hashCode = (Integer) Ognl.getValue("hashCode()", "yeecode");
System.out.println("对字符串对象调用hashCode方法得到:" + hashCode);
// 调用类方法
Double result = (Double)Ognl.getValue("@java.lang.Math@random()", null);
System.out.println("调用Math类中的静态方法random,得到:" + result);
便可以得到输出:
OGNL支持表达式的预编译,对表达式进行预编译后,避免了每次执行表达式前的编译工作,能够明显地提高OGNL的执行效率。
我们通过下面代码组建一个对比试验:
User user01 = new User(1, "易哥", 18);
User user02 = new User(2, "莉莉", 15);
Map<String, User> userMap = new HashMap<>();
userMap.put("user1", user01);
userMap.put("user2", user02);
String userName;
// 先对表达式解析,然后再执行可以提高效率
long time1 = new Date().getTime();
// 解析表达式
Object expressionTree = Ognl.parseExpression("#user2.name");
// 重复运行多次
for (int i = 0; i < 10000; i++) {
userName = (String) Ognl.getValue(expressionTree, userMap, new Object());
}
long time2 = new Date().getTime();
// 直接重复运行多次
for (int i = 0; i < 10000; i++) {
userName = (String) Ognl.getValue("#user2.name", userMap, new Object());
}
long time3 = new Date().getTime();
System.out.println("编译之后再执行,共花费" + (time2 - time1) + "ms");
System.out.println("不编译直接执行,共花费" + (time3 - time2) + "ms");
结果如下:
可见,如果要多次运行一个表达式,则先将其编译后再运行的执行效率更高。
到此,我们了解了OGNL的威力。我们在JSP、XML中常常见到OGNL表达式,而这些表达式的解析就是通过本小节中介绍的方式进行的。可见,OGNL是一种广泛、便捷、强大的语言。
以上内容参考自《通用源码阅读指导书——MyBatis源码详解》一书。
这是一本以MyBatis的源码为实例讲述源码阅读方法的书籍,并且附带有示例项目源码,MyBatis的全中文注解。书籍还总结了大量的编程知识和架构经验,对提升编程和架构能力十分有用。
书中还介绍了MyBatis如何完成XML中的OGNL解析,以及后续的变量替换等等。感兴趣的读者可以阅读本书。
可以访问个人知乎阅读更多文章:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。