Java中OGNL表达式语言的使用详解
作者:骑个小蜗牛
OGNL 介绍
OGNL(Object Graph Navigation Language)表达式语言是一种用于Java语言的表达式语言,专门用于在对象图中进行导航和操作。
在Java中,OGNL可以让开发人员以简洁的方式访问和操作Java对象的属性、调用对象的方法,执行算术和逻辑运算,以及处理集合和数组等操作。
OGNL的语法简洁明了,可以方便地用于在Java开发中进行动态属性存取、列表和Map操作、函数和方法调用等,为开发人员提供了便利的数据操作手段。
OGNL 使用场景
常见使用场景:
- ognl
- Fastjson
- ognl
使用ognl包需要引入依赖
maven依赖:
<dependency> <groupId>ognl</groupId> <artifactId>ognl</artifactId> <version>3.3.4</version> </dependency>
- 主要功能
访问对象属性:
使用点号(.)来访问对象的属性(也可设置对象的属性)。例如:person.name 表示访问 person 对象的 name 属性。
调用对象方法
使用at符号(@)来调用对象的方法。例如:@java.lang.Math@random() 表示调用 Math 类的 random 方法。
访问集合和数组:
使用方括号([])来访问集合或数组中的元素。例如:myList[0] 表示访问 myList 集合中的第一个元素。
赋值和表达式计算:
支持变量赋值和基本的算术、逻辑运算符。例如:age > 18 && age < 60 表示对 age 变量进行逻辑判断。
条件表达式:
支持条件运算符,如三元运算符 condition ? true : false。
对象引用:
使用(#)符号来引用对象。例如:#person.name 表示引用 person 对象的 name 属性。
内置对象:
OGNL中有一些内置对象,如 #context(上下文对象)、#root(根对象)、#this(当前对象)等,可以方便地用于表达式中的引用和操作。
- 注意事项
- 当表达式expression中的属性不存在时,获取或设置值会报错
- Ognl类的主要方法
applyExpressionMaxLength
:设置Ognl 表达式的最大允许长度限制
void applyExpressionMaxLength(Integer expressionMaxLength)
- expressionMaxLength:表达式的最大允许长度
freezeExpressionMaxLength
:冻结 Ognl 表达式的最大允许长度的限制
- void freezeExpressionMaxLength()
thawExpressionMaxLength
:解除 Ognl 表达式的最大允许长度的限制
- void thawExpressionMaxLength()
createDefaultContext
:创建一个默认的 Ognl 上下文对象
Map createDefaultContext(Object root)
root
:根对象
Map createDefaultContext(Object root, ClassResolver classResolver)
root
:根对象classResolver
:类解析器
Map createDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter)
root
:根对象classResolver
:类解析器converter
:类型转换器
Map createDefaultContext(Object root, MemberAccess memberAccess)
root
:根对象memberAccess
:成员访问对象
Map createDefaultContext(Object root, MemberAccess memberAccess, ClassResolver classResolver, TypeConverter converter)
root
:根对象memberAccess
:成员访问对象classResolver
:类解析器converter
:类型转换器
addDefaultContext
:向默认 Ognl 上下文中添加一个自定义的上下文对象
Map addDefaultContext(Object root, Map context)
root
:根对象context
:上下文对象
Map addDefaultContext(Object root, ClassResolver classResolver, Map context)
root
:根对象classResolver
:类解析器context
:上下文对象
Map addDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter, Map context)
root
:根对象classResolver
:类解析器converter
:类型转换器context
:上下文对象
Map addDefaultContext(Object root, MemberAccess memberAccess, ClassResolver classResolver, TypeConverter converter, Map context)
root
:根对象memberAccess
:成员访问对象classResolver
:类解析器converter
:类型转换器context
:上下文对象
compileExpression
:将字符串表达式编译为 Ognl 表达式对象
Node compileExpression(OgnlContext context, Object root, String expression)
context
:上下文对象expression
:字符串表达式
parseExpression
:将字符串表达式解析为 Ognl 表达式对象
Object parseExpression(String expression)
expression:字符串表达式
getLastEvaluation
:从上下文中获取最后一个评估(Evaluation)对象
Evaluation getLastEvaluation(Map context)
- context:上下文对象
setRoot
:设置 Ognl 表达式的根对象
void setRoot(Map context, Object root)
context
:上下文对象root
:根对象
getRoot
:获取 Ognl 表达式的根对象
Object getRoot(Map context)
context
:上下文对象
isConstant
:判断给定的表达式是否是常量
isConstant(Object tree)
tree
:Ognl 表达式对象
isConstant(Object tree, Map context)
tree
:Ognl 表达式对象context
:上下文对象
isConstant(String expression)
expression
:字符串表达式
isConstant(String expression, Map context)
expression
:字符串表达式context
:上下文对象
isSimpleProperty
:判断给定的表达式是否是简单的属性
isSimpleProperty(String expression)
expression
:字符串表达式
isSimpleProperty(String expression, Map context)
expression
:字符串表达式context
:上下文对象
isSimpleProperty(Object tree)
tree
:Ognl 表达式对象
isSimpleProperty(Object tree, Map context)
tree
:Ognl 表达式对象context
:上下文对象
isSimpleNavigationChain
:判断给定的表达式是否是简单的导航链
isSimpleNavigationChain(String expression)
expression
:字符串表达式
isSimpleNavigationChain(String expression, Map context)
expression
:字符串表达式context
:上下文对象
isSimpleNavigationChain(Object tree)
tree
:Ognl 表达式对象
isSimpleNavigationChain(Object tree, Map context)
tree
:Ognl 表达式对象context
:上下文对象
setValue
:将给定的值设置到根对象的属性中
void setValue(String expression, Object root, Object value)
根据表达式定位到根对象的属性,并将给定的值设置为该属性的值
expression
:字符串表达式root
:根对象value
:要设置的值
void setValue(String expression, Map context, Object root, Object value)
使用给定的上下文对象和根对象,根据表达式定位到根对象的属性,并将给定的值设置为该属性的值
expression
:字符串表达式context
:上下文对象root
:根对象value
:要设置的值
void setValue(Object tree, Object root, Object value)
使用表达式对象定位到根对象的属性,并将给定的值设置为该属性的值
tree
:Ognl 表达式对象root
:根对象value
:要设置的值
void setValue(Object tree, Map context, Object root, Object value)
使用给定的上下文对象和根对象,根据表达式对象定位到根对象的属性,并将给定的值设置为该属性的值
tree
:Ognl 表达式对象context
:上下文对象root
:根对象value
:要设置的值
void setValue(ExpressionAccessor expression, OgnlContext context, Object root, Object value)
使用给定的表达式访问器、上下文对象和根对象,定位到根对象的属性,并将给定的值设置为该属性的值
expression
:Ognl 表达式访问器context
:Ognl 表达式访问器root
:根对象value
:要设置的值
getValue
:通过给定的表达式从根对象中获取属性值
Object getValue(String expression, Object root)
根据表达式定位到根对象的属性,并返回该属性的值
expression
:字符串表达式root
:根对象
Object getValue(String expression, Object root, Class resultType)
根据表达式定位到根对象的属性,并将返回的值转换为指定的结果类型
expression
:字符串表达式root
:根对象resultType
:结果类型
Object getValue(String expression, Map context, Object root)
使用给定的上下文对象和根对象,根据表达式定位到根对象的属性,并返回该属性的值
expression
:字符串表达式context
:上下文对象root
:根对象
Object getValue(String expression, Map context, Object root, Class resultType)
使用给定的上下文对象和根对象,根据表达式定位到根对象的属性,并将返回的值转换为指定的结果类型
expression
:字符串表达式context
:上下文对象root
:根对象resultType
:结果类型
Object getValue(Object tree, Object root)
使用表达式对象定位到根对象的属性,并返回该属性的值
tree
:Ognl 表达式对象root
:根对象
Object getValue(Object tree, Object root, Class resultType)
使用表达式对象定位到根对象的属性,并将返回的值转换为指定的结果类型
tree
:Ognl 表达式对象root
:根对象resultType
:结果类型
Object getValue(Object tree, Map context, Object root)
使用给定的上下文对象和根对象,根据表达式对象定位到根对象的属性,并返回该属性的值
tree
:Ognl 表达式对象context
:上下文对象root
:根对象
Object getValue(Object tree, Map context, Object root, Class resultType)
使用给定的上下文对象和根对象,根据表达式对象定位到根对象的属性,并将返回的值转换为指定的结果类型
tree
:Ognl 表达式对象context
:上下文对象root
:根对象resultType
:结果类型
Object getValue(ExpressionAccessor expression, OgnlContext context, Object root)
使用给定的表达式访问器、上下文对象和根对象,定位到根对象的属性,并返回该属性的值
expression
:Ognl 表达式访问器context
:Ognl 上下文对象root
:根对象
Object getValue(ExpressionAccessor expression, OgnlContext context, Object root, Class resultType)
使用给定的表达式访问器、上下文对象和根对象,定位到根对象的属性,并将返回的值转换为指定的结果类型
expression
:Ognl 表达式访问器context
:Ognl 上下文对象root
:根对象resultType
:结果类型
- 设置值
1.使用字符串表达式方式设置值
public static void main(String[] args) throws Exception { // 创建一个根对象 Person person = new Person("John", 25); // 创建一个 Ognl 上下文 OgnlContext context = new OgnlContext(); context.setRoot(person); // 使用字符串表达式方式设置值 Ognl.setValue("name", context, person, "Alice"); Ognl.setValue("age", context, person, 30); // 输出更新后的属性值 System.out.println(person.getName()); // 输出:Alice System.out.println(person.getAge()); // 输出:30 }
2.使用 Ognl 表达式对象方式设置值
public static void main(String[] args) throws Exception { // 创建一个根对象 Person person = new Person("John", 25); // 创建一个 Ognl 表达式对象 Object tree = Ognl.parseExpression("name"); // 使用表达式对象方式设置值 Ognl.setValue(tree, person, "Alice"); // 输出更新后的属性值 System.out.println(person.getName()); // 输出:Alice }
3.使用 Ognl 表达式访问器和 Ognl 上下文对象方式设置根值
public static void main(String[] args) throws OgnlException { // 创建一个根对象 Person person = new Person("John", 25); // 创建一个 Ognl 上下文 OgnlContext context = new OgnlContext(); context.setRoot(person); // 创建一个 Ognl 表达式访问器 ExpressionAccessor expression = OgnlRuntime.getPropertyAccessor(Person.class); // 使用表达式访问器和上下文对象设置值 Object tree = Ognl.parseExpression("age"); Ognl.setValue(expression, context, person, tree, 30); // 输出更新后的属性值 System.out.println(person.getAge()); // 输出:30 }
- 获取值
使用字符串表达式方式获取值
public static void main(String[] args) throws OgnlException { // 创建一个根对象 Person person = new Person("Alice", 30); // 使用字符串表达式方式获取值 Object nameValue = Ognl.getValue("name", person); System.out.println(nameValue); // 输出:Alice // 使用字符串表达式方式获取值 Object ageValue = Ognl.getValue("age", person); System.out.println(ageValue); // 输出:30 }
使用字符串表达式及Ognl 上下文对象方式获取值
public static void main(String[] args) throws OgnlException { // 创建一个根对象 Person person = new Person("Alice", 30); // 创建一个 Ognl 上下文 OgnlContext context = new OgnlContext(); context.setRoot(person); // 使用带上下文参数的字符串表达式方式获取值 Object nameValue = Ognl.getValue("name", context, person); System.out.println(nameValue); // 输出:Alice // 使用带上下文参数的字符串表达式方式获取值 Object ageValue = Ognl.getValue("age", context, person); System.out.println(ageValue); // 输出:30 }
使用 Ognl 表达式对象方式获取值
public static void main(String[] args) throws OgnlException { // 创建一个根对象 Person person = new Person("Alice", 30); // 创建一个 Ognl 表达式对象 Object nameTree = Ognl.parseExpression("name"); // 使用表达式对象方式获取值 Object nameValue = Ognl.getValue(nameTree, person); System.out.println(nameValue); // 输出:Alice }
使用 Ognl 表达式对象及Ognl 上下文对象方式获取值
public static void main(String[] args) throws OgnlException { // 创建一个根对象 Person person = new Person("Alice", 30); // 创建一个 Ognl 上下文 OgnlContext context = new OgnlContext(); context.setRoot(person); // 创建一个 Ognl 表达式对象 Object nameTree = Ognl.parseExpression("name"); // 使用表达式对象方式获取值 Object nameValue = Ognl.getValue(nameTree, context, person); System.out.println(nameValue); // 输出:Alice }
使用自定义的表达式访问器和上下文对象方式获取值
public static void main(String[] args) throws OgnlException { // 创建一个根对象 Person person = new Person("Alice", 30); // 创建一个 Ognl 上下文 OgnlContext context = new OgnlContext(); context.setRoot(person); // 创建一个自定义的表达式访问器 ExpressionAccessor expression = OgnlRuntime.getPropertyAccessor(Person.class); // 使用自定义的表达式访问器和上下文对象获取值 Object ageValue = Ognl.getValue(expression, context, person, "age"); System.out.println(ageValue); // 输出:30 }
- 使用示例
maven依赖
<dependency> <groupId>ognl</groupId> <artifactId>ognl</artifactId> <version>3.3.4</version> </dependency>
实现源码
public class OGNLTest { @Data @NoArgsConstructor @AllArgsConstructor public static class User { public Integer userId; public String userName; public Sex sex; } @Data @NoArgsConstructor @AllArgsConstructor public static class Sex { public Integer sexCode; public String sexName; } public static void main(String[] args) throws OgnlException { // 访问对象属性 User user = new User(111111, "哈哈", new Sex(1, "男")); Integer userId = (Integer) Ognl.getValue("userId", user); System.out.println(userId);// 111111 Integer sexCode = (Integer) Ognl.getValue("sex.sexCode", user); System.out.println(sexCode);// 1 // 调用对象方法 Double random = (Double) Ognl.getValue("@java.lang.Math@random()", null); System.out.println(random);// 0.9562367273068916 // 访问集合和数组 List<User> list = Stream.of(user).collect(Collectors.toList()); String sexName = (String) Ognl.getValue("[0].sex.sexName", list); System.out.println(sexName);// 男 // 赋值和表达式计算 boolean isMan = (Boolean) Ognl.getValue("sex.sexCode == 1", user); System.out.println(isMan);// true // 条件表达式 String sex = (String) Ognl.getValue("sex.sexCode == 1 ? \"男\" : \"女\"", user); System.out.println(sex);// 男 // 对象引用 Map<String,Object> context = new HashMap<>(); context.put("aaa", new Sex(2, "女")); Integer sexCode1 = (Integer) Ognl.getValue("#aaa.sexCode", context, user); System.out.println(sexCode1);// 2 // 内置对象 Integer sexCode2 = (Integer) Ognl.getValue("#root.sex.sexCode", user); System.out.println(sexCode2);// 1 Integer sexCode3 = (Integer) Ognl.getValue("#this.sex.sexCode", user); System.out.println(sexCode3);// 1 } }
- Mybatis
MyBatis 早期版本确实使用过 OGNL 作为表达式语言,特别是在动态 SQL 处理中。在 MyBatis 的 XML 配置文件中,OGNL 被用来处理一些复杂的表达式,例如在 、 等标签中进行条件判断。
然而,由于 OGNL 存在一些性能和安全问题,MyBatis 在后续版本中逐步淘汰了 OGNL,转而使用更为简洁和安全的 OGNL 变种 或 Java 原生表达式。目前,OGNL 仍然可以通过自定义插件使用,但它已不再是 MyBatis 的核心功能。
Java原生表达式更简单、更安全,并且能够直接利用 Java 语言的语法和功能进行条件判断、循环等操作。
Java原生表达式的使用
在 MyBatis 中,Java原生表达式主要用于动态 SQL 的编写,特别是在 、、 等标签中,用于控制 SQL 的生成逻辑。
基本语法:
在 MyBatis 中,Java原生表达式通常出现在如下标签的属性中:
- :用于条件判断
- :类似于 switch-case 的控制结构
- :用于循环生成 SQL 部分
- :用于动态 SQL 中的空白字符去除
常用的Java 原生表达式:
- 条件表达式
- 三元运算符
- 逻辑运算符
- 逻辑运算(&&, ||, !)
- 数学运算(+, -, *, /)
- 类型转换(通过 (type) 强制转换)
- 方法调用(如 string.equals())
使用示例:
(1) 标签中的 Java 原生表达式:
标签用于根据某个条件动态地生成 SQL 片段。我们可以在 标签中直接使用 Java 表达式进行条件判断。
<select id="selectUser" resultType="User"> SELECT * FROM users <where> <if test="username != null"> AND username = #{username} </if> <if test="age != null"> AND age = #{age} </if> </where> </select>
- test=“username != null” 中的 username != null 就是一个 Java 原生表达式。
- 在这里,test 属性值就是 Java 表达式的条件判断语句,如果表达式结果为 true,那么 中的 SQL 片段才会被加入到最终生成的 SQL 中。
(2) 标签中的 Java 原生表达式:
标签类似于 Java 中的 switch-case 语法,用于根据多个条件选择生成不同的 SQL 片段。
<select id="selectUser" resultType="User"> SELECT * FROM users <where> <choose> <when test="age != null"> AND age = #{age} </when> <when test="username != null"> AND username = #{username} </when> <otherwise> AND status = 'active' </otherwise> </choose> </where> </select>
- 允许在多个 中根据不同条件选择合适的 SQL 片段。
- 每个 中的 test 属性也可以使用 Java 表达式。
(3) 标签中的 Java 原生表达式:
标签用于循环生成 SQL 片段,通常用于处理数组、集合或列表类型的参数。
<select id="selectUsersByIds" resultType="User"> SELECT * FROM users <where> <foreach collection="ids" item="id" open="AND id IN (" separator="," close=")"> #{id} </foreach> </where> </select>
- collection=“ids”:表示传入的参数集合或数组。
- 在 标签中,我们可以使用 Java 表达式来处理循环逻辑和参数拼接。
(4) 使用 Java 原生三元运算符:
在 MyBatis 中,Java原生表达式也支持使用 Java 的三元运算符来简化条件判断。
<select id="selectUser" resultType="User"> SELECT * FROM users WHERE status = <if test="status != null"> #{status} </if> <if test="status == null"> 'active' </if> </select>
使用三元运算符来替代多重条件判断,改为:
<select id="selectUser" resultType="User"> SELECT * FROM users WHERE status = #{status != null ? status : 'active'} </select>
(5) 标签中的 Java 原生表达式:
标签用于对生成的 SQL 语句进行前后字符的去除(如去掉 AND 或 OR 等)。
<select id="selectUser" resultType="User"> SELECT * FROM users <trim prefix="WHERE" prefixOverrides="AND |OR "> <if test="status != null">AND status = #{status}</if> <if test="age != null">AND age = #{age}</if> </trim> </select>
- Fastjson
- JSONPath类的主要方法
Object
eval
(String json, String path)
通过给定的 JSON 数据和 JSONPath 表达式,返回匹配该表达式的结果对象JSONArray
extract
(Object json, String path)
根据指定的 JSONPath 表达式,从 JSON 对象中提取匹配的 JSON 数组。返回一个 JSONArray 对象boolean
contains
(Object json, String path)
判断 JSON 数据中是否包含匹配指定 JSONPath 表达式的内容boolean
containsValue
(Object json, String path, Object value)
判断 JSON 对象中匹配 JSONPath 表达式的内容是否包含指定的值int
size
(Object json, String path)
计算 JSONPath 表达式匹配的内容的大小,并返回匹配结果的个数Set<?>
keySet
(Object json, String path)
返回 JSONPath 表达式匹配的所有键值的 Set 集合void
arrayAdd
(Object rootObject, String path, Object… values)
在 JSON 对象中的指定位置添加一个或多个元素void
remove
(Object json, String path)
根据指定的 JSONPath 表达式,从 JSON 对象中移除匹配路径的内容void
set
(Object json, String path, Object value)
根据指定的 JSONPath 表达式,在 JSON 对象中设置匹配路径的值为指定的 valueJSONPath
compile
(String path)
编译 JSONPath 表达式,返回一个对应的 JSONPath 对象这个方法可以提高后续对同一个 JSONPath 表达式的操作性能。
Object
read
(String json, String path)
读取 JSON 数据中指定 JSONPath 表达式的值List
paths
(Object json, String path)
获取 JSON 对象中匹配指定 JSONPath 表达式的所有路径,并返回一个 JSONPath 列表void
setArrayItem
(Object json, String path, Object value)
根据指定的 JSONPath 表达式,在 JSON 数组中设置匹配路径的元素为指定的 valuevoid
removeArrayItem
(Object json, String path, int index)
根据指定的 JSONPath 表达式,从 JSON 数组中移除指定索引位置的元素
- 主要功能
- 设置JSON对象中的属性值
- 获取JSON对象中的属性值
- JSONPath的优势
- 设置JSON对象中的属性值时,如果属性不存在,也会自动创建
- 获取JSON对象中的属性值是,如果属性不存在,不会报错空指针,会返回null
- 使用示例
public class OGNLTest { public static void main(String[] args) { // 设置JSON对象中的属性值 JSONObject jsonObject = new JSONObject(); JSONPath.set(jsonObject, "$.role.roleName", "管理员"); JSONPath.set(jsonObject, "$.user.userName", "Joker"); System.out.println(JSON.toJSONString(jsonObject));// {"role":{"roleName":"管理员"},"user":{"userName":"Joker"}} JSONObject jsonObject1 = JSON.parseObject("{\"role\":{\"roleName\":\"管理员\"},\"user\":{\"userName\":\"Joker\"}}"); JSONPath.set(jsonObject1, "$.user.userId", 18); System.out.println(JSON.toJSONString(jsonObject1));// {"role":{"roleName":"管理员"},"user":{"userName":"Joker","userId":18}} // 获取JSON对象中的属性值 System.out.println(JSONPath.eval(jsonObject, "$.role.roleName"));// 管理员 System.out.println(JSONPath.eval(jsonObject, "$.user.userName"));// Joker System.out.println(JSONPath.eval(jsonObject, "$.user.age"));// null System.out.println(JSONPath.eval(jsonObject, "$.user.sex.setName"));// null } }
Spring不选择OGNL的原因
Spring 官方推荐的表达式语言是Spring Expression Language (SpEL) ,它提供了更强大的功能和更多的特性。
为什么Spring使用SpEL替代了OGNL了呢:
OGNL 在早期版本中被发现存在一些安全漏洞,尤其是当其用于动态求值时,可能会导致恶意代码执行。因此,在使用 OGNL 时要特别小心,尽量避免在不信任的输入中执行 OGNL 表达式,或使用一些安全措施来过滤不安全的表达式。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。