筹划并用Java实现一个浅易的规则引擎

打印 上一主题 下一主题

主题 984|帖子 984|积分 2962

前言

使用规则引擎可以很方便的实现一些比力复杂的业务逻辑。
本文介绍的浅易版,是一个小的通用代码结构。
通过组装业务数据,创建执行模版,最终执行,获取到最终结果。
各人在复杂场景下,也可以选用Drools来实现。
   Drools 是一个开源的业务规则管理系统,它提供了基于Java的规则引擎,用于实现复杂的变乱处理和业务逻辑。Drools 支持声明式编程,允许开发人员将业务逻辑从应用程序代码中分离出来,这使得业务逻辑的管理和变动更加机动和便捷。
  正文

一、代码结构


二、焦点代码

maven依赖文件:
  1.     <properties>
  2.         <java.version>17</java.version>
  3.         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  4.         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  5.         <maven.compiler.source>17</maven.compiler.source>
  6.         <maven.compiler.target>17</maven.compiler.target>
  7.     </properties>
  8.     <dependencies>
  9.         <dependency>
  10.             <groupId>org.projectlombok</groupId>
  11.             <artifactId>lombok</artifactId>
  12.             <optional>true</optional>
  13.         </dependency>
  14.         <dependency>
  15.             <groupId>org.springframework.boot</groupId>
  16.             <artifactId>spring-boot-starter</artifactId>
  17.         </dependency>
  18.     </dependencies>
复制代码
2.1 上下文数据接口 ContextData.java

  1. package com.song.tools.ruleengine.core;
  2. /**
  3. * 上下文数据
  4. *
  5. * @author song tools
  6. */
  7. public interface ContextData {
  8.     /**
  9.      * 校验上下文数据
  10.      */
  11.     default void validProperty() {
  12.     }
  13. }
复制代码
2.2 规则接口 Rule.java

  1. package com.song.tools.ruleengine.core;
  2. /**
  3. * 规则接口
  4. *
  5. * @author song tools
  6. */
  7. public interface Rule<ContextData extends com.song.tools.ruleengine.core.ContextData> {
  8.     /**
  9.      * 执行前
  10.      *
  11.      * @param contextData 上下文数据
  12.      */
  13.     default void before(ContextData contextData) {
  14.         contextData.validProperty();
  15.     }
  16.     /**
  17.      * 执行
  18.      *
  19.      * @param context 上下文
  20.      */
  21.     void execute(RuleEngineContext<ContextData> context);
  22.     /**
  23.      * 执行后
  24.      *
  25.      * @param contextData 上下文数据
  26.      */
  27.     default void after(ContextData contextData) {
  28.     }
  29. }
复制代码
2.3 规则引擎接口 RuleEngine.java

  1. package com.song.tools.ruleengine.core;
  2. /**
  3. * 规则引擎
  4. *
  5. * @author song tools
  6. */
  7. public interface RuleEngine<ContextData extends com.song.tools.ruleengine.core.ContextData> {
  8.     /**
  9.      * 注册规则
  10.      *
  11.      * @param ruleKey 规则key
  12.      * @param rule    规则
  13.      */
  14.     void registerRule(String ruleKey, Rule<ContextData> rule);
  15.     /**
  16.      * 执行规则
  17.      *
  18.      * @param context 上下文
  19.      */
  20.     void executeRule(RuleEngineContext<ContextData> context) throws Exception;
  21.     /**
  22.      * 规则数量
  23.      *
  24.      * @return 规则数量
  25.      */
  26.     int ruleSize();
  27. }
复制代码
2.4 规则引擎上下文类 RuleEngineContext.java

  1. package com.song.tools.ruleengine.core;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import lombok.NoArgsConstructor;
  5. /**
  6. * 规则引擎上下文
  7. *
  8. * @author song tools
  9. */
  10. @Data
  11. @NoArgsConstructor
  12. @AllArgsConstructor
  13. public class RuleEngineContext<ContextData extends com.song.tools.ruleengine.core.ContextData> {
  14.     /**
  15.      * 上下文数据
  16.      */
  17.     private ContextData contextData;
  18. }
