Java中動態(tài)規(guī)則的實現(xiàn)方式示例詳解
背景
業(yè)務(wù)系統(tǒng)在應(yīng)用過程中,有時候要處理“經(jīng)常變化”的部分,這部分需求可能是“業(yè)務(wù)規(guī)則”,也可能是“不同的數(shù)據(jù)處理邏輯”,這部分動態(tài)規(guī)則的問題,往往需要可配置,并對性能和實時性有一定要求。
Java不是解決動態(tài)層問題的理想語言,在實踐中發(fā)現(xiàn)主要有以下幾種方式可以實現(xiàn):
- 表達式語言(expression language)
- 動態(tài)語言(dynamic/script language language),如Groovy
- 規(guī)則引擎(rule engine)
表達式語言
Java Unified Expression Language,簡稱JUEL,是一種特殊用途的編程語言,主要在JavaWeb應(yīng)用程序用于將表達式嵌入到web頁面。Java規(guī)范制定者和Java Web領(lǐng)域技術(shù)專家小組制定了統(tǒng)一的表達式語言。JUEL最初包含在JSP2.1規(guī)范JSR-245中,后來成為Java EE 7的一部分,改在JSR-341中定義。
主要的開源實現(xiàn)有:OGNL,MVEL,SpEL,JUEL,Java Expression Language (JEXL),JEval,Jakarta JXPath等。
這里主要介紹在實踐中使用較多的MVEL、OGNL和SpEL。
OGNL(Object Graph Navigation Library)
在Struts 2 的標(biāo)簽庫中都是使用OGNL表達式訪問ApplicationContext中的對象數(shù)據(jù),簡單示例:
Foo foo = new Foo(); foo.setName("test"); Map<String, Object> context = new HashMap<String, Object>(); context.put("foo",foo); String expression = "foo.name == 'test'"; try { Boolean result = (Boolean) Ognl.getValue(expression,context); System.out.println(result); } catch (OgnlException e) { e.printStackTrace(); }
MVEL
MVEL最初作為Mike Brock創(chuàng)建的 Valhalla項目的表達式計算器(expression evaluator),相比最初的OGNL、JEXL和JUEL等項目,而它具有遠超它們的性能、功能和易用性 - 特別是集成方面。它不會嘗試另一種JVM語言,而是著重解決嵌入式腳本的問題。
MVEL主要使用在Drools,是Drools規(guī)則引擎不可分割的一部分。
MVEL語法較為豐富,不僅包含了基本的屬性表達式,布爾表達式,變量復(fù)制和方法調(diào)用,還支持函數(shù)定義,詳情參見MVEL Language Guide。
MVEL在執(zhí)行語言時主要有解釋模式(Interpreted Mode)和編譯模式(Compiled Mode)兩種:
解釋模式(Interpreted Mode)是一個無狀態(tài)的,動態(tài)解釋執(zhí)行,不需要負載表達式就可以執(zhí)行相應(yīng)的腳本。編譯模式(Compiled Mode)需要在緩存中產(chǎn)生一個完全規(guī)范化表達式之后再執(zhí)行。
//解釋模式 Foo foo = new Foo(); foo.setName("test"); Map context = new HashMap(); String expression = "foo.name == 'test'"; VariableResolverFactory functionFactory = new MapVariableResolverFactory(context); context.put("foo",foo); Boolean result = (Boolean) MVEL.eval(expression,functionFactory); System.out.println(result); //編譯模式 Foo foo = new Foo();foo.setName("test"); Map context = new HashMap(); String expression = "foo.name == 'test'"; VariableResolverFactory functionFactory = new MapVariableResolverFactory(context);context.put("foo",foo); Serializable compileExpression = MVEL.compileExpression(expression);
SpEL
SpEl(Spring表達式語言)是一個支持查詢和操作運行時對象導(dǎo)航圖功能的強大的表達式語言。 它的語法類似于傳統(tǒng)EL,但提供額外的功能,最出色的就是函數(shù)調(diào)用和簡單字符串的模板函數(shù)。SpEL類似于Struts2x中使用的OGNL表達式語言,能在運行時構(gòu)建復(fù)雜表達式、存取對象圖屬性、對象方法調(diào)用等等,并且能與Spring功能完美整合,如能用來配置Bean定義。
SpEL主要提供基本表達式、類相關(guān)表達式及集合相關(guān)表達式等,詳細參見Spring 表達式語言 (SpEL)。
類似與OGNL,SpEL具有expression(表達式),Parser(解析器),EvaluationContext(上下文)等基本概念;類似與MVEL,SpEl也提供了解釋模式和編譯模式兩種運行模式。
//解釋器模式 Foo foo = new Foo(); foo.setName("test"); // Turn on: // - auto null reference initialization // - auto collection growing SpelParserConfiguration config = new SpelParserConfiguration(true,true); ExpressionParser parser = new SpelExpressionParser(config); String expressionStr = "#foo.name == 'test'"; StandardEvaluationContext context = new StandardEvaluationContext(); context.setVariable("foo",foo); Expression expression = parser.parseExpression(expressionStr); Boolean result = expression.getValue(context,Boolean.class); //編譯模式 config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, RunSpel.class.getClassLoader()); parser = new SpelExpressionParser(config); context = new StandardEvaluationContext(); context.setVariable("foo",foo); expression = parser.parseExpression(expressionStr); result = expression.getValue(context,Boolean.class);
規(guī)則引擎
一些規(guī)則引擎(rule engine):aviator,easy-rules,drools,esper
AviatorScript
是一門高性能、輕量級寄宿于 JVM 之上的腳本語言。
使用場景包括:
- 規(guī)則判斷及規(guī)則引擎
- 公式計算
- 動態(tài)腳本控制
- 集合數(shù)據(jù) ELT 等
public class Test { public static void main(String[] args) { String expression = "a+(b-c)>100"; // 編譯表達式 Expression compiledExp = AviatorEvaluator.compile(expression); Map<String, Object> env = new HashMap<>(); env.put("a", 100.3); env.put("b", 45); env.put("c", -199.100); // 執(zhí)行表達式 Boolean result = (Boolean) compiledExp.execute(env); System.out.println(result); } }
Easy Rules is a Java rules engine。
使用POJO定義規(guī)則:
@Rule(name = "weather rule", description = "if it rains then take an umbrella") public class WeatherRule { @Condition public boolean itRains(@Fact("rain") boolean rain) { return rain; } @Action public void takeAnUmbrella() { System.out.println("It rains, take an umbrella!"); } } Rule weatherRule = new RuleBuilder() .name("weather rule") .description("if it rains then take an umbrella") .when(facts -> facts.get("rain").equals(true)) .then(facts -> System.out.println("It rains, take an umbrella!")) .build();
支持使用表達式語言(MVEL/SpEL)來定義規(guī)則:
weather-rule.yml
example:
name: "weather rule" description: "if it rains then take an umbrella" condition: "rain == true" actions: - "System.out.println(\"It rains, take an umbrella!\");" MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader()); Rule weatherRule = ruleFactory.createRule(new FileReader("weather-rule.yml"));
觸發(fā)規(guī)則:
public class Test { public static void main(String[] args) { // define facts Facts facts = new Facts(); facts.put("rain", true); // define rules Rule weatherRule = ... Rules rules = new Rules(); rules.register(weatherRule); // fire rules on known facts RulesEngine rulesEngine = new DefaultRulesEngine(); rulesEngine.fire(rules, facts); } }
An open source rule engine,DMN engineand complex event processing (CEP) engine for Java and the JVM Platform.
定義規(guī)則:
import com.lrq.wechatDemo.domain.User // 導(dǎo)入類 dialect "mvel" rule "age" // 規(guī)則名,唯一 when $user : User(age<15 || age>60) //規(guī)則的條件部分 then System.out.println("年齡不符合要求!"); end
參考例子:
public class TestUser { private static KieContainer container = null; private KieSession statefulKieSession = null; @Test public void test(){ KieServices kieServices = KieServices.Factory.get(); container = kieServices.getKieClasspathContainer(); statefulKieSession = container.newKieSession("myAgeSession"); User user = new User("duval yang",12); statefulKieSession.insert(user); statefulKieSession.fireAllRules(); statefulKieSession.dispose(); } }
esper
Esper is a component for complex event processing (CEP), streaming SQL and event series analysis, available for Java as Esper, and for .NET as NEsper.
一個例子:
public class Test { public static void main(String[] args) throws InterruptedException { EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider(); EPAdministrator admin = epService.getEPAdministrator(); String product = Apple.class.getName(); String epl = "select avg(price) from " + product + ".win:length_batch(3)"; EPStatement state = admin.createEPL(epl); state.addListener(new AppleListener()); EPRuntime runtime = epService.getEPRuntime(); Apple apple1 = new Apple(); apple1.setId(1); apple1.setPrice(5); runtime.sendEvent(apple1); Apple apple2 = new Apple(); apple2.setId(2); apple2.setPrice(2); runtime.sendEvent(apple2); Apple apple3 = new Apple(); apple3.setId(3); apple3.setPrice(5); runtime.sendEvent(apple3); } }
drools和esper都是比較重的規(guī)則引擎,詳見其官方文檔。
動態(tài)JVM語言
Groovy除了Gradle上的廣泛應(yīng)用之外,另一個大范圍的使用應(yīng)該就是結(jié)合Java使用動態(tài)代碼了。Groovy的語法與Java非常相似,以至于多數(shù)的Java代碼也是正確的Groovy代碼。Groovy代碼動態(tài)的被編譯器轉(zhuǎn)換成Java字節(jié)碼。由于其運行在JVM上的特性,Groovy可以使用其他Java語言編寫的庫。
Groovy可以看作給Java靜態(tài)世界補充動態(tài)能力的語言,同時Groovy已經(jīng)實現(xiàn)了java不具備的語言特性:
- 函數(shù)字面值;
- 對集合的一等支持;
- 對正則表達式的一等支持;
- 對xml的一等支持;
Groovy作為基于JVM的語言,與表達式語言存在語言級的不同,因此在語法上比表達還是語言更靈活。Java在調(diào)用Groovy時,都需要將Groovy代碼編譯成Class文件。
Groovy 可以采用GroovyClassLoader、GroovyShell、GroovyScriptEngine和JSR223等方式與Java語言集成。
一個使用GroovyClassLoader動態(tài)對json對象進行filter的例子:
public class GroovyFilter implements Filter { private static String template = "" + "package com.alarm.eagle.filter;" + "import com.fasterxml.jackson.databind.node.ObjectNode;" + "def match(ObjectNode o){[exp]}"; private static String method = "match"; private String filterExp; private transient GroovyObject filterObj; public GroovyFilter(String filterExp) throws Exception { ClassLoader parent = Thread.currentThread().getContextClassLoader(); GroovyClassLoader classLoader = new GroovyClassLoader(parent); Class clazz = classLoader.parseClass(template.replace("[exp]", filterExp)); filterObj = (GroovyObject)clazz.newInstance(); } public boolean filter(ObjectNode objectNode) { return (boolean)filterObj.invokeMethod(method, objectNode); } }
Java每次調(diào)用Groovy代碼都會將Groovy編譯成Class文件,因此在調(diào)用過程中會出現(xiàn)JVM級別的問題。如使用GroovyShell的parse方法導(dǎo)致perm區(qū)爆滿的問題,使用GroovyClassLoader加載機制導(dǎo)致頻繁gc問題和CodeCache用滿,導(dǎo)致JIT禁用問題等,相關(guān)問題可以參考深入學(xué)習(xí)java中的Groovy 和 Scala 類。
參考:
Java各種規(guī)則引擎:https://www.jianshu.com/p/41ea7a43093c
Java中使用動態(tài)代碼:http://brucefengnju.github.io/post/dynamic-code-in-java/
量身定制規(guī)則引擎,適應(yīng)多變業(yè)務(wù)場景:https://my.oschina.net/yygh/blog/616808?p=1
總結(jié)
到此這篇關(guān)于Java中動態(tài)規(guī)則的實現(xiàn)方式的文章就介紹到這了,更多相關(guān)Java動態(tài)規(guī)則內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Idea+Jconsole實現(xiàn)線程監(jiān)控步驟
這篇文章主要介紹了基于Idea+Jconsole實現(xiàn)線程監(jiān)控功能,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-04-04解決springboot集成rocketmq關(guān)于tag的坑
這篇文章主要介紹了解決springboot集成rocketmq關(guān)于tag的坑,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08IntelliJ IDEA 無法正常使用SVN的問題和完美解決辦法
這篇文章主要介紹了IntelliJ IDEA 無法正常使用SVN的問題和解決辦法,本文給大家分享完美解決方案,通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08MyBatis學(xué)習(xí)教程(五)-實現(xiàn)關(guān)聯(lián)表查詢方法詳解
本文給大家介紹mybatis關(guān)聯(lián)查詢,包括一對一關(guān)聯(lián)查詢,一對多關(guān)聯(lián)查詢,代碼簡單易懂,感興趣的朋友一起學(xué)習(xí)吧2016-05-05java實現(xiàn)字符串轉(zhuǎn)String數(shù)組的方法示例
這篇文章主要介紹了java實現(xiàn)字符串轉(zhuǎn)String數(shù)組的方法,涉及java字符串的遍歷、分割、轉(zhuǎn)換等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10SpringBoot整合iText7導(dǎo)出PDF及性能優(yōu)化方式
在SpringBoot項目中整合iText7庫以導(dǎo)出PDF文件,不僅能夠滿足報告生成需求,而且可以處理復(fù)雜的文檔布局與樣式,整合步驟包括添加Maven依賴、編寫PDF生成代碼,性能優(yōu)化方面,建議使用流式處理、緩存樣式與字體、優(yōu)化HTML/CSS結(jié)構(gòu)、采用異步處理2024-09-09寧可用Lombok也不把成員設(shè)置為public原理解析
這篇文章主要為大家介紹了寧可用Lombok也不把成員設(shè)置為public原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03SpringBoot實現(xiàn)自定義Starter的步驟詳解
在SpringBoot中,Starter是一種特殊的依賴,它可以幫助我們快速地集成一些常用的功能,例如數(shù)據(jù)庫連接、消息隊列、Web框架等。在本文中,我們將介紹如何使用Spring Boot實現(xiàn)自定義Starter,需要的朋友可以參考下2023-06-06