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

背景

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

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

  • 表达式语言(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中定义。

主要的开源实现有:OGNLMVELSpELJUELJava Expression Language (JEXL)JEvalJakarta 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);

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):aviatoreasy-rulesdroolsesper

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

@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://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动态规则内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java实现仿微信红包分配规则

    最近过年发红包拜年成为一种新的潮流,作为程序猿对算法的好奇远远要大于对红包的好奇,这里介绍一种自己想到的一种随机红包分配策略,还请大家多多指教. 算法介绍 一.红包金额限制 对于微信红包,我们知道没人随机的最小红包是1分,最大金额是200元,这里我们同样来设置红包的范围,下面代码我们统一金钱的单位为分. //最小红包额度 private static final int MINMONEY = 1; //最大红包额度 private static final int MAXMONEY = 200

  • Java命名规则详细总结

    1. JAVA源文件的命名 JAVA源文件名必须和源文件中所定义的类的类名相同. 2. Package的命名 Package名的第一部分应是小写ASCII字符,并且是顶级域名之一,通常是com.edu.gov.mil.net.org或由ISO标准3166.1981定义的国家唯一标志码.Package名的后续部分由各组织内部命名规则决定,内部命名规则指定了各组件的目录名,所属部门名.项目名等. 3. Class/Interface的命名 Class名应是首字母大写的名词.命名时应该使其简洁而又具有

  • java 命名空间 命名规则第1/2页

    摘要                                                                                          Java的类装载器是Java动态性的核心,本文将向大家简要介绍Java的类装载器,及相关的parent delegation模型,命名空间,运行时包等概念,同时讨论一些在学习中容易混淆的问题. 类装载器的功能及分类 顾名思义,类装载器是用来把类(class)装载进JVM的.JVM规范定义了两种类型的类装载器:启动类

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

    背景 业务系统在应用过程中,有时候要处理"经常变化"的部分,这部分需求可能是"业务规则",也可能是"不同的数据处理逻辑",这部分动态规则的问题,往往需要可配置,并对性能和实时性有一定要求. Java不是解决动态层问题的理想语言,在实践中发现主要有以下几种方式可以实现: 表达式语言(expression language) 动态语言(dynamic/script language language),如Groovy 规则引擎(rule engine

  • Java中的Collections类的使用示例详解

    Collections的常用方法及其简单使用 代码如下: package Collections; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Stack; public class collections { public static void main(String[]args){ int array[]={125,75,56,7}; Li

  • 在java中如何定义一个抽象属性示例详解

    前言 本文主要给大家介绍的是在java中定义一个抽象属性的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: Abstract关键字通常被用于类和方法,用来把某些行为的实现委托给子类.由于Java不支持抽象属性,如果你试图将类属性标记为抽象,将会得到一个编译时错误. 在本教程中,我们将介绍两种定义抽象属性的方法,这些抽象属性可以由子类进行设置,而且不使用Abstract 关键字. 实用案例 假设我们想要实现一个记录事务的日志模块,用来记录特定事务的信息.我们希望这个模块是抽

  • Java中的权限修饰符(protected)示例详解

    前言 大部分来自:https://blog.csdn.net/justloveyou_/article/details/61672133.并在这个博客的基础上,加上了自己的一些理解. 权限控制表 修饰词 本类 同一个包的类 继承类 其他类 private √ × × × 无(默认) √ √ × × protected √ √ √ × public √ √ √ √ 关于protected 最近在看Effective Java时,遇到了一个关于protected修饰符的问题.这个问题中,对于它的认识

  • Java中class和Class的区别示例详解

    目录 一.class与Class区别 二.Class介绍 三.如何得到Class对象 1.通过getClass()方法获取到Class对象 2.通过forName()方法获取到Class对象 3.类.class获得Class对象(类字面常量) 四.Class常用方法 总结 一.class与Class区别 class是Java中的关键字,如public class Xxx 或者 class Xxx ,在声明Java类时使用. 而Class是一个类. 我们通常认为类是对象的抽象和集合,Class就相

  • Java中的vector类使用方法示例详解

     基本操作示例 VectorApp.java import java.util.Vector; import java.lang.*; import java.util.Enumeration; public class VectorApp { public static void main(String args[]) { Vector v1 = new Vector(); Integer integer1= new Integer(1); //加入为字符串对象 v1.addElement("

  • JS中原始值和引用值的储存方式示例详解

    在ECMAscript中,变量可以存放两种类型的值,即原始值和引用值 原始值指的是代表原始数据类型的值,也叫基本数据类型,包括:Number.Stirng.Boolean.Null.Underfined 引用值指的是复合数据类型的值,包括:Object.Function.Array.Date.RegExp 根据数据类型不同,有的变量储存在栈中,有的储存在堆中.具体区别如下: 原始变量及他们的值储存在栈中,当把一个原始变量传递给另一个原始变量时,是把一个栈房间的东西复制到另一个栈房间,且这两个原始

  • Java中Validated、Valid 、Validator区别详解

    目录 1. 结论先出 JSR 380 Valid VS Validated 不同点? Validator 2. @Valid和​​​​​​​@Validated 注解 3. 例子 4.使用@Valid嵌套校验 5. 组合使用@Valid和@Validated 进行集合校验 6. 自定义校验 自定义约束注解 工作原理 结论 参考链接: 1. 结论先出 Valid VS Validated 相同点 都可以对方法和参数进行校验 @Valid和@Validated 两种注释都会导致应用标准Bean验证.

  • Java框架设计灵魂之反射的示例详解

    目录 获取Class对象的方式 Class对象功能 获取成员变量们 获取构造方法们 获取成员方法们 获取全类名 Field:成员变量 Constructor:构造方法 Method:方法对象 案例 框架:半成品软件.可以在框架的基础上进行软件开发,简化编码. 反射就是把Java类中的各个成员映射成一个个的Java对象. 即在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法: 对于任意一个对象,都能调用它的任意一个方法和属性. 这种动态获取信息及动态调用对象方法的功能叫Java的反射机

  • JS前端中的设计模式和使用场景示例详解

    目录 引言 策略模式 1.绩效考核 2.表单验证 策略模式的优缺点: 代理模式 1.图片懒加载: 2.缓存代理 总结 引言 相信大家在日常学习和工作中都多多少少听说/了解/使用过 设计模式,我们都知道,使用恰当的设计模式可以优化我们的代码,那你是否知道对于前端开发哪些 设计模式 是日常工作经常用到或者必须掌握的呢?本文我将带大家一起学习下前端常见的设计模式以及它们的 使用场景!!! 本文主讲: 策略模式 代理模式 适合人群: 前端人员 设计模式小白/想知道如何在项目中使用设计模式 策略模式 策略

随机推荐