复制代码
2.5 规则引擎默认实现类 DefaultRuleEngine.java

  1. package com.song.tools.ruleengine.core;
  2. import java.util.LinkedHashMap;
  3. import java.util.Map;
  4. /**
  5. * 默认实现的规则引擎
  6. *
  7. * @author song tools
  8. */
  9. public class DefaultRuleEngine<ContextData extends com.song.tools.ruleengine.core.ContextData> implements RuleEngine<ContextData> {
  10.     private final Map<String, Rule<ContextData>> RULE_MAP;
  11.     public DefaultRuleEngine() {
  12.         RULE_MAP = new LinkedHashMap<>(16);
  13.     }
  14.     @Override
  15.     public void registerRule(String ruleKey, Rule<ContextData> rule) {
  16.         RULE_MAP.put(ruleKey, rule);
  17.     }
  18.     @Override
  19.     public void executeRule(RuleEngineContext<ContextData> context) {
  20.         for (Rule<ContextData> rule : RULE_MAP.values()) {
  21.             rule.before(context.getContextData());
  22.             rule.execute(context);
  23.             rule.after(context.getContextData());
  24.         }
  25.     }
  26.     @Override
  27.     public int ruleSize() {
  28.         return RULE_MAP.size();
  29.     }
  30. }
复制代码
2.6 执行时出错监听器接口 ExecuteOnErrorListener.java

  1. package com.song.tools.ruleengine.core;
  2. /**
  3. * 执行时出错监听器
  4. *
  5. * @author song tools
  6. */
  7. public interface ExecuteOnErrorListener<ContextData extends com.song.tools.ruleengine.core.ContextData> {
  8.     void onError(ContextData contextData, Exception e);
  9. }
复制代码
2.7 规则引擎执行模版类 RuleEngineExecuteTemplate.java

  1. package com.song.tools.ruleengine.core;
  2. import lombok.Getter;
  3. import lombok.extern.slf4j.Slf4j;
  4. import java.util.List;
  5. import java.util.Objects;
  6. import java.util.concurrent.atomic.AtomicInteger;
  7. /**
  8. * 规则执行模版
  9. *
  10. * @author song tools
  11. */
  12. @Getter
  13. @Slf4j
  14. public class RuleEngineExecuteTemplate<ContextData extends com.song.tools.ruleengine.core.ContextData> {
  15.     /**
  16.      * 规则引擎
  17.      */
  18.     private final RuleEngine<ContextData> ruleEngine;
  19.     /**
  20.      * 规则索引
  21.      */
  22.     private final AtomicInteger ruleIndex;
  23.     /**
  24.      * 执行失败监听器
  25.      */
  26.     private ExecuteOnErrorListener<ContextData> executeOnErrorListener;
  27.     public RuleEngineExecuteTemplate() {
  28.         this(new DefaultRuleEngine<>());
  29.     }
  30.     public RuleEngineExecuteTemplate(RuleEngine<ContextData> ruleEngine) {
  31.         Objects.requireNonNull(ruleEngine, "ruleEngine is null");
  32.         this.ruleIndex = new AtomicInteger(1);
  33.         this.ruleEngine = ruleEngine;
  34.     }
  35.     /**
  36.      * 注册规则
  37.      *
  38.      * @param rule 规则
  39.      * @return this 当前模版对象
  40.      */
  41.     public RuleEngineExecuteTemplate<ContextData> registerRule(Rule<ContextData> rule) {
  42.         String ruleKey = rule.getClass().getSimpleName();
  43.         return this.registerRule(ruleKey, rule);
  44.     }
  45.     /**
  46.      * 注册规则
  47.      *
  48.      * @param rules 规则列表
  49.      * @return this 当前模版对象
  50.      */
  51.     public RuleEngineExecuteTemplate<ContextData> registerRule(List<Rule<ContextData>> rules) {
  52.         for (Rule<ContextData> rule : rules) {
  53.             this.registerRule(rule);
  54.         }
  55.         return this;
  56.     }
  57.     /**
  58.      * 注册规则
  59.      *
  60.      * @param ruleKey 规则key
  61.      * @param rule    规则
  62.      * @return this 当前模版对象
  63.      */
  64.     public RuleEngineExecuteTemplate<ContextData> registerRule(String ruleKey, Rule<ContextData> rule) {
  65.         ruleEngine.registerRule(ruleKey + ruleIndex.getAndIncrement(), rule);
  66.         return this;
  67.     }
  68.     public RuleEngineExecuteTemplate<ContextData> registerExecuteOnErrorListener(ExecuteOnErrorListener<ContextData> executeOnErrorListener) {
  69.         this.executeOnErrorListener = executeOnErrorListener;
  70.         return this;
  71.     }
  72.     /**
  73.      * 执行规则
  74.      */
  75.     public void execute(ContextData contextData) throws Exception {
  76.         Objects.requireNonNull(contextData, "contextData is null");
  77.         if (ruleEngine.ruleSize() == 0) {
  78.             throw new IllegalArgumentException("ruleEngine not found register rule");
  79.         }
  80.         // 组装上下文
  81.         RuleEngineContext<ContextData> context = new RuleEngineContext<>();
  82.         context.setContextData(contextData);
  83.         try {
  84.             // 执行规则
  85.             ruleEngine.executeRule(context);
  86.         } catch (Exception e) {
  87.             log.error("execute rule error", e);
  88.             if (Objects.nonNull(executeOnErrorListener)) {
  89.                 executeOnErrorListener.onError(context.getContextData(), e);
  90.             }
  91.             throw e;
  92.         }
  93.     }
  94. }
