Java中動(dòng)態(tài)規(guī)則的實(shí)現(xiàn)方式示例詳解
背景
業(yè)務(wù)系統(tǒng)在應(yīng)用過(guò)程中,有時(shí)候要處理“經(jīng)常變化”的部分,這部分需求可能是“業(yè)務(wù)規(guī)則”,也可能是“不同的數(shù)據(jù)處理邏輯”,這部分動(dòng)態(tài)規(guī)則的問(wèn)題,往往需要可配置,并對(duì)性能和實(shí)時(shí)性有一定要求。
Java不是解決動(dòng)態(tài)層問(wèn)題的理想語(yǔ)言,在實(shí)踐中發(fā)現(xiàn)主要有以下幾種方式可以實(shí)現(xiàn):
- 表達(dá)式語(yǔ)言(expression language)
- 動(dòng)態(tài)語(yǔ)言(dynamic/script language language),如Groovy
- 規(guī)則引擎(rule engine)
表達(dá)式語(yǔ)言
Java Unified Expression Language,簡(jiǎn)稱JUEL,是一種特殊用途的編程語(yǔ)言,主要在JavaWeb應(yīng)用程序用于將表達(dá)式嵌入到web頁(yè)面。Java規(guī)范制定者和Java Web領(lǐng)域技術(shù)專家小組制定了統(tǒng)一的表達(dá)式語(yǔ)言。JUEL最初包含在JSP2.1規(guī)范JSR-245中,后來(lái)成為Java EE 7的一部分,改在JSR-341中定義。
主要的開源實(shí)現(xiàn)有:OGNL,MVEL,SpEL,JUEL,Java Expression Language (JEXL),JEval,Jakarta JXPath等。
這里主要介紹在實(shí)踐中使用較多的MVEL、OGNL和SpEL。
OGNL(Object Graph Navigation Library)
在Struts 2 的標(biāo)簽庫(kù)中都是使用OGNL表達(dá)式訪問(wèn)ApplicationContext中的對(duì)象數(shù)據(jù),簡(jiǎn)單示例:
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項(xiàng)目的表達(dá)式計(jì)算器(expression evaluator),相比最初的OGNL、JEXL和JUEL等項(xiàng)目,而它具有遠(yuǎn)超它們的性能、功能和易用性 - 特別是集成方面。它不會(huì)嘗試另一種JVM語(yǔ)言,而是著重解決嵌入式腳本的問(wèn)題。
MVEL主要使用在Drools,是Drools規(guī)則引擎不可分割的一部分。
MVEL語(yǔ)法較為豐富,不僅包含了基本的屬性表達(dá)式,布爾表達(dá)式,變量復(fù)制和方法調(diào)用,還支持函數(shù)定義,詳情參見(jiàn)MVEL Language Guide。
MVEL在執(zhí)行語(yǔ)言時(shí)主要有解釋模式(Interpreted Mode)和編譯模式(Compiled Mode)兩種:
解釋模式(Interpreted Mode)是一個(gè)無(wú)狀態(tài)的,動(dòng)態(tài)解釋執(zhí)行,不需要負(fù)載表達(dá)式就可以執(zhí)行相應(yīng)的腳本。編譯模式(Compiled Mode)需要在緩存中產(chǎn)生一個(gè)完全規(guī)范化表達(dá)式之后再執(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á)式語(yǔ)言)是一個(gè)支持查詢和操作運(yùn)行時(shí)對(duì)象導(dǎo)航圖功能的強(qiáng)大的表達(dá)式語(yǔ)言。 它的語(yǔ)法類似于傳統(tǒng)EL,但提供額外的功能,最出色的就是函數(shù)調(diào)用和簡(jiǎn)單字符串的模板函數(shù)。SpEL類似于Struts2x中使用的OGNL表達(dá)式語(yǔ)言,能在運(yùn)行時(shí)構(gòu)建復(fù)雜表達(dá)式、存取對(duì)象圖屬性、對(duì)象方法調(diào)用等等,并且能與Spring功能完美整合,如能用來(lái)配置Bean定義。
SpEL主要提供基本表達(dá)式、類相關(guān)表達(dá)式及集合相關(guān)表達(dá)式等,詳細(xì)參見(jiàn)Spring 表達(dá)式語(yǔ)言 (SpEL)。
類似與OGNL,SpEL具有expression(表達(dá)式),Parser(解析器),EvaluationContext(上下文)等基本概念;類似與MVEL,SpEl也提供了解釋模式和編譯模式兩種運(yùn)行模式。
//解釋器模式 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
是一門高性能、輕量級(jí)寄宿于 JVM 之上的腳本語(yǔ)言。
使用場(chǎng)景包括:
- 規(guī)則判斷及規(guī)則引擎
- 公式計(jì)算
- 動(dòng)態(tài)腳本控制
- 集合數(shù)據(jù) ELT 等
public class Test { public static void main(String[] args) { String expression = "a+(b-c)>100"; // 編譯表達(dá)式 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í)行表達(dá)式 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();
支持使用表達(dá)式語(yǔ)言(MVEL/SpEL)來(lái)定義規(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.
一個(gè)例子:
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ī)則引擎,詳見(jiàn)其官方文檔。
動(dòng)態(tài)JVM語(yǔ)言
Groovy除了Gradle上的廣泛應(yīng)用之外,另一個(gè)大范圍的使用應(yīng)該就是結(jié)合Java使用動(dòng)態(tài)代碼了。Groovy的語(yǔ)法與Java非常相似,以至于多數(shù)的Java代碼也是正確的Groovy代碼。Groovy代碼動(dòng)態(tài)的被編譯器轉(zhuǎn)換成Java字節(jié)碼。由于其運(yùn)行在JVM上的特性,Groovy可以使用其他Java語(yǔ)言編寫的庫(kù)。
Groovy可以看作給Java靜態(tài)世界補(bǔ)充動(dòng)態(tài)能力的語(yǔ)言,同時(shí)Groovy已經(jīng)實(shí)現(xiàn)了java不具備的語(yǔ)言特性:
- 函數(shù)字面值;
- 對(duì)集合的一等支持;
- 對(duì)正則表達(dá)式的一等支持;
- 對(duì)xml的一等支持;
Groovy作為基于JVM的語(yǔ)言,與表達(dá)式語(yǔ)言存在語(yǔ)言級(jí)的不同,因此在語(yǔ)法上比表達(dá)還是語(yǔ)言更靈活。Java在調(diào)用Groovy時(shí),都需要將Groovy代碼編譯成Class文件。
Groovy 可以采用GroovyClassLoader、GroovyShell、GroovyScriptEngine和JSR223等方式與Java語(yǔ)言集成。
一個(gè)使用GroovyClassLoader動(dòng)態(tài)對(duì)json對(duì)象進(jìn)行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代碼都會(huì)將Groovy編譯成Class文件,因此在調(diào)用過(guò)程中會(huì)出現(xiàn)JVM級(jí)別的問(wèn)題。如使用GroovyShell的parse方法導(dǎo)致perm區(qū)爆滿的問(wèn)題,使用GroovyClassLoader加載機(jī)制導(dǎo)致頻繁gc問(wèn)題和CodeCache用滿,導(dǎo)致JIT禁用問(wèn)題等,相關(guān)問(wèn)題可以參考深入學(xué)習(xí)java中的Groovy 和 Scala 類。
參考:
Java各種規(guī)則引擎:https://www.jianshu.com/p/41ea7a43093c
Java中使用動(dòng)態(tài)代碼:http://brucefengnju.github.io/post/dynamic-code-in-java/
量身定制規(guī)則引擎,適應(yīng)多變業(yè)務(wù)場(chǎng)景:https://my.oschina.net/yygh/blog/616808?p=1
總結(jié)
到此這篇關(guān)于Java中動(dòng)態(tài)規(guī)則的實(shí)現(xiàn)方式的文章就介紹到這了,更多相關(guān)Java動(dòng)態(tài)規(guī)則內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Idea+Jconsole實(shí)現(xiàn)線程監(jiān)控步驟
這篇文章主要介紹了基于Idea+Jconsole實(shí)現(xiàn)線程監(jiān)控功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04解決springboot集成rocketmq關(guān)于tag的坑
這篇文章主要介紹了解決springboot集成rocketmq關(guān)于tag的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08IntelliJ IDEA 無(wú)法正常使用SVN的問(wèn)題和完美解決辦法
這篇文章主要介紹了IntelliJ IDEA 無(wú)法正常使用SVN的問(wèn)題和解決辦法,本文給大家分享完美解決方案,通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08MyBatis學(xué)習(xí)教程(五)-實(shí)現(xiàn)關(guān)聯(lián)表查詢方法詳解
本文給大家介紹mybatis關(guān)聯(lián)查詢,包括一對(duì)一關(guān)聯(lián)查詢,一對(duì)多關(guān)聯(lián)查詢,代碼簡(jiǎn)單易懂,感興趣的朋友一起學(xué)習(xí)吧2016-05-05java實(shí)現(xiàn)字符串轉(zhuǎn)String數(shù)組的方法示例
這篇文章主要介紹了java實(shí)現(xiàn)字符串轉(zhuǎn)String數(shù)組的方法,涉及java字符串的遍歷、分割、轉(zhuǎn)換等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10SpringBoot整合iText7導(dǎo)出PDF及性能優(yōu)化方式
在SpringBoot項(xiàng)目中整合iText7庫(kù)以導(dǎo)出PDF文件,不僅能夠滿足報(bào)告生成需求,而且可以處理復(fù)雜的文檔布局與樣式,整合步驟包括添加Maven依賴、編寫PDF生成代碼,性能優(yōu)化方面,建議使用流式處理、緩存樣式與字體、優(yōu)化HTML/CSS結(jié)構(gòu)、采用異步處理2024-09-09寧可用Lombok也不把成員設(shè)置為public原理解析
這篇文章主要為大家介紹了寧可用Lombok也不把成員設(shè)置為public原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03幾種常見(jiàn)mybatis分頁(yè)的實(shí)現(xiàn)方式
這篇文章主要介紹了幾種常見(jiàn)mybatis分頁(yè)的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06SpringBoot實(shí)現(xiàn)自定義Starter的步驟詳解
在SpringBoot中,Starter是一種特殊的依賴,它可以幫助我們快速地集成一些常用的功能,例如數(shù)據(jù)庫(kù)連接、消息隊(duì)列、Web框架等。在本文中,我們將介紹如何使用Spring Boot實(shí)現(xiàn)自定義Starter,需要的朋友可以參考下2023-06-06