OGNL详解及其解析源码分析

标签: Java, 协议与规范

保留所有版权,请引用而不是转载本文(原文地址 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解析时要接触的三个重要概念:

例如在下面代码中,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),欢迎关注。

作者书籍推荐