上一篇博文介绍了ongl的基础语法,接下来进入实际的使用篇,我们将结合一些实际的case,来演示ognl究竟可以支撑到什么地步

在看本文之前,强烈建议先熟悉一下什么是ognl,以及其语法特点,减少阅读障碍,五分钟入门系列: 191129-Ognl 语法基础教程

I. 基本使用

1. 配置

我们选用的是java开发环境,使用maven来进行包管理,首先在pom文件中添加依赖

<!-- https://mvnrepository.com/artifact/ognl/ognl -->
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.2.11</version>
</dependency>

2. 基础使用

对于Ognl的使用,关键的地方在于获取OgnlContext, 在这个上下文中保存一些实例用来支撑ognl的语法

所以一般使用ognl的先前操作就是创建OgnlContext,然后将我们的实例扔到上下文中,接收ognl表达式,最后执行并获取结果

伪代码如下

// 构建一个OgnlContext对象
OgnlContext context = (OgnlContext) Ognl.createDefaultContext(this,
new DefaultMemberAccess(true),
new DefaultClassResolver(),
new DefaultTypeConverter()); // 设置根节点,以及初始化一些实例对象
context.setRoot(this);
context.put("实例名", obj);
... // ognl表达式执行
Object expression = Ognl.parseExpression("#a.name")
Object result = Ognl.getValue(expression, context, context.getRoot());

II. 实例演示

接下来进入实例演示,首先我们需要创建两个测试对象,用于填充OgnlContext

0. 准备

两个普通对象,一个静态类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ADemo { private String name; private Integer age; } @Data
public class PrintDemo { private String prefix; private ADemo aDemo; public void sayHello(String name, int age) {
System.out.println("name: " + name + " age: " + age);
} private void print(ADemo a) {
System.out.println(prefix + " => " + a);
} public <T> T print(String str, Class<T> clz) {
T obj = JSON.parseObject(str, clz);
System.out.println("class: " + obj);
return obj;
} public void print(String str, String clz) {
System.out.println("str2a: " + str + " clz: " + clz);
} public void print(String str, OgnlEnum ognlEnum) {
System.out.println("enum: " + str + ":" + ognlEnum);
} public void print(String str, ADemo a) {
System.out.println("obj: " + str + ":" + a);
} public void show(Class clz) {
System.out.println(clz.getName());
}
} public class StaticDemo { private static int num = (int) (Math.random() * 100); public static int showDemo(int a) {
System.out.println("static show demo: " + a);
return a;
}
} public enum OgnlEnum {
CONSOLE, FILE;
}

上面在创建OgnlContext时,有一个DefaultMemberAccess类,主要用于设置成员的访问权限,需要自己实现

@Setter
public class DefaultMemberAccess implements MemberAccess {
private boolean allowPrivateAccess = false;
private boolean allowProtectedAccess = false;
private boolean allowPackageProtectedAccess = false; public DefaultMemberAccess(boolean allowAllAccess) {
this(allowAllAccess, allowAllAccess, allowAllAccess);
} public DefaultMemberAccess(boolean allowPrivateAccess, boolean allowProtectedAccess,
boolean allowPackageProtectedAccess) {
super();
this.allowPrivateAccess = allowPrivateAccess;
this.allowProtectedAccess = allowProtectedAccess;
this.allowPackageProtectedAccess = allowPackageProtectedAccess;
} @Override
public Object setup(Map context, Object target, Member member, String propertyName) {
Object result = null; if (isAccessible(context, target, member, propertyName)) {
AccessibleObject accessible = (AccessibleObject) member; if (!accessible.isAccessible()) {
result = Boolean.TRUE;
accessible.setAccessible(true);
}
}
return result;
} @Override
public void restore(Map context, Object target, Member member, String propertyName, Object state) {
if (state != null) {
((AccessibleObject) member).setAccessible((Boolean) state);
}
} /**
* Returns true if the given member is accessible or can be made accessible by this object.
*/
@Override
public boolean isAccessible(Map context, Object target, Member member, String propertyName) {
int modifiers = member.getModifiers();
if (Modifier.isPublic(modifiers)) {
return true;
} else if (Modifier.isPrivate(modifiers)) {
return this.allowPrivateAccess;
} else if (Modifier.isProtected(modifiers)) {
return this.allowProtectedAccess;
} else {
return this.allowPackageProtectedAccess;
}
}
}