复制代码
三、测试业务

假设现在必要机动的计算单个商品的代价折扣相关的内容。
3.1 新增折扣代价上下文实现类DiscountPriceContextData.java

  1. package com.song.tools.ruleengine;
  2. import com.song.tools.ruleengine.core.ContextData;
  3. import lombok.Data;
  4. import java.math.BigDecimal;
  5. import java.util.Objects;
  6. /**
  7. * 折扣价格上下文(对于单件商品,简单场景的计算)
  8. *
  9. * @author song tools
  10. */
  11. @Data
  12. public class DiscountPriceContextData implements ContextData {
  13.     /**
  14.      * 临时价格(存在多个计算规则时,使用它进行传递计算的中间值)
  15.      */
  16.     private BigDecimal calculatedTempPrice;
  17.     /**
  18.      * 原价
  19.      */
  20.     private BigDecimal originalPrice;
  21.     /**
  22.      * 折扣后价格
  23.      */
  24.     private BigDecimal discountedPrice;
  25.     @Override
  26.     public void validProperty() {
  27.         Objects.requireNonNull(originalPrice, "原价不能为空");
  28.     }
  29. }
复制代码
3.2 新增固定金额折扣规则实现类 FixedAmountDiscountRule.java

  1. package com.song.tools.ruleengine;
  2. import com.song.tools.ruleengine.core.Rule;
  3. import com.song.tools.ruleengine.core.RuleEngineContext;
  4. import java.math.BigDecimal;
  5. import java.math.RoundingMode;
  6. public class FixedAmountDiscountRule implements Rule<DiscountPriceContextData> {
  7.     private final BigDecimal discountAmount;
  8.     public FixedAmountDiscountRule(BigDecimal discountAmount) {
  9.         this.discountAmount = discountAmount;
  10.     }
  11.     @Override
  12.     public void execute(RuleEngineContext<DiscountPriceContextData> context) {
  13.         DiscountPriceContextData contextData = context.getContextData();
  14.         // 临时价格
  15.         BigDecimal calculatedTempPrice = contextData.getCalculatedTempPrice();
  16.         // 原始价格
  17.         BigDecimal originalPrice = contextData.getOriginalPrice();
  18.         if (calculatedTempPrice != null) {
  19.             originalPrice = calculatedTempPrice;
  20.         }
  21.         // 进行精确减法运算,并确保结果不会小于0
  22.         BigDecimal discountedPrice = (originalPrice.subtract(discountAmount).max(BigDecimal.ZERO)).setScale(2, RoundingMode.HALF_UP);
  23.         // 设置折扣后的价格
  24.         contextData.setDiscountedPrice(discountedPrice);
  25.         contextData.setCalculatedTempPrice(discountedPrice);
  26.     }
  27.     @Override
  28.     public void before(DiscountPriceContextData contextData) {
  29.         Rule.super.before(contextData);
  30.     }
  31.     @Override
  32.     public void after(DiscountPriceContextData contextData) {
  33.         Rule.super.after(contextData);
  34.     }
  35. }
