《写自己的框架6》:自定义规则引擎,适应不断变化的业务场景

规则引擎适于频繁变动做生意的场景规则。我们在应用过程中的业务,还经常要处理大量的业务规则,当然,此外,我们希望有一个规则引擎,支持,这仅仅是更好。

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

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

  • 业务规则运行器须要支持多种,也应该支持业务人员自行扩展。原因是我自己设计的业务规则再完美,也不可能完美的适应全部人的胃口,所以这个默认能够有支持的。可是一定是能够扩展的
  • 业务规则要支持优先级。也就是说有的业务规则先运行。有的业务规则后运行
  • 业务规则同意排他规则,也就是说仅仅要运行到排他规则,就能够立即结束
  • 业务规则能够同意反复运行,这样才干够方便的进行循环处理
  • 在规则引擎中。能够方便的使用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);
 }

如上面的代码一样,还是很easy的。  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框架的一大特点就是用很easy的逻辑来实现相对复杂的处理。

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://bbs.tinygroup.org

本例涉及的代码和框架资料,将会在社区分享。

《自己动手写框架》成员QQ群:228977971,让我们一起动手,了解开源框架的奥秘!

版权声明:本文博主原创文章。博客,未经同意不得转载。

时间: 2024-11-03 22:25:55

《写自己的框架6》:自定义规则引擎,适应不断变化的业务场景的相关文章

SpringBoot2 整合 Drools规则引擎,实现高效的业务规则

本文源码:GitHub·点这里 || GitEE·点这里 一.Drools引擎简介 1.基础简介 Drools是一个基于java的规则引擎,开源的,可以将复杂多变的规则从硬编码中解放出来,以规则脚本的形式存放在文件中,使得规则的变更不需要修正代码重启机器就可以立即在线上环境生效.具有易于访问企业策略.易于调整以及易于管理的特点,作为开源业务规则引擎,符合业内标准,速度快.效率高. 2.规则语法 (1).演示drl文件格式 package droolRule ; import org.slf4j.

Drrols规则引擎

1.什么是规则引擎? 规则引擎是一种嵌套在应用程序中的组件,它实现了将业务规则从应用程序代码中分离出来.规则引擎使用特定的语法编写业务规则,规则引擎可以接受数据输入.解释业务规则.并根据业务规则做出相应的决策.简单的说就是类似于于if...else语句 2.为什么要用规则引擎? -实现业务逻辑与业务规则的分离,实现业务规则的集中管理 -可以动态修改业务规则,从而快速响应需求变更 -使业务分析人员也可以参与编辑.维护系统的业务规则 -使用规则引擎提供的规则编辑工具,使复杂的业务规则实现变得的简单

规则引擎在数据分析中的应用

前言:规则引擎通过将业务规则和开发者的技术决策分离, 实现了动态管理和修改业务规则而又不影响软件系统的需求.以下通过实例对基于SQL 查询.自定义规则等一系列场景来说明规则引擎在数据分析中的应用. 在现代的企业级项目开发中, 商业决策逻辑或业务规则往往是硬编码嵌入在系统各处代码中的.但是外部市场业务规则是随时可能发生变化的, 这样开发人员必须时刻准备修改.更新系统,降低了效率.在这种背景下, 规则引擎应运而生,它通过将业务规则和开发者的技术决策分离, 实现了动态管理和修改业务规则而又不影响软件系

规则引擎入门

什么是规则引擎? 规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策.接受数据输入,解释业务规则,并根据业务规则做出业务决策. 参考: http://baike.baidu.com/link?url=BfAR-sxz1ryvOZ4uSVCqn9Csy51U5qCUaIomAQBogPE7FOxbbDB-2iXWR6N8FReIkoNNdc-Hk4y0sxKWpknWd_ 规则引擎和工作流引擎有什么区别? 工作流引

旗正商业规则引擎和开源及其它规则引擎测试对比

规则引擎在基础软件,或者在很多系统中已经不是稀奇的玩意,最近这几年,国内不断兴起很多的规则引擎,至于什么是规则引擎,在这篇文章中,就不做介绍了,我想能看以下内容的,多少对规则引擎也都有所了解了. 国内在2003年的时候,出了第一款商业规则引擎-旗正商业规则引擎(VisualRules),为什么这么说呢,因为再此之前,国内所用的规则引擎,都是国外产品,或者开源产品,纯自主研发旗正是第一款,直至目前为止,纯自主研发的规则引擎少之又少.那么旗正商业规则引擎到底怎样?今天,给大家介绍一下,顺便,我们拿出

Asp.net 面向接口可扩展框架之业务规则引擎扩展模块

随着面向接口可扩展框架的继续开发,有些功能开发出现了"瓶颈",有太多的东西要写死才好做.但写死的代码扩展性是非常的不好,迷茫中寻找出入... 进而想到我以前开发的好几个项目,都已有一定的可配置能力,想想怎么把这些地方的代码抽象提取出来.进而想到"业务规则引擎",网上找了几个都不太入"眼",就抽时间再造个"轮子" 业务规则引擎在很多成熟的工作流引擎中都有相应的模块,是工作流的核心之一.但是除了工作流很多业务都需要业务规则引擎,所

【猪猪-后端】WebMagic框架搭建的爬虫,根据自定义规则,直接抓取,使用灵活,Demo部署即可查看。

原文:[猪猪-后端]WebMagic框架搭建的爬虫,根据自定义规则,直接抓取,使用灵活,Demo部署即可查看. 源代码下载地址:http://www.zuidaima.com/share/1581523414404096.htm 如果要使用注解方式实现,也是支持的. @TargetUrl("http://my.oschina.net/flashsword/blog/\\d+") public class OschinaBlog { @ExtractBy("//title&qu

yii 框架 自定义规则客户端验证

前提:yii 自定义规则不能通过失去焦点验证 view层中:设置form的3个属性,validationUrl 可以不设置,默认为当前页面,但是一般情况验证不会跟提交数据在一个方法中处理 $form = zActiveForm::begin([ 'id'=> 'validate', 'enableAjaxValidation'=> true, 'validationUrl' => \yii\helpers\Url::toRoute('/hotel/hotel-room/validate-

SNF快速开发平台--规则引擎介绍和使用文档

设计目标: a) 规则引擎语法能够满足分单,计费,WMS策略的配置要求.语法是一致和统一的 b) 能够在不修改规则引擎模块的情况下,加入任意一个新的规则:实现上述需求之外的规则配置需求 c) 运算速度快 d) 有良好的展现效果,能够在售前阶段帮助销售 e) 提供良好的调试和诊断手段,便于配置规则 可以把以前固定写的业务逻辑,特别不确定的那种和变化比较多的写到规则中去.这样在实施时不同客户有各种各样的需求,可以按需配置,并不能修改程序代码,这样就更灵活. 并且我们实现的日志跟踪调试,运算符.条件语