接下来创建我们的OgnlContext对象

ADemo a = new ADemo();
a.setName("yihui");
a.setAge(10); PrintDemo print = new PrintDemo();
print.setPrefix("ognl");
print.setADemo(a); // 构建一个OgnlContext对象
// 扩展,支持传入class类型的参数
OgnlContext context = (OgnlContext) Ognl.createDefaultContext(this,
new DefaultMemberAccess(true), new DefaultClassResolver(), new DefaultTypeConverter());
context.setRoot(print);
context.put("print", print);
context.put("a", a);

到此,我们的前置准备已经就绪,接下来进入实际case篇

1. 实例访问

我们的实例访问分为两类,分别为实例的方法调用;实例的属性访问

a. 实例方法调用

比如我们希望执行 print的sayHello方法,可以如下使用

Object ans = Ognl.getValue(Ognl.parseExpression("#print.sayHello(\"一灰灰blog\", 18)"), context, context.getRoot());
System.out.println("实例方法执行: " + ans);

关键点在ognl表达式: #print.sayHello("一灰灰blog", 18),其中print为实例名,对应的构建OgnlContext对象之后执行的context.put("print", print);这一行代码

输出结果:

name: 一灰灰blog age: 18
实例方法执行: null

b. 实例成员属性访问

成员属性的访问可以划分为直径获取成员属性值和设置成员属性值,对此可以如下使用

ans = Ognl.getValue(Ognl.parseExpression("#a.name=\"一灰灰Blog\""), context, context.getRoot());
System.out.println("实例属性设置: " + ans); ans = Ognl.getValue(Ognl.parseExpression("#a.name"), context, context.getRoot());
System.out.println("实例属性访问: " + ans);

输出结果

实例属性设置: 一灰灰Blog
实例属性访问: 一灰灰Blog

看到上面这个,自然会想到一个问题,可不可以访问父类的私有成员呢?

为了验证这个问题,我们新建一个实例继承自ADemo,并注册到 OgnlContext 上下文

@Data
public class BDemo extends ADemo {
private String address;
} // 注册到ognlContext
BDemo b = new BDemo();
b.setName("b name");
b.setAge(20);
b.setAddress("测试ing");
context.put("b", b); // 测试case
ans = Ognl.getValue(Ognl.parseExpression("#b.name"), context, context.getRoot());
System.out.println("实例父类属性访问:" + ans);

输出结果如下

实例父类属性访问:b name

注意:

我们这里可以直接访问私有成员,访问私有方法,访问父类的私有成员,这些都得益于我们自定义的DefaultMemberAccess,并制定了访问策略为true(即私有、保护、默认访问权限的都可以访问)

2. 静态类访问

实例成员,需要先注册到OgnlContext之后才能根据实例名来访问,但是静态类则不需要如此,默认支持当前的ClassLoader加载的所有静态类的访问姿势;下面我们进入实例演示

a. 静态类方法调用

静态类的访问需要注意的是需要传入全路径,用@开头,类与方法之间也是用@进行分割

ans = Ognl.getValue(Ognl.parseExpression("@git.hui.fix.test.ognl.bean.StaticDemo@showDemo(20)"), context,
context.getRoot());
System.out.println("静态类方法执行:" + ans);

输出结果

static show demo: 20

a. 静态类成员访问

同样我们分为成员访问和修改

ans = Ognl.getValue(Ognl.parseExpression("@git.hui.fix.test.ognl.bean.StaticDemo@num"), context,
context.getRoot());
System.out.println("静态类成员访问:" + ans); ans = Ognl.getValue(Ognl.parseExpression("@git.hui.fix.test.ognl.bean.StaticDemo@num=1314"), context,
context.getRoot());
System.out.println("静态类成员设置:" + ans);

输出结果如下

静态类方法执行:20

ognl.InappropriateExpressionException: Inappropriate OGNL expression: @git.hui.fix.test.ognl.bean.StaticDemo@num

	at ognl.SimpleNode.setValueBody(SimpleNode.java:312)
