熬了7天,总结出来了Java中动态规则的实现方式

背景

业务系统在应用过程中,有时候要处理“经常变化”的部分,这部分需求可能是“业务规则”,也可能是“不同的数据处理逻辑”,这部分动态规则的问题,往往需要可配置,并对性能和实时性有一定要求。

Java不是解决动态层问题的理想语言,在实践中发现主要有以下几种方式可以实现:

  • 表达式语言(expression language)
  • 动态语言(dynamic/script language language),如Groovy
  • 规则引擎(rule engine)

表达式语言

Java Unified Expression Language,简称JUEL,是一种特殊用途的编程语言,主要在Java Web应用程序用于将表达式嵌入到web页面。Java规范制定者和Java Web领域技术专家小组制定了统一的表达式语言。JUEL最初包含在JSP 2.1规范JSR-245中,后来成为Java EE 7的一部分,改在JSR-341中定义。

主要的开源实现有:OGNL ,MVEL ,SpEL,JUEL,Java Expression Language (JEXL),JEval,Jakarta JXPath 等。

这里主要介绍在实践中使用较多的MVEL、OGNL和SpEL。

OGNL(Object Graph Navigation Library)

在Struts 2 的标签库中都是使用OGNL表达式访问ApplicationContext中的对象数据,简单示例:

 

ini

复制代码

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创建的 Valhalla项目的表达式计算器(expression evaluator),相比最初的OGNL、JEXL和JUEL等项目,而它具有远超它们的性能、功能和易用性 - 特别是集成方面。它不会尝试另一种JVM语言,而是着重解决嵌入式脚本的问题。

MVEL主要使用在Drools,是Drools规则引擎不可分割的一部分。

MVEL语法较为丰富,不仅包含了基本的属性表达式,布尔表达式,变量复制和方法调用,还支持函数定义,详情参见MVEL Language Guide 。

MVEL在执行语言时主要有解释模式(Interpreted Mode)和编译模式(Compiled Mode )两种:

  • 解释模式(Interpreted Mode)是一个无状态的,动态解释执行,不需要负载表达式就可以执行相应的脚本。

  • 编译模式(Compiled Mode)需要在缓存中产生一个完全规范化表达式之后再执行。

    //解释模式 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); Boolean result = (Boolean) MVEL.executeExpression(compileExpression, context, functionFactory);

SpEL

SpEl(Spring表达式语言)是一个支持查询和操作运行时对象导航图功能的强大的表达式语言。 它的语法类似于传统EL,但提供额外的功能,最出色的就是函数调用和简单字符串的模板函数。SpEL类似于Struts2x中使用的OGNL表达式语言,能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等,并且能与Spring功能完美整合,如能用来配置Bean定义。

SpEL主要提供基本表达式、类相关表达式及集合相关表达式等,详细参见Spring 表达式语言 (SpEL) 。

类似与OGNL,SpEL具有expression(表达式),Parser(解析器),EvaluationContext(上下文)等基本概念;类似于MVEL,SpEl也提供了解释模式和编译模式两种运行模式。

 

ini

复制代码

//解释器模式 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);

规则引擎

一些规则引擎(rule engine):aviator,easy-rules,drools,esper, siddhi

aviator

AviatorScript 是一门高性能、轻量级寄宿于 JVM 之上的脚本语言。

使用场景包括:

  1. 规则判断及规则引擎

  2. 公式计算

  3. 动态脚本控制

  4. 集合数据 ELT 等

    public class Test { public static void main(String[] args) { String expression = "a+(b-c)>100"; // 编译表达式 Expression compiledExp = AviatorEvaluator.compile(expression);

     ini 

    复制代码

    Map<String, Object> env = new HashMap<>(); env.put("a", 100.3); env.put("b", 45); env.put("c", -199.100); // 执行表达式 Boolean result = (Boolean) compiledExp.execute(env); System.out.println(result);

    } }

easy-rules

Easy Rules is a Java rules engine。

使用POJO定义规则:

 

typescript

复制代码

@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)来定义规则:

weather-rule.yml example:

 

vbnet

复制代码

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"));

触发规则:

 

java

复制代码

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); } }

drools

An open source rule engine, DMN engine and complex event processing (CEP) engine for Java and the JVM Platform.

定义规则:

 

kotlin

复制代码

