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中的对象数据,简单示例:
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也提供了解释模式和编译模式两种运行模式。
//解释器模式
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
AviatorScript
是一门高性能、轻量级寄宿于 JVM 之上的脚本语言。
使用场景包括:
- 规则判断及规则引擎
- 公式计算
- 动态脚本控制
- 集合数据 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); // 执行表达式
Boolean result = (Boolean) compiledExp.execute(env);
System.out.println(result);
}
}
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.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"));
触发规则:
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 engine and 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除了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禁用问题等,相关问题可以参考Groovy与Java集成常见的坑 。
参考:
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中创建对象的几种方式
Java中创建对象的五种方式: 作为java开发者,我们每天创建很多对象,但是我们通常使用依赖注入的方式管理系统,比如:Spring去创建对象,然而这里有很多创建对象的方法:使用New关键字.使用Cl ...
- Java中反射的三种常用方式
Java中反射的三种常用方式 package com.xiaohao.test; public class Test{ public static void main(String[] args) t ...
- java中动态反射
java中动态反射能达到的效果和python的语法糖很像,能够截获方法的实现,在真实方法调用之前和之后进行修改,甚至能够用自己的实现进行特别的替代,也可以用其实现面向切片的部分功能.动态代理可以方便实 ...
- Java进阶(四十二)Java中多线程使用匿名内部类的方式进行创建3种方式
Java中多线程使用匿名内部类的方式进行创建3种方式 package cn.edu.ujn.demo; // 匿名内部类的格式: public class ThreadDemo { public st ...
- Java中创建对象的五种方式
我们总是讨论没有对象就去new一个对象,创建对象的方式在我这里变成了根深蒂固的new方式创建,但是其实创建对象的方式还是有很多种的,不单单有new方式创建对象,还有使用反射机制创建对象,使用clone ...
- Java中的静态代理实现方式
1.编写一个接口类 如:Subject package com.neusoft.pattern.staticProxy; /** * <p>Title:</p> * <p ...
- 【转】Java中创建对象的5种方式
Java中创建对象的5种方式 作为Java开发者,我们每天创建很多对象,但我们通常使用依赖管理系统,比如Spring去创建对象.然而这里有很多创建对象的方法,我们会在这篇文章中学到. Java中有 ...
- JAVA中循环遍历list有三种方式
转自:https://blog.csdn.net/changjizhi1212/article/details/81036509JAVA中循环遍历list有三种方式for循环.增强for循环(也就是常 ...
- Java中正负数的存储方式-正码 反码和补码
Java中正负数的存储方式-正码 反码和补码 正码 我们以int 为例,一个int占用4个byte,32bits 0 存在内存上为 00000000 00000000 00000000 0000000 ...
随机推荐
- Python核心编程(第3版)PDF高清晰完整中文版|网盘链接附提取码下载|
一.书籍简介<Python核心编程(第3版)>是经典畅销图书<Python核心编程(第二版)>的全新升级版本.<Python核心编程(第3版)>总共分为3部分.第1 ...
- Python字符串内建函数_下
Python字符串内建函数: 1.join(str) : 使用调用的字符串对 str 进行分割,返回值为字符串类型 # join(str) : # 使用调用的字符串对 str 进行分割. strs = ...
- PHP Session 变量
PHP Session PHP session 变量用于存储关于用户会话(session)的信息,或者更改用户会话(session)的设置.Session 变量存储单一用户的信息,并且对于应用程序中的 ...
- PHP strftime() 函数
------------恢复内容开始------------ 实例 根据区域设置格式化本地日期和时间: <?php echo(strftime("%B %d %Y, %X %Z&quo ...
- 畅购商城(七):Thymeleaf实现静态页
好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 Thymeleaf简单入门 什么 ...
- CF习题集二
CF习题集二 一.CF507E Breaking Good 题目描述 \(Breaking Good\)这个游戏对于有经验的玩家来说也有一定的难度. 游戏的主角小明希望加入一个叫斧头帮的犯罪团伙.这个 ...
- FreeSql增加新特性Context
源 FreeSql 作者做了很完善的组件 我看了一下,感觉很实用,使用上有很大的可自定义操作的地方,跟传统Orm固定格式不同,也异于Dapper的设计,支持表达式树 原地址 https://www.c ...
- 在IntelliJ IDEA中多线程并发代码的调试方法
通常来说,多线程的并发及条件断点的debug是很难完成的,或许本篇文章会给你提供一个友好的调试方法.让你在多线程开发过程中的调试更加的有的放矢. 我们将通过一个例子来学习.在这里,我编写了一个多线程程 ...
- Python画各种 3D 图形Matplotlib库
回顾 2D 作图 用赛贝尔曲线作 2d 图.此图是用基于 Matplotlib 的 Path 通过赛贝尔曲线实现的,有对赛贝尔曲线感兴趣的朋友们可以去学习学习,在 matplotlib 中,figur ...
- JS 图片跟随鼠标移动案例
css代码 img { position: absolute; /* top: 2px; */ width: 50px; height: 50px; } HTML代码 <img src=&quo ...