at ognl.SimpleNode.evaluateSetValueBody(SimpleNode.java:220)
at ognl.SimpleNode.setValue(SimpleNode.java:301)
at ognl.ASTAssign.getValueBody(ASTAssign.java:53)
at ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212)

直接设置静态变量,抛出了移仓,提示InappropriateExpressionException

那么静态类的成员可以修改么?这里先留一个疑问

3. 特殊传参

一般的java操作,无外乎方法调用,属性访问两种,接下来我们聚焦在方法的调用上;如果一个方法接收的参数是一些基本类型的对象,使用起来还比较简单;但是其他的场景呢?

a. class类型参数

如我们前面的PrintDemo中,有一个方法如下

public <T> T print(String str, Class<T> clz) {
T obj = JSON.parseObject(str, clz);
System.out.println("class: " + obj);
return obj;
}

如需调用上面的方法,clz参数可以怎么处理呢?

ans = Ognl.getValue(Ognl.parseExpression(
"#print.print(\"{'name':'xx', 'age': 20}\", @git.hui.fix.test.ognl.bean.ADemo@class)"), context,
context.getRoot());
System.out.println("class 参数方法执行:" + ans); // class传参
ans = Ognl.getValue(Ognl.parseExpression("#print.print(\"{'name':'haha', 'age': 10}\", #a.getClass())"),
context, context.getRoot());
System.out.println("class 参数方法执行:" + ans);

上面给出了两种方式,一个是根据已有的对象获取class,一个是直接根据静态类获取class,输出结果如下

class: ADemo(name=xx, age=20)
class 参数方法执行:ADemo(name=xx, age=20)
class: ADemo(name=haha, age=10)
class 参数方法执行:ADemo(name=haha, age=10)

b. 枚举参数

如PrintDemo中的方法, 其中第二个参数为枚举

public void print(String str, OgnlEnum ognlEnum) {
System.out.println("enum: " + str + ":" + ognlEnum);
}

结合上面的使用姿势,这个也不太难

ans = Ognl.getValue(
Ognl.parseExpression("#print.print(\"print enum\", @git.hui.fix.test.ognl.model.OgnlEnum@CONSOLE)"),
context, context.getRoot());
System.out.println("枚举参数方法执行:" + ans);

输出结果

enum: print enum:CONSOLE
枚举参数方法执行:null

c. null传参

目标方法如下

private void print(ADemo a) {
System.out.println(prefix + " => " + a);
}

因为我们需要传参为空对象,稍微有点特殊,ognl针对这个进行了支持,传参直接填null即可

ans = Ognl.getValue(Ognl.parseExpression("#print.print(null)"), context, context.getRoot());
System.out.println("null 传参:" + ans);

输出如下

ognl => null
null 传参:null

然后一个问题来了,在PrintDemo中的print方法,有多个重载的case,那么两个参数都传null,具体是哪个方法会被执行呢?

public <T> T print(String str, Class<T> clz) {
T obj = JSON.parseObject(str, clz);
System.out.println("class: " + obj);
return obj;
} public void print(String str, String clz) {
System.out.println("str2a: " + str + " clz: " + clz);
} public void print(String str, OgnlEnum ognlEnum) {
System.out.println("enum: " + str + ":" + ognlEnum);
} public void print(String str, ADemo a) {
System.out.println("obj: " + str + ":" + a);
}

通过实际的测试,第三个方法被调用了,这里面难道有啥潜规则么,然而我并没有找到

ans = Ognl.getValue(Ognl.parseExpression("#print.print(null, null)"), context, context.getRoot());
System.out.println("null 传参:" + ans);

输出

enum: null:null
null 传参:null

d. 对象传递

传参是一个POJO对象,这个时候咋整?

public void print(String str, ADemo a) {
System.out.println("obj: " + str + ":" + a);
}

现在的问题主要集中在如何构建一个Aemo对象,当做参数丢进去,通过前面的语法篇我们知道ognl是支持new来创建对象的, 如果ADemo恰好提供了全属性的构造方法,那么可以如下操作

ex = Ognl.parseExpression("#print.print(\"对象构建\", new git.hui.fix.test.ognl.bean.ADemo(\"test\", 20))");
Object ans = Ognl.getValue(ex, context, context.getRoot());
System.out.println("对象传参:" + ans);

