Java中动态规则的实现方式示例详解

网友投稿 824 2022-11-25


Java中动态规则的实现方式示例详解

背景

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

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

表达式语言(expression language)

动态语言(dynamic/script language language),如Groovy

规则引擎(rule engine)

表达式语言

Java Unified Expression Language,简称JUEL,是一种特殊用途的编程语言,主要在JavaWeb应用程序用于将表达式嵌入到web页面。Java规范制定者和Java Web领域技术专家小组制定了统一的表达式语言。JUEL最初包含在jsP2.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中的对象数据,简单示例:

Foo foo = new Foo();

foo.setName("test");

Map context = new HashMap();

context.put("foo",foo);

String expression = "foo.name == 'test'";

try {

Boolean result = (Boolean) Ognl.getValue(expression,context);

System.out.println(reshttp://ult);

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

SpEL

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

SpEL主要提供基本表达式、类相关表达式及集合相关表达式等,详细参见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);

规则引擎

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

aviator

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

使用场景包括:

规则判断及规则引擎

公式计算

动态脚本控制

集合数据 ELT 等

public class Test {

public static void main(String[] args) {

String expression = "a+(b-c)>100";

// 编译表达式

Expression compiledExp = AviatorEvaluator.compile(expression);

Map 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定义规则:

@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.ymlexample:

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

触发规则:

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 engineand complex event processing (CEP) engine for Java and the JVM Platform.

定义规则:

import com.lrq.wechatDemo.domain.User // 导入类

dialect "mvel"

rule "age" // 规则名,唯一

when

$user : User(age<15 || age>60) //规则的条件部分

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都是比较重的规则引擎,详见其官方文档。

动态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的例子:

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禁用问题等,相关问题可以参考深入学习java中的Groovy 和 Scala 类。

参考:

Java各种规则引擎:https://jianshu.com/p/41ea7a43093c

Java中使用动态代码:http://brucefengnju.github.io/post/dynamic-code-in-java/

量身定制规则引擎,适应多变业务场景:https://my.oschina.net/yygh/blog/616808?p=1

总结


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:在idea中将java项目中的单个类打包成jar包操作
下一篇:Java8生成时间方式及格式化时间的方法实例
相关文章

 发表评论

暂时没有评论,来抢沙发吧~