上一篇博文介紹了ongl的基礎語法,接下來進入實際的使用篇,咱們將結合一些實際的case,來演示ognl究竟能夠支撐到什麼地步java
在看本文以前,強烈建議先熟悉一下什麼是ognl,以及其語法特色,減小閱讀障礙,五分鐘入門系列: 191129-Ognl 語法基礎教程git
<!-- more -->github
咱們選用的是java開發環境,使用maven來進行包管理,首先在pom文件中添加依賴express
<!-- https://mvnrepository.com/artifact/ognl/ognl --> <dependency> <groupId>ognl</groupId> <artifactId>ognl</artifactId> <version>3.2.11</version> </dependency>
對於Ognl的使用,關鍵的地方在於獲取OgnlContext
, 在這個上下文中保存一些實例用來支撐ognl的語法緩存
因此通常使用ognl的先前操做就是建立OgnlContext
,而後將咱們的實例扔到上下文中,接收ognl表達式,最後執行並獲取結果bash
僞代碼以下app
// 構建一個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());
接下來進入實例演示,首先咱們須要建立兩個測試對象,用於填充OgnlContextdom
兩個普通對象,一個靜態類maven
@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
類,主要用於設置成員的訪問權限,須要本身實現ide
@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篇
咱們的實例訪問分爲兩類,分別爲實例的方法調用;實例的屬性訪問
好比咱們但願執行 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
成員屬性的訪問能夠劃分爲直徑獲取成員屬性值和設置成員屬性值,對此能夠以下使用
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(即私有、保護、默認訪問權限的均可以訪問)
實例成員,須要先註冊到OgnlContext以後才能根據實例名來訪問,可是靜態類則不須要如此,默認支持當前的ClassLoader加載的全部靜態類的訪問姿式;下面咱們進入實例演示
靜態類的訪問須要注意的是須要傳入全路徑,用@
開頭,類與方法之間也是用@
進行分割
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
一樣咱們分爲成員訪問和修改
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
那麼靜態類的成員能夠修改麼?這裏先留一個疑問
通常的java操做,無外乎方法調用,屬性訪問兩種,接下來咱們聚焦在方法的調用上;若是一個方法接收的參數是一些基本類型的對象,使用起來還比較簡單;可是其餘的場景呢?
如咱們前面的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)
如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
目標方法以下
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
傳參是一個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上下文中,因此這種操做很是有可能致使咱們本身建立的臨時對象覆蓋了原有的對象
那麼有什麼方法能夠避免麼?
這個問題先攢着,後面再敘說
在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
接下來屬於另一個範疇的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
鑑於篇幅過長,本篇博文將只限於使用基礎的ognl能支持到什麼地步,在java中使用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());
博文中遺留了兩個問題還沒有解答
一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛
盡信書則不如,已上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
一灰灰blog