Java中动态规则的实现方式示例详解
背景
业务系统在应用过程中,有时候要处理“经常变化”的部分,这部分需求可能是“业务规则”,也可能是“不同的数据处理逻辑”,这部分动态规则的问题,往往需要可配置,并对性能和实时性有一定要求。
Java不是解决动态层问题的理想语言,在实践中发现主要有以下几种方式可以实现:
- 表达式语言(expressionlanguage)
- 动态语言(dynamic/scriptlanguagelanguage),如Groovy
- 规则引擎(ruleengine)
表达式语言
JavaUnifiedExpressionLanguage,简称JUEL,是一种特殊用途的编程语言,主要在JavaWeb应用程序用于将表达式嵌入到web页面。Java规范制定者和JavaWeb领域技术专家小组制定了统一的表达式语言。JUEL最初包含在JSP2.1规范JSR-245中,后来成为JavaEE7的一部分,改在JSR-341中定义。
主要的开源实现有:OGNL,MVEL,SpEL,JUEL,JavaExpressionLanguage(JEXL),JEval,JakartaJXPath等。
这里主要介绍在实践中使用较多的MVEL、OGNL和SpEL。
OGNL(ObjectGraphNavigationLibrary)
在Struts2的标签库中都是使用OGNL表达式访问ApplicationContext中的对象数据,简单示例:
Foofoo=newFoo();
foo.setName("test");
Mapcontext=newHashMap();
context.put("foo",foo);
Stringexpression="foo.name=='test'";
try{
Booleanresult=(Boolean)Ognl.getValue(expression,context);
System.out.println(result);
}catch(OgnlExceptione){
e.printStackTrace();
}
MVEL
MVEL最初作为MikeBrock创建的Valhalla项目的表达式计算器(expressionevaluator),相比最初的OGNL、JEXL和JUEL等项目,而它具有远超它们的性能、功能和易用性-特别是集成方面。它不会尝试另一种JVM语言,而是着重解决嵌入式脚本的问题。
MVEL主要使用在Drools,是Drools规则引擎不可分割的一部分。
MVEL语法较为丰富,不仅包含了基本的属性表达式,布尔表达式,变量复制和方法调用,还支持函数定义,详情参见MVELLanguageGuide。
MVEL在执行语言时主要有解释模式(InterpretedMode)和编译模式(CompiledMode)两种:
解释模式(InterpretedMode)是一个无状态的,动态解释执行,不需要负载表达式就可以执行相应的脚本。编译模式(CompiledMode)需要在缓存中产生一个完全规范化表达式之后再执行。
//解释模式
Foofoo=newFoo();
foo.setName("test");
Mapcontext=newHashMap();
Stringexpression="foo.name=='test'";
VariableResolverFactoryfunctionFactory=newMapVariableResolverFactory(context);
context.put("foo",foo);
Booleanresult=(Boolean)MVEL.eval(expression,functionFactory);
System.out.println(result);
//编译模式
Foofoo=newFoo();foo.setName("test");
Mapcontext=newHashMap();
Stringexpression="foo.name=='test'";
VariableResolverFactoryfunctionFactory=newMapVariableResolverFactory(context);context.put("foo",foo);
SerializablecompileExpression=MVEL.compileExpression(expression);
SpEL
SpEl(Spring表达式语言)是一个支持查询和操作运行时对象导航图功能的强大的表达式语言。它的语法类似于传统EL,但提供额外的功能,最出色的就是函数调用和简单字符串的模板函数。SpEL类似于Struts2x中使用的OGNL表达式语言,能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等,并且能与Spring功能完美整合,如能用来配置Bean定义。
SpEL主要提供基本表达式、类相关表达式及集合相关表达式等,详细参见Spring表达式语言(SpEL)。
类似与OGNL,SpEL具有expression(表达式),Parser(解析器),EvaluationContext(上下文)等基本概念;类似与MVEL,SpEl也提供了解释模式和编译模式两种运行模式。
//解释器模式
Foofoo=newFoo();
foo.setName("test");
//Turnon:
//-autonullreferenceinitialization
//-autocollectiongrowing
SpelParserConfigurationconfig=newSpelParserConfiguration(true,true);
ExpressionParserparser=newSpelExpressionParser(config);
StringexpressionStr="#foo.name=='test'";
StandardEvaluationContextcontext=newStandardEvaluationContext();
context.setVariable("foo",foo);
Expressionexpression=parser.parseExpression(expressionStr);
Booleanresult=expression.getValue(context,Boolean.class);
//编译模式
config=newSpelParserConfiguration(SpelCompilerMode.IMMEDIATE,RunSpel.class.getClassLoader());
parser=newSpelExpressionParser(config);
context=newStandardEvaluationContext();
context.setVariable("foo",foo);
expression=parser.parseExpression(expressionStr);
result=expression.getValue(context,Boolean.class);
规则引擎
一些规则引擎(ruleengine):aviator,easy-rules,drools,esper
aviator
AviatorScript是一门高性能、轻量级寄宿于JVM之上的脚本语言。
使用场景包括:
- 规则判断及规则引擎
- 公式计算
- 动态脚本控制
- 集合数据ELT等
publicclassTest{
publicstaticvoidmain(String[]args){
Stringexpression="a+(b-c)>100";
//编译表达式
ExpressioncompiledExp=AviatorEvaluator.compile(expression);
Mapenv=newHashMap<>();
env.put("a",100.3);
env.put("b",45);
env.put("c",-199.100);
//执行表达式
Booleanresult=(Boolean)compiledExp.execute(env);
System.out.println(result);
}
}
easy-rules
EasyRulesisaJavarulesengine。
使用POJO定义规则:
@Rule(name="weatherrule",description="ifitrainsthentakeanumbrella")
publicclassWeatherRule{
@Condition
publicbooleanitRains(@Fact("rain")booleanrain){
returnrain;
}
@Action
publicvoidtakeAnUmbrella(){
System.out.println("Itrains,takeanumbrella!");
}
}
RuleweatherRule=newRuleBuilder()
.name("weatherrule")
.description("ifitrainsthentakeanumbrella")
.when(facts->facts.get("rain").equals(true))
.then(facts->System.out.println("Itrains,takeanumbrella!"))
.build();
支持使用表达式语言(MVEL/SpEL)来定义规则:
weather-rule.ymlexample:
name:"weatherrule"
description:"ifitrainsthentakeanumbrella"
condition:"rain==true"
actions:
-"System.out.println(\"Itrains,takeanumbrella!\");"
MVELRuleFactoryruleFactory=newMVELRuleFactory(newYamlRuleDefinitionReader());
RuleweatherRule=ruleFactory.createRule(newFileReader("weather-rule.yml"));
触发规则:
publicclassTest{
publicstaticvoidmain(String[]args){
//definefacts
Factsfacts=newFacts();
facts.put("rain",true);
//definerules
RuleweatherRule=...
Rulesrules=newRules();
rules.register(weatherRule);
//firerulesonknownfacts
RulesEnginerulesEngine=newDefaultRulesEngine();
rulesEngine.fire(rules,facts);
}
}
drools
Anopensourceruleengine,DMNengineandcomplexeventprocessing(CEP)engineforJavaandtheJVMPlatform.
定义规则:
importcom.lrq.wechatDemo.domain.User//导入类
dialect"mvel"
rule"age"//规则名,唯一
when
$user:User(age<15||age>60)//规则的条件部分
then
System.out.println("年龄不符合要求!");
end
参考例子:
publicclassTestUser{
privatestaticKieContainercontainer=null;
privateKieSessionstatefulKieSession=null;
@Test
publicvoidtest(){
KieServiceskieServices=KieServices.Factory.get();
container=kieServices.getKieClasspathContainer();
statefulKieSession=container.newKieSession("myAgeSession");
Useruser=newUser("duvalyang",12);
statefulKieSession.insert(user);
statefulKieSession.fireAllRules();
statefulKieSession.dispose();
}
}
esper
Esperisacomponentforcomplexeventprocessing(CEP),streamingSQLandeventseriesanalysis,availableforJavaasEsper,andfor.NETasNEsper.
一个例子:
publicclassTest{
publicstaticvoidmain(String[]args)throwsInterruptedException{
EPServiceProviderepService=EPServiceProviderManager.getDefaultProvider();
EPAdministratoradmin=epService.getEPAdministrator();
Stringproduct=Apple.class.getName();
Stringepl="selectavg(price)from"+product+".win:length_batch(3)";
EPStatementstate=admin.createEPL(epl);
state.addListener(newAppleListener());
EPRuntimeruntime=epService.getEPRuntime();
Appleapple1=newApple();
apple1.setId(1);
apple1.setPrice(5);
runtime.sendEvent(apple1);
Appleapple2=newApple();
apple2.setId(2);
apple2.setPrice(2);
runtime.sendEvent(apple2);
Appleapple3=newApple();
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的例子:
publicclassGroovyFilterimplementsFilter{
privatestaticStringtemplate=""+
"packagecom.alarm.eagle.filter;"+
"importcom.fasterxml.jackson.databind.node.ObjectNode;"+
"defmatch(ObjectNodeo){[exp]}";
privatestaticStringmethod="match";
privateStringfilterExp;
privatetransientGroovyObjectfilterObj;
publicGroovyFilter(StringfilterExp)throwsException{
ClassLoaderparent=Thread.currentThread().getContextClassLoader();
GroovyClassLoaderclassLoader=newGroovyClassLoader(parent);
Classclazz=classLoader.parseClass(template.replace("[exp]",filterExp));
filterObj=(GroovyObject)clazz.newInstance();
}
publicbooleanfilter(ObjectNodeobjectNode){
return(boolean)filterObj.invokeMethod(method,objectNode);
}
}
Java每次调用Groovy代码都会将Groovy编译成Class文件,因此在调用过程中会出现JVM级别的问题。如使用GroovyShell的parse方法导致perm区爆满的问题,使用GroovyClassLoader加载机制导致频繁gc问题和CodeCache用满,导致JIT禁用问题等,相关问题可以参考深入学习java中的Groovy和Scala类。
参考:
Java各种规则引擎:https://www.jianshu.com/p/41ea7a43093c
Java中使用动态代码:http://brucefengnju.github.io/post/dynamic-code-in-java/
量身定制规则引擎,适应多变业务场景:https://my.oschina.net/yygh/blog/616808?p=1
总结
到此这篇关于Java中动态规则的实现方式的文章就介绍到这了,更多相关Java动态规则内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。