`
j2eetop
  • 浏览: 64414 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Tiny之RuleEngine

 
阅读更多

目录[-]
规则执行器接口
规则引擎接口
规则集对象
规则抽象类
MVEL方式的规则及其执行器
Mvel规则
Mvel规则执行器
Mvel上下文
规则引擎实现类
示例
定义规则
编写TestCase
总结
规则引擎适合于做业务规则频繁变化的场景,我们的业务在应用过程中,也经常要处理大量的业务规则,当然,也希望能有一套规则引擎来支撑,这样是再好不过的。

对一些常用的商业规则引擎做一下了解,感觉非常不错,但是太贵了。看一些开源的引擎吧,也不错,但是感觉相对于我们自己这么简单的需求,太复杂了。

于是就想着自己做个,试试看能不能解决了自己的这些简单的业务规则频繁变化的业务场景,嗯嗯,脑子里大概过了一下电影,感觉路是通的,主要有如下业务需求:

业务规则执行器需要支持多种,也应该支持业务人员自行扩展,原因是我自己设计的业务规则再完美,也不可能完美的适应所有人的胃口,所以这个默认可以有支持的,但是一定是可以扩展的
业务规则要支持优先级,也就是说有的业务规则先执行,有的业务规则后执行
业务规则允许排他规则,也就是说只要执行到排他规则,就可以马上结束
业务规则可以允许重复执行,这样才可以方便的进行循环处理
在规则引擎中,可以方便的使用Spring中的业务对象
于是就可以开始设计了:


规则执行器接口


由于业务规则执行器需要支持扩展,当然需要设计一个接口了:

/**
 * 规则执行器,可以有多种实现
 */
public interface RuleExecutor<T extends Rule> {
    /**
     * 返回执行器类型
     *
     * @return
     */
    String getType();
 
    /**
     * 执行规则,并把结果放到上下文上
     *
     * @param context
     * @return 返回条件是否成立
     */
    boolean execute(Context context, T rule);
}

 一共就两方法,getType用来返回规则执行器的类型,以确定它是解决哪种类型的规则的。
execute方法用来执行规则,执行的结果是一个布尔值,表示此条规则是否有执行。

 

规则引擎接口


接下来就是设计规则引擎的接口了:

 

public interface RuleEngine {
    /**
     * 对指定上下文执行指定类型的规则
     *
     * @param context
     * @param ruleSetName
     */
    void execute(Context context, String ruleSetName);
 
    /**
     * 添加一组规则
     *
     * @param ruleSet
     */
    void addRules(RuleSet ruleSet);
 
    /**
     * 删除一组规则
     *
     * @param ruleSet
     */
    void removeRules(RuleSet ruleSet);
 
    /**
     * 添加规则执行器列表
     *
     * @param ruleExecutors
     */
    void addRuleExecutors(List<RuleExecutor> ruleExecutors);
 
    /**
     * 添加一个规则执行器
     *
     * @param ruleExecutor
     */
    void addRuleExecutor(RuleExecutor ruleExecutor);
     
    /**
     * 删除规则执行器列表
     *
     * @param ruleExecutors
     */
    void removeRuleExecutors(List<RuleExecutor> ruleExecutors);
 
    /**
     * 删除一个规则执行器
     *
     * @param ruleExecutor
     */
    void removeRuleExecutor(RuleExecutor ruleExecutor);
     
    /**
     * 设置一批规则执行器
     * @param ruleExecutors
     */
    void setRuleExecutors(List<RuleExecutor> ruleExecutors);
}

 如上面的代码一样,还是非常简单的。
execute用来执行一个规则集,其它的方法就是对规则集和规则执行器的管理,只要看一遍就一清二楚了。

 

规则集对象

 

 

@XStreamAlias("rule-set")
public class RuleSet {
    /**
     * 同种名称的规则集会自动合并
     */
    @XStreamAsAttribute
    private String name;
     
    @XStreamImplicit
    private List<Rule> rules;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public List<Rule> getRules() {
        if(rules==null){
            rules = new ArrayList<Rule>();
        }
        return rules;
    }
 
    public void setRules(List<Rule> rules) {
        this.rules = rules;
    }
}

  规则集就两属性,一个属性是规则集的名称,另外一个属性就是一组规则。规则集的名称用来表示一组相关的业务规则。


规则抽象类

 

根据上面的业务需求,抽象类Rule的结构如下:



  

它里面只有基本的几个属性:优先级,标识,是否排他,是否允许重复,描述,标题,类型,有效性。

说好的业务规则呢,怎么描述?

由于不同的规则执行器,它可以支持的规则也不一样,因此里的规则抽象类只有基本的一些属性,怎么样描述规则由其子类决定。

 

MVEL方式的规则及其执行器


Mvel规则

 

/**
 * 采用MVEL表达式作为条件实现
 * @author yancheng11334
 *
 */
@XStreamAlias("mvel-rule")
public class MvelRule extends Rule{
    //匹配条件
    private String condition;
    //后续操作
    private String action;
  
    public String getCondition() {
        return condition;
    }
 
    public void setCondition(String condition) {
        this.condition = condition;
    }
     
    public String getAction() {
        return action;
    }
 
    public void setAction(String action) {
        this.action = action;
    }
 
    public String getType(){
        return "mvel";
    }
 
    public String toString() {
        return "MvelRule [condition=" + condition + ", action=" + action
                + ", type=" + getType() + ", id=" + getId() + ", priority="+ getPriority() +", multipleTimes="+isMultipleTimes()+",exclusive="+isExclusive()+"]";
    }
 
    /**
     * 验证mvel规则的合法性
     */
    public boolean isVaild() {
        if(StringUtil.isEmpty(getCondition())){
            throw new RuntimeException(String.format("规则[%s]的匹配条件为空", getId()));
        }
        if(StringUtil.isEmpty(getAction())){
            throw new RuntimeException(String.format("规则[%s]的后续操作为空", getId()));
        }
        return true;
    }
}

上面表示,这个规则的类型都是mvel,这个规则包含了两个属性:condition和action,condition表示条件,只有条件执行结果为真的时候,才执行action中的处理。

Mvel规则执行器

 

public class MvelRuleExecutor implements RuleExecutor<MvelRule>{
 
    private EL el;
     
    public EL getEl() {
        return el;
    }
 
    public void setEl(EL el) {
        this.el = el;
    }
 
    public String getType() {
        return "mvel";
    }
 
    public boolean execute(Context context, MvelRule rule) {
        try{
            if(executeCondition(rule.getCondition(),context)){
                executeAction(rule.getAction(),context);
                return true;
            }else{
                return false;
            }
        }catch(RuntimeException e){
           throw e;
        }catch(Exception e){
           throw new RuntimeException("Mvel规则引擎执行器发生异常:",e);
        }
    }
     
    /**
     * 判断条件是否匹配
     * @param condition
     * @param context
     * @return
     */
    protected boolean executeCondition(String condition,Context context){
        try{
            MvelContext mvelContext=null;
            if(context instanceof MvelContext){
                mvelContext=(MvelContext) context;
            }else{
                mvelContext=new MvelContext(context);
            }
            return (Boolean)el.execute(condition, mvelContext);
        }catch(Exception e){
           throw new RuntimeException(String.format("条件[%s]匹配发生异常:", condition),e);
        }
    }
     
    /**
     * 执行条件匹配后的操作
     * @param action
     * @param context
     */
    protected void executeAction(String action,Context context) {
        try {
            MvelContext mvelContext = null;
            if (context instanceof MvelContext) {
                mvelContext = (MvelContext) context;
            } else {
                mvelContext = new MvelContext(context);
            }
 
            el.execute(action, mvelContext);
        } catch (Exception e) {
            throw new RuntimeException(String.format("后续操作[%s]执行发生异常:", action), e);
        }
    }
}

 execute方法的意思是:如果执行条件表达式且返回真,那么就执行action中的处理,并返回true,否则就返回false。
呵呵,这个逻辑也太简单了。对,tiny框架的一大特点就是用非常简单的逻辑来实现相对复杂的处理。

 

Mvel上下文

 

前面讲到,要方便的在表达式中调用Spring中托管的对象,这个的实现就要从上下文上作文章了:

public <T> T get(String name) {
        if(context.exist(name)){
           return (T)context.get(name);
        }else{
           //必须保存到上下文,否则每次返回不一定是同一个对象(scope可能是属性)
           T t = (T)beanContainer.getBean(name);
           context.put(name, t);
           return t;
        }
    }

 主要的逻辑在上面,也就是说:如果上下文中有对像,那么就从上下文中取;如果没有,那么就从Spring容器中取。呵呵,这么高大上的功能,实现起来也这么简单。

规则引擎实现类


到上面为止,相关的准备工作都就绪了,规则引擎的实现类也可以现身了。其实这个类不贴吧,看文章的同学们一定说我藏着掖着,但是贴出来吧,真的没有啥技术含量:

public class RuleEngineDefault implements RuleEngine {
    private Map<String, List<Rule>> ruleSetMap = new ConcurrentHashMap<String, List<Rule>>();
    private List<RuleExecutor> ruleExecutors = null;
    private Map<String, RuleExecutor> ruleExecutorMap = new ConcurrentHashMap<String, RuleExecutor>();
     
    protected static Logger logger = LoggerFactory
    .getLogger(RuleEngineDefault.class);
     
    public void execute(Context context, String ruleSetName) {
        List<Rule> ruleSet = ruleSetMap.get(ruleSetName);
        if (ruleSet != null) {
            Vector<Rule> newSet = new Vector<Rule>(ruleSet);
            processRuleSet(context, newSet);
        }
    }
 
    private void processRuleSet(Context context, Vector<Rule> newSet) {
        //如果没有后续规则,则退出
        if (newSet.size() == 0) {
            return;
        }
        Rule rule = newSet.get(0);
        RuleExecutor ruleExecutor = ruleExecutorMap.get(rule.getType());
        if (ruleExecutor != null) {
            boolean executed = ruleExecutor.execute(context, rule);
            if (executed) {
                //如果
                if (rule.isExclusive()) {
                    //如果条件成立,则是独占条件,则直接返回
                    return;
                } else if (!rule.isMultipleTimes()) {
                    //如果不是可重复执行的规则,则删除之
                    newSet.remove(0);
                }
            } else {
                //如果不匹配,则删除之
                newSet.remove(0);
            }
        } else {
            throw new RuntimeException("找不到对应" + rule.getType() + "的执行器");
        }
        processRuleSet(context, newSet);
    }
 
    public void addRules(RuleSet ruleSet) {
        List<Rule> rules = ruleSetMap.get(ruleSet.getName());
        if (rules == null) {
            rules = new Vector<Rule>();
            ruleSetMap.put(ruleSet.getName(), rules);
        }
        //检查规则
        for(Rule rule:ruleSet.getRules()){
            if(rule.isVaild()){
                rules.add(rule);
            }else{
                logger.logMessage(LogLevel.ERROR, String.format("规则[%s]检查无效.", rule.getId()));
            }
            rule.isVaild();
        }
        Collections.sort(rules);
    }
     
 
    public void removeRules(RuleSet ruleSet) {
        List<Rule> rules = ruleSetMap.get(ruleSet.getName());
        if (rules != null) {
            rules.removeAll(ruleSet.getRules());
        }
    }
 
    public void setRuleExecutors(List<RuleExecutor> ruleExecutors) {
        this.ruleExecutors = ruleExecutors;
        for (RuleExecutor ruleExecutor : ruleExecutors) {
            ruleExecutorMap.put(ruleExecutor.getType(), ruleExecutor);
        }
    }
 
    public void addRuleExecutor(RuleExecutor ruleExecutor) {
        if (ruleExecutors == null) {
            ruleExecutors = new ArrayList<RuleExecutor>();
        }
        ruleExecutors.add(ruleExecutor);
        ruleExecutorMap.put(ruleExecutor.getType(), ruleExecutor);
    }
 
    public void addRuleExecutors(List<RuleExecutor> ruleExecutors) {
        if(ruleExecutors!=null){
           for(RuleExecutor ruleExecutor:ruleExecutors){
               addRuleExecutor(ruleExecutor);
           }
        }
    }
 
    public void removeRuleExecutors(List<RuleExecutor> ruleExecutors) {
        if(ruleExecutors!=null){
           for(RuleExecutor ruleExecutor:ruleExecutors){
               removeRuleExecutor(ruleExecutor);
           }
        }
    }
 
    public void removeRuleExecutor(RuleExecutor ruleExecutor) {
        if (ruleExecutors == null) {
            ruleExecutors = new ArrayList<RuleExecutor>();
        }
        ruleExecutors.remove(ruleExecutor);
        ruleExecutorMap.remove(ruleExecutor.getType());
    }
}

 一大堆维护规则和规则执行器的代码就不讲了,关键的几个讲下:

public void execute(Context context, String ruleSetName) {
        List<Rule> ruleSet = ruleSetMap.get(ruleSetName);
        if (ruleSet != null) {
            Vector<Rule> newSet = new Vector<Rule>(ruleSet);
            processRuleSet(context, newSet);
        }
    }

 查找规则集,如果能找到就执行规则集,否则啥也不干。

private void processRuleSet(Context context, Vector<Rule> newSet) {
        //如果没有后续规则,则退出
        if (newSet.size() == 0) {
            return;
        }
        Rule rule = newSet.get(0);
        RuleExecutor ruleExecutor = ruleExecutorMap.get(rule.getType());
        if (ruleExecutor != null) {
            boolean executed = ruleExecutor.execute(context, rule);
            if (executed) {
                //如果
                if (rule.isExclusive()) {
                    //如果条件成立,则是独占条件,则直接返回
                    return;
                } else if (!rule.isMultipleTimes()) {
                    //如果不是可重复执行的规则,则删除之
                    newSet.remove(0);
                }
            } else {
                //如果不匹配,则删除之
                newSet.remove(0);
            }
        } else {
            throw new RuntimeException("找不到对应" + rule.getType() + "的执行器");
        }
        processRuleSet(context, newSet);
    }

     }
执行规则集的逻辑是:
如果规则集合中没有规则了,表示规则集已经执行完毕,直接返回。否则获取优先级最高的规则,首先检查是否有对象的规则执行器,如果没有,则抛异常。如果有就开始执行。

如果执行返回true,说明此规则被成功执行,则判断其是否是排他规则,如果是,则返回;否则检查是否是可重复执行规则,如果是则返回继续执行,否则把此条规则删除,继续执行下一条规则。

 

示例


这里假定做一个计算个人所得税的规则实例

 

定义规则

<rule-set name="feerule" >
     <!-- 独占类条件(执行顺序交互不影响执行结果) -->
     <!--优先级,数值越小优先级越高,用户设置优先级必须大于0;如果没有设置,系统会随机分配一个优先级;同一个规则集不能出现两个相同优先级的规则-->
     <mvel-rule id="step1"  multipleTimes="false" exclusive="true">
        <condition><![CDATA[salary<=3500]]></condition>
        <action><![CDATA[fee=0]]></action>
     </mvel-rule>
     <mvel-rule id="step2"  multipleTimes="false" exclusive="true">
        <condition><![CDATA[salary>3500 && salary<=5000]]></condition>
        <action><![CDATA[fee=(salary-3500)*0.03]]></action>
     </mvel-rule>
     <mvel-rule id="step3"  multipleTimes="false" exclusive="true">
        <condition><![CDATA[salary>5000 && salary<=8000]]></condition>
        <action><![CDATA[fee=(salary-3500)*0.1-105]]></action>
     </mvel-rule>
     <mvel-rule id="step4"  multipleTimes="false" exclusive="true">
        <condition><![CDATA[salary>8000 && salary<=12500]]></condition>
        <action><![CDATA[fee=(salary-3500)*0.2-555]]></action>
     </mvel-rule>
     <mvel-rule id="step5" multipleTimes="false" exclusive="true">
        <condition><![CDATA[salary>12500 && salary<=38500]]></condition>
        <action><![CDATA[fee=(salary-3500)*0.25-1005]]></action>
     </mvel-rule>
     <mvel-rule id="step6"  multipleTimes="false" exclusive="true">
        <condition><![CDATA[salary>38500 && salary<=58500]]></condition>
        <action><![CDATA[fee=(salary-3500)*0.3-2755]]></action>
     </mvel-rule>
     <mvel-rule id="step7"  multipleTimes="false" exclusive="true">
        <condition><![CDATA[salary>58500 && salary<=83500]]></condition>
        <action><![CDATA[fee=(salary-3500)*0.35-5505]]></action>
     </mvel-rule>
     <mvel-rule id="step8"  multipleTimes="false" exclusive="true">
        <condition><![CDATA[salary>83500]]></condition>
        <action><![CDATA[fee=(salary-3500)*0.45-13505]]></action>
     </mvel-rule>
  </rule-set>

 

编写TestCase

public void testFeeRule(){
        Context context = new MvelContext();
        context.put("fee", 0.0);
         
        context.put("salary", 1000);
        ruleEngine.execute(context, "feerule");
        assertEquals(0, context.get("fee"));
         
        context.put("salary", 4000);
        ruleEngine.execute(context, "feerule");
        assertEquals(15.0, context.get("fee"));
         
        context.put("salary", 7000);
        ruleEngine.execute(context, "feerule");
        assertEquals(245.0, context.get("fee"));
         
        context.put("salary", 21000);
        ruleEngine.execute(context, "feerule");
        assertEquals(3370.0, context.get("fee"));
         
        context.put("salary", 40005);
        ruleEngine.execute(context, "feerule");
        assertEquals(8196.50, context.get("fee"));
         
        context.put("salary", 70005);
        ruleEngine.execute(context, "feerule");
        assertEquals(17771.75, context.get("fee"));
         
        context.put("salary", 100000);
        ruleEngine.execute(context, "feerule");
        assertEquals(29920.00, context.get("fee"));
    }

 

 

 

 看到这里的时候,我唯一的想法是:啥时我才可以一个月缴3万块的税呀。

 

总结

 


呵呵,按照Tiny惯例,传上代码统计数据:

 

至此,一个简单的规则引擎就实现了,总共代码行数不包含注释为:462行。可以较好的适应各种简单的业务逻辑频繁变化的业务场景。


 欢迎登录关注:http://web.j2ee.top,本例涉及的代码和框架资料,将会在论坛分享。技术交流群:228977971,让我们一起动手,了解框架的奥秘!

0
0
分享到:
评论

相关推荐

    作品:《Tiny Dynamics Engine演示》.rar

    《Tiny Dynamics Engine演示》是一个关于小型物理引擎应用的示例项目。这个压缩包包含的文件主要是为了展示如何使用Tiny Dynamics Engine来实现动态物体的交互和模拟。Tiny Dynamics Engine是一款轻量级的3D物理引擎...

    北大的tiny search engine源码加电子书讲解

    【标题】"北大的tiny search engine源码加电子书讲解"是关于北京大学开发的一个小型搜索引擎项目的资源集合。这个项目旨在让学生了解搜索引擎的基本工作原理,并通过实际操作C++代码来加深理解。 【描述】中提到的...

    tinyxml与tinyxml2

    TinyXML和TinyXML2是两个小型的开源库,专门用于解析和操作XML(eXtensible Markup Language)文档。这两个库都是用C++编写的,为C++开发者提供了方便的接口来读取、写入和操作XML数据。下面将详细介绍这两个库的...

    tinyxml2.h tinyxml2.cpp文件

    TinyXML-2,通常以tinyxml2库的形式存在,是一个轻量级的C++库,专门用于解析和操作XML文档。这个库由李·斯坦普(Lee Stemp)开发,设计简洁,易于集成到各种项目中,特别是对于嵌入式系统和资源有限的环境。在给定...

    TINY编译器源码,tiny编译器源码下载,C,C++

    《深入理解TINY编译器源码:C与C++编程实践》 TINY编译器,作为一个小型的、易于理解的编译器项目,是学习编译原理和实践的重要资源。它用C和C++编写,源码简洁且带有详细注释,非常适合初学者或者对编译器感兴趣的...

    Tiny6410官方PDF资料合集

    此文档为友善之臂Tiny6410官方文档,其中包含: 01-Tiny6410硬件手册 02-Tiny6410功能测试指南 03-Tiny6410刷机指南 04-Tiny6410 Linux开发指南 05-Tiny6410 Android开发指南 06-Tiny6410WinCE开发指南 07-...

    RTX51 TINY 2.02 中文手册

    * What's New:RTX51 TINY 2.02 中的新功能和改进之处,帮助开发者快速了解新版本的变化。 * 已解决的问题:RTX51 TINY 2.02 中解决了哪些问题,帮助开发者了解新版本的改进之处。 * 产品规约:RTX51 TINY 2.02 的...

    tinyxml2 version 9.0.0下载地址

    TinyXML2是一个小型、快速且易于使用的XML解析库,主要用于C++编程。版本9.0.0是该库的一个更新版本,提供了最新的功能和错误修复。在这个版本中,开发者可以期待更加稳定和优化的XML处理性能。 TinyXML2的设计目标...

    TinyXML2使用示例

    TinyXML2是一个轻量级的XML解析库,主要用于读取和写入XML文档。这个库在C++中设计,提供了一种简洁的方式来处理XML数据,适用于小型项目或嵌入式系统。在“TinyXML2使用示例”中,我们可以通过提供的源代码和XML...

    tinyOS内核分析

    ### TinyOS内核分析 #### 1. 综述 TinyOS是一款专为无线传感器网络设计的操作系统,其内核采用非抢占式设计,这意味着它依赖于任务的主动放弃CPU控制权来完成任务间的切换。这种设计使得TinyOS能够更好地支持资源...

    yolov7-tiny.weights和yolov7-tiny.conv.87预训练模型文件下载

    YOLOv7-Tiny是YOLO(You Only Look Once)系列目标检测算法的一个轻量级变体,由Alexey Bochkovskiy等开发者在Darknet框架下设计。这个算法在保持高效的同时,具备较高的目标检测精度。在给定的压缩包中,包含了两个...

    tinyxml2最新版本(来自github)

    TinyXML2是一个轻量级的XML解析库,主要用于读取和写入XML文档。这个库由Steve Baker开发,它的最新版本在2017年8月17日更新,这表明了开发者持续对库进行维护和改进以适应不断变化的编程需求。在本文中,我们将深入...

    Tiny4412中文用户手册

    友善之臂公司对Tiny4412开发板提供持续的更新和支持,包括但不限于硬件底板的改进、内核与系统的升级、各种开发工具和文档的更新等。开发者可以随时登录官方网站查看最新的手册更新,并下载更新内容以保证开发板的...

    tinyxml+tinyxpath+tinyxml++最新套件(20080916)

    TinyXML、TinyXPath和TinyXML++是开源的C++库,专门用于解析和操作XML文档。这些库在处理XML数据时提供了轻量级且高效的选择,尤其适合嵌入式系统和资源有限的环境。 TinyXML,版本2.5.3,是一个小型、简单且易于...

    QT使用TinyXML开源库解析XML文件

    QT框架是广受欢迎的开源C++开发平台,用于构建跨平台的应用程序,而TinyXML则是一个小巧、轻量级的开源库,专门用于解析和操作XML文档。在QT项目中集成TinyXML,可以方便地处理XML数据,尤其适用于那些需要读取、...

    tinyxml2源码及示例

    TinyXML2是一个轻量级的XML解析库,主要由C++编写,旨在提供简单、高效的方式来读取和写入XML文档。它适用于各种平台,包括桌面系统和移动系统,如Android。这个库的设计目标是小巧且易于理解,使得开发者能够快速地...

    yolov7-tiny.conv.87.zip

    YOLOv7-tiny是YOLO系列的最新版本之一,它在保持快速检测速度的同时,提升了模型的精度。本文将详细探讨YOLOv7-tiny的结构特点、工作原理以及如何使用预训练模型"yolov7-tiny.conv.87"进行实际应用。 YOLOv7-tiny是...

    TinyXML入门教程

    ### TinyXML入门教程知识点详解 #### 一、XML简介与TinyXML概述 - **XML**:全称为**可扩展标记语言**(EXtensible Markup Language),是一种用于标记数据的语言,允许用户自定义数据的标识,从而区分不同类型的结构...

    tinycore v6.3

    Tinycore v6.3 是一个基于Linux的极小型操作系统,其设计目标是提供一个轻量级、快速启动的环境,适合于老旧硬件、虚拟化应用、服务器或作为基础系统进行定制开发。Tinycore Linux 以其小巧的体积和高度模块化的特性...

Global site tag (gtag.js) - Google Analytics