注意观察上面的ognl表达式,其中重点在new git.hui.fix.test.ognl.bean.ADemo("test", 20)),创建对象的时候,请指定全路径名

输出结果

obj: 对象构建:ADemo(name=test, age=20)
对象传参:null

上面这个虽然实现了我们的case,但是有局限性,如果这个POJO没有全属性的构造方法,又可以怎么整?

这里就需要借助ognl语法中的链式语句了,通过new创建对象,然后设置属性,最后抛出对象

ex = Ognl.parseExpression("#print.print(\"对象构建\", (#demo=new git.hui.fix.test.ognl.bean.ADemo(), #demo.setName(\"一灰灰\"), #demo))");
ans = Ognl.getValue(ex, context, context.getRoot());
System.out.println("对象传参:" + ans);

核心语句在(#demo=new git.hui.fix.test.ognl.bean.ADemo(), #demo.setName(\"一灰灰\"), #demo),创建对象,设置属性

输出结果

obj: 对象构建:ADemo(name=一灰灰, age=null)
对象传参:null

虽说上面实现了我们的需求场景,但是这里有个坑,我们创建的这个属性会丢到OgnlContext上下文中,所以这种操作非常有可能导致我们自己创建的临时对象覆盖了原有的对象

那么有什么方法可以避免么?

这个问题先攒着,后面再叙说

e. 容器传参

在PrintDemo对象中添加方法

public void print(List<Integer> args) {
System.out.println(args);
} public void print(Map<String, Integer> args) {
System.out.println(args);
}

然后我们的访问case如下

ex = Ognl.parseExpression("#print.print({1, 3, 5})");
ans = Ognl.getValue(ex, context, context.getRoot());
System.out.println("List传参:" + ans); ex = Ognl.parseExpression("#print.print(#{\"A\": 1, \"b\": 3, \"c\": 5})");
ans = Ognl.getValue(ex, context, context.getRoot());
System.out.println("Map传参:" + ans);

输出结果

[1, 3, 5]
List传参:null
{A=1, b=3, c=5}
Map传参:null

4. 表达式执行

接下来属于另外一个范畴的case了,执行一些简单的算术操作or条件表达式

ans = Ognl.getValue(Ognl.parseExpression("1 + 3 + 4"), context, context.getRoot());
System.out.println("表达式执行: " + ans); // 阶乘
ans = Ognl.getValue(Ognl.parseExpression("#fact = :[#this<=1? 1 : #this*#fact(#this-1)], #fact(3)"), context, context.getRoot());
System.out.println("lambda执行: " + ans);

输出

表达式执行: 8
lambda执行: 6

III. 小结

鉴于篇幅过长,本篇博文将只限于使用基础的ognl能支持到什么地步,在java中使用ognl套路比较简单

1. 创建OgnlContext,并注册实例

// 构建一个OgnlContext对象
OgnlContext context = (OgnlContext) Ognl.createDefaultContext(this,
new DefaultMemberAccess(true),
new DefaultClassResolver(),
new DefaultTypeConverter()); // 设置根节点,以及初始化一些实例对象
context.setRoot(this);
context.put("实例名", obj);
...

2. 编译ognl表达式,并获取执行结果

// ognl表达式执行
Object expression = Ognl.parseExpression("#a.name")
Object result = Ognl.getValue(expression, context, context.getRoot());

3. 遗留

博文中遗留了两个问题尚未解答

  • 静态成员默认场景下不能修改,那么有办法让它支持修改么
  • 方法传参,传递对象时,通过链式创建临时对象时会缓存在OgnlContext上下文中,如何避免这种场景?

II. 其他

1. 一灰灰Bloghttps://liuyueyi.github.io/hexblog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

2. 声明

尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

3. 扫描关注

一灰灰blog

Ognl 使用实例手册的更多相关文章

  1. (转)Python实例手册

    原文地址:http://hi.baidu.com/quanzhou722/item/cf4471f8e23d3149932af2a7 实在是太好的资料了,不得不转 python实例手册 #encodi ...

  2. (转)shell实例手册

    原文地址:http://hi.baidu.com/quanzhou722/item/f4a4f3c9eb37f02d46d5c0d9 实在是太好的资料了,不得不转 shell实例手册 0说明{ 手册制 ...

  3. 转载 python实例手册

    python实例手册 #encoding:utf8# 设定编码-支持中文 0说明 手册制作: 雪松 更新日期: 2013-12-19 欢迎系统运维加入Q群: 198173206 # 加群请回答问题 请 ...

  4. 【转载】python实例手册

    今天写爬虫的时候遇到了问题,在网上不停地查找资料,居然碰到两篇好文章: 1.python实例手册   作者:没头脑的土豆 另一篇在这:shell实例手册 python实例手册 #encoding:ut ...

  5. 【转载】shell实例手册

    原文地址:shell实例手册  作者:没头脑的土豆 shell实例手册 0说明{ 手册制作: 雪松 更新日期: -- 欢迎系统运维加入Q群: 请使用"notepad++"打开此文档 ...

  6. Python实例手册

    在电脑中突然发现一个这么好的资料,雪松大神制作,不敢独享,特与大家共享.连他的广告也一并复制了吧! python实例手册 #encoding:utf8 # 设定编码-支持中文 0说明 手册制作: 雪松 ...

  7. (转) shell实例手册

    shell实例手册 1文件{ touch file              # 创建空白文件rm -rf 目录名           # 不提示删除非空目录(-r:递归删除 -f强制)dos2uni ...

  8. 告诉你:DOS系统实例手册系列专辑连载中

    DOS系统实例手册系列专辑连载中 内容提要:

  9. 使用nRF51822/nRF51422创建一个简单的BLE应用 ---入门实例手册(中文)之五

    5应用测试 需要一个USB dongle与开发板evaluation kit,并配合Master Control Panel软件,以用于测试BLE应用.前期的准备工作在<nRF51822 Eva ...

随机推荐

  1. mvc视图双下拉框联动

    html部分的代码 <tr class="trs"> <td class="item1"><div class="ite ...

  2. Python - 基本数据类型 - 第二天

    Python3 基本数据类型 Python 中的变量不需要声明.每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建. 在 Python 中,变量就是变量,它没有类型,我们所说的"类型& ...

  3. Sql语句groupBY分组后取最新一条记录的SQL

    一.问题 groupBY分组后取最新一条记录的SQL的解决方案. 二.解决方案 select Message,EventTime from PT_ChildSysAlarms as a where E ...

  4. LearnOpenGL笔记(2)三角形

    这是学习LearnOpenGL CN教程的笔记,包括我遇到的问题和我的烂笔头.文章名与网站小节对应. ------------------------------------分割线---------- ...

  5. js 数组去重总结

    es6 set ES6 提供了新的数据结构 Set.它类似于数组,但是成员的值都是唯一的,没有重复的值. let arr = [1,2,3,4,3,2,3,4,6,7,6]; let unique = ...

  6. 强化Linux 服务器的7个步骤

    这篇入门文章将向你介绍基本的 Linux 服务器安全知识.虽然主要针对 Debian/Ubuntu,但是你可以将此处介绍的所有内容应用于其他 Linux 发行版.我也鼓励你研究这份材料,并在适用的情况 ...

  7. C++中的Mat, const Mat, Mat &,Mat &, const Mat &的区别

    Mat, copy传递,不会改变外部变量的Mat. Mat &, reference传递,函数内部修改将会改变外部. const Mat, copy传递,在函数内,不会被修改,也不会影响到外部 ...

  8. 华为企业级AS111-S,比较垃圾的地方

    今天换了一个华为企业级AS111-S 路由器,比较垃圾的地方: 1. 网页管理界面是https,却用一个无效的证书,chrome直接不能访问,IE可以访问,但第一次登陆改密码的时候就出错了. 然后怎么 ...

  9. day 68

    目录 表单指令 条件指令 循环指令 分隔符 过滤器 计算属性 监听属性 表单指令 v-model="变量",变量值与表单标签的value相关 v-model可以实现数据的双向绑定, ...

  10. linux系统启动报错:[contains a file system with errors, check forced]的解决方法参考

    1.解决参考一Press enter for maintenance(or type Control-D to continue):/dev/sda3 contains a file system w ...