• 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 ,SpELJUELJava 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);
    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):aviatoreasy-rulesdroolsespersiddhi

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

    drools

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

    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.

     一个例子:

    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:

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

    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

    Drools 规则引擎探究以及在 IOT 的应用:https://www.infoq.cn/article/jhSIS8qKp3WRHeUoq5uT

  • 相关阅读:
    what's the python之异常处理
    what's the python之面向对象(进阶)
    what's the python之面向对象
    what's the python之自定义模块和包
    Java并发编程-并发工具包(java.util.concurrent)使用指南(全)
    Java之JUC系列:外部Tools
    java CS结构软件自动升级的实现
    史上最全最强SpringMVC详细示例实战教程
    搭建最简单的SpringMVC框架(使用maven)
    小心对待query_cache_size
  • 原文地址:https://www.cnblogs.com/luxiaoxun/p/13554965.html
Copyright © 2020-2023  润新知