复制代码
3.3 新增百分比折扣规则实现类 PercentageDiscountRule.java

  1. package com.song.tools.ruleengine;
  2. import com.song.tools.ruleengine.core.Rule;
  3. import com.song.tools.ruleengine.core.RuleEngineContext;
  4. import java.math.BigDecimal;
  5. import java.math.RoundingMode;
  6. public class PercentageDiscountRule implements Rule<DiscountPriceContextData> {
  7.     private static final BigDecimal ONE_HUNDRED = new BigDecimal("100");
  8.     private final BigDecimal discountPercentage;
  9.     public PercentageDiscountRule(BigDecimal discountPercentage) {
  10.         this.discountPercentage = discountPercentage;
  11.     }
  12.     @Override
  13.     public void execute(RuleEngineContext<DiscountPriceContextData> context) {
  14.         DiscountPriceContextData contextData = context.getContextData();
  15.         // 临时价格
  16.         BigDecimal calculatedTempPrice = contextData.getCalculatedTempPrice();
  17.         // 原始价格
  18.         BigDecimal originalPrice = contextData.getOriginalPrice();
  19.         if (calculatedTempPrice != null) {
  20.             originalPrice = calculatedTempPrice;
  21.         }
  22.         // 计算折扣率
  23.         BigDecimal discountRate = discountPercentage.divide(ONE_HUNDRED, 2, RoundingMode.HALF_UP);
  24.         // 计算折扣后的价格(四舍五入,保留2位小数)
  25.         BigDecimal discountedPrice = originalPrice.multiply(discountRate).setScale(2, RoundingMode.HALF_UP);
  26.         contextData.setDiscountedPrice(discountedPrice);
  27.         contextData.setCalculatedTempPrice(discountedPrice);
  28.     }
  29.     @Override
  30.     public void before(DiscountPriceContextData contextData) {
  31.         Rule.super.before(contextData);
  32.     }
  33.     @Override
  34.     public void after(DiscountPriceContextData contextData) {
  35.         Rule.super.after(contextData);
  36.     }
  37. }
复制代码
3.4 新增满减折扣规则实现类 FullReductionDiscountRule.java

  1. package com.song.tools.ruleengine;
  2. import com.song.tools.ruleengine.core.Rule;
  3. import com.song.tools.ruleengine.core.RuleEngineContext;
  4. import java.math.BigDecimal;
  5. import java.math.RoundingMode;
  6. /**
  7. * 满减折扣规则
  8. *
  9. * @author song tools
  10. */
  11. public class FullReductionDiscountRule implements Rule<DiscountPriceContextData> {
  12.     /**
  13.      * 满减金额,满xx金额数
  14.      */
  15.     private final BigDecimal fullReductionBoundAmount;
  16.     /**
  17.      * 满减折扣金额,减去xx金额数
  18.      */
  19.     private final BigDecimal fullReductionDiscountAmount;
  20.     public FullReductionDiscountRule(BigDecimal fullReductionBoundAmount, BigDecimal fullReductionDiscountAmount) {
  21.         this.fullReductionBoundAmount = fullReductionBoundAmount;
  22.         this.fullReductionDiscountAmount = fullReductionDiscountAmount;
  23.     }
  24.     public void execute(RuleEngineContext<DiscountPriceContextData> context) {
  25.         DiscountPriceContextData contextData = context.getContextData();
  26.         // 临时价格
  27.         BigDecimal calculatedTempPrice = contextData.getCalculatedTempPrice();
  28.         // 原始价格
  29.         BigDecimal originalPrice = contextData.getOriginalPrice();
  30.         if (calculatedTempPrice != null) {
  31.             originalPrice = calculatedTempPrice;
  32.         }
  33.         // 比较金额是否满足满减条件
  34.         if (originalPrice.compareTo(fullReductionBoundAmount) >= 0) {
  35.             BigDecimal discountedPrice = originalPrice.subtract(fullReductionDiscountAmount).setScale(2, RoundingMode.HALF_UP);
  36.             contextData.setDiscountedPrice(discountedPrice);
  37.             contextData.setCalculatedTempPrice(discountedPrice);
  38.         }
  39.     }
  40. }
复制代码
3.5 编写测试类 Test.java