import com.lrq.wechatDemo.domain.User // 导入类 dialect "mvel" rule "age" // 规则名,唯一 when $user : User(age<15 || age>60) //规则的条件部分 then System.out.println("年龄不符合要求!"); end

参考例子:

 

ini

复制代码

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(); } }

drools是比较重的规则引擎,有自己的状态存储,详见其官方文档。

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.

一个例子:

 

ini

复制代码

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); } }

siddhi

Siddhi is a cloud native

Streaming

and

Complex Event Processing

engine that understands Streaming SQL queries in order to capture events from diverse data sources, process them, detect complex conditions, and publish output to various endpoints in real time.

For example:

 

typescript

复制代码

package io.siddhi.sample; import io.siddhi.core.SiddhiAppRuntime; import io.siddhi.core.SiddhiManager; import io.siddhi.core.event.Event; import io.siddhi.core.stream.input.InputHandler; import io.siddhi.core.stream.output.StreamCallback; import io.siddhi.core.util.EventPrinter; /** * The sample demonstrate how to use Siddhi within another Java program. * This sample contains a simple filter query. */ public class SimpleFilterSample { public static void main(String[] args) throws InterruptedException { // Create Siddhi Manager SiddhiManager siddhiManager = new SiddhiManager(); //Siddhi Application String siddhiApp = "" + "define stream StockStream (symbol string, price float, volume long); " + "" + "@info(name = 'query1') " + "from StockStream[volume < 150] " + "select symbol, price " + "insert into OutputStream;"; //Generate runtime SiddhiAppRuntime siddhiAppRuntime = siddhiManager.createSiddhiAppRuntime(siddhiApp); //Adding callback to retrieve output events from stream siddhiAppRuntime.addCallback("OutputStream", new StreamCallback() { @Override public void receive(Event[] events) { EventPrinter.print(events); //To convert and print event as a map //EventPrinter.print(toMap(events)); } }); //Get InputHandler to push events into Siddhi InputHandler inputHandler = siddhiAppRuntime.getInputHandler("StockStream"); //Start processing siddhiAppRuntime.start(); //Sending events to Siddhi inputHandler.send(new Object[]{"IBM", 700f, 100L}); inputHandler.send(new Object[]{"WSO2", 60.5f, 200L}); inputHandler.send(new Object[]{"GOOG", 50f, 30L}); inputHandler.send(new Object[]{"IBM", 76.6f, 400L}); inputHandler.send(new Object[]{"WSO2", 45.6f, 50L}); Thread.sleep(500); //Shutdown runtime siddhiAppRuntime.shutdown(); //Shutdown Siddhi Manager siddhiManager.shutdown(); } }

esper和siddhi都是streaming process,支持CEP和SQL,详见其官方文档。

动态JVM语言

Groovy

Groovy除了Gradle 上的广泛应用之外,另一个大范围的使用应该就是结合Java使用动态代码了。Groovy的语法与Java非常相似,以至于多数的Java代码也是正确的Groovy代码。Groovy代码动态的被编译器转换成Java字节码。由于其运行在JVM上的特性,Groovy可以使用其他Java语言编写的库。

Groovy可以看作给Java静态世界补充动态能力的语言,同时Groovy已经实现了java不具备的语言特性:

  • 函数字面值;
  • 对集合的一等支持;
  • 对正则表达式的一等支持;
  • 对xml的一等支持;

Groovy作为基于JVM的语言,与表达式语言存在语言级的不同,因此在语法上比表达还是语言更灵活。Java在调用Groovy时,都需要将Groovy代码编译成Class文件。

Groovy 可以采用GroovyClassLoader、GroovyShell、GroovyScriptEngine和JSR223 等方式与Java语言集成。

一个使用GroovyClassLoader动态对json对象进行filter的例子:

 

java

复制代码

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每次调用Groovy代码都会将Groovy编译成Class文件,因此在调用过程中会出现JVM级别的问题。如使用GroovyShell的parse方法导致perm区爆满的问题,使用GroovyClassLoader加载机制导致频繁gc问题和CodeCache用满,导致JIT禁用问题等,相关问题可以参考Groovy与Java集成常见的坑 。

最后

感谢大家看到这里,文章有不足,欢迎大家指出;最后欢迎大家关注微信公众号【Java程序员聚集地】获取最新技术知识。

作者:程序员麦冬
链接:https://juejin.cn/post/6874446545316478990
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值