编排差别的规则,不通的顺序,验证最终的执行结果。
  1. package com.song.tools.ruleengine;
  2. import com.song.tools.ruleengine.core.DefaultRuleEngine;
  3. import com.song.tools.ruleengine.core.RuleEngineContext;
  4. import com.song.tools.ruleengine.core.RuleEngineExecuteTemplate;
  5. import java.math.BigDecimal;
  6. import java.math.RoundingMode;
  7. /**
  8. * 测试规则执行
  9. *
  10. * @author song tools
  11. */
  12. public class Test {
  13.     public static void main(String[] args) throws Exception {
  14.         System.out.println("----------------test1----------------");
  15.         test1();
  16.         System.out.println("----------------test1----------------");
  17.         System.out.println("----------------test2----------------");
  18.         test2();
  19.         System.out.println("----------------test2----------------");
  20.     }
  21.     private static void test1() {
  22.         // 创建规则引擎
  23.         DefaultRuleEngine<DiscountPriceContextData> ruleEngine = new DefaultRuleEngine<>();
  24.         // 创建规则+注册规则
  25.         ruleEngine.registerRule("percentageDiscountRule1", new PercentageDiscountRule(BigDecimal.valueOf(95)));
  26.         ruleEngine.registerRule("fixedAmountDiscountRule1", new FixedAmountDiscountRule(BigDecimal.valueOf(12.34)));
  27.         ruleEngine.registerRule("percentageDiscountRule2", new PercentageDiscountRule(BigDecimal.valueOf(80)));
  28.         ruleEngine.registerRule("fixedAmountDiscountRule2", new FixedAmountDiscountRule(BigDecimal.valueOf(12.4)));
  29.         // 创建上下文数据
  30.         RuleEngineContext<DiscountPriceContextData> context = new RuleEngineContext<>();
  31.         DiscountPriceContextData discountPriceContextData = new DiscountPriceContextData();
  32.         discountPriceContextData.setOriginalPrice(BigDecimal.valueOf(100));
  33.         context.setContextData(discountPriceContextData);
  34.         // 执行规则引擎
  35.         ruleEngine.executeRule(context);
  36.         // 打印执行结果
  37.         System.out.println(discountPriceContextData.getDiscountedPrice());
  38.         System.out.println(BigDecimal.valueOf(100).multiply(BigDecimal.valueOf(0.95)).subtract(BigDecimal.valueOf(12.34)));
  39.         System.out.println(BigDecimal.valueOf(82.66).multiply(BigDecimal.valueOf(0.8).setScale(2, RoundingMode.HALF_UP)).subtract(BigDecimal.valueOf(12.4)).setScale(2, RoundingMode.HALF_UP));
  40.     }
  41.     private static void test2() throws Exception {
  42.         // 创建上下文数据
  43.         DiscountPriceContextData discountPriceContextData = new DiscountPriceContextData();
  44.         discountPriceContextData.setOriginalPrice(BigDecimal.valueOf(100));
  45.         // 创建规则引擎执行模板
  46.         RuleEngineExecuteTemplate<DiscountPriceContextData> ruleEngineExecuteTemplate = new RuleEngineExecuteTemplate<>();
  47.         // 注册规则&执行规则
  48.         ruleEngineExecuteTemplate.registerRule(new PercentageDiscountRule(BigDecimal.valueOf(95))) // 95折
  49.                 .registerRule(new FixedAmountDiscountRule(BigDecimal.valueOf(12.34))) // 减12.34元
  50.                 .registerRule(new PercentageDiscountRule(BigDecimal.valueOf(80))) // 80折
  51.                 .registerRule(new FullReductionDiscountRule(BigDecimal.valueOf(50), BigDecimal.valueOf(10))) // 满50减10
  52.                 .registerRule(new FixedAmountDiscountRule(BigDecimal.valueOf(12.4))) // 减12.4元
  53.                 .registerRule(new FullReductionDiscountRule(BigDecimal.valueOf(40), BigDecimal.valueOf(19.9))) // 满40减19.9元
  54.                 .execute(discountPriceContextData);
  55.         // 打印最终结果
  56.         System.out.println(discountPriceContextData.getDiscountedPrice());
  57.     }
  58. }
复制代码
3.6 测试类运行结果

  1. ----------------test1----------------
  2. 53.73
  3. 82.66
  4. 53.73
  5. ----------------test1----------------
  6. ----------------test2----------------
  7. 23.83
  8. ----------------test2----------------
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

来自云龙湖轮廓分明的月亮

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表