前言
接手了一个运维项目,查看其中的代码,中间有接近 100 个 if else 判断,全部采用硬编码的方式。代码特别长,规则十分混乱,不好管理。
- 如何处理这些问题?
- 采用什么方式,将业务规则抽象出来?
我建议采用 Easy Rules 规则引擎。不光可以使代码变得优雅,同时还可以降低项目的运维难度,提升项目的健壮性。
我将从如下 4 点进行分析:
- Easy Rules 是什么?它的原理是什么?涉及概念解释。
- Easy Rules 的原理是什么?
- Easy Rules 怎么使用?如何创建 rule、fact 等?
- 手把手写 Demo 使用 Easy Rules 规则引擎。
Easy Rules 是什么?涉及概念解释
Easy rules 是什么?
Easy Rules 是一款开源的 Java 规则引擎,她将业务规则抽象出来统一管理,让复杂的多变的业务逻辑变得业务清晰。她支持简单规则创建组合规则、支持表达式语言、简单易用的 API、基于 POJO 的注解式编程模型。
规则引擎的目的就是要以松散灵活的方式来替代硬编码式的方式,使代码逻辑变得清晰、松耦合。告别传统 if else 判断的模式。
Easy Rules 的组成部分
事实(Fact)
业务数据,例如:我们 Java 里面的 bean 对象实例。
Product product = new Product();
product 这个对象就是一个事实对象。
规则(Rule)
业务规则,包含业务条件和满足条件后的结果。
例如:满 1000 元送 200 元优惠券,满 500 元,送 100 元优惠券,少于 200 则不送任何优惠。这 就是一条业务规则。
规则引擎(Rule Engine)
它的作用就是执行规则,将事实 fact 和规则 rule 都加载到规则引擎后,规则引擎将两者进行匹配,得到最后的行动结果 action。
规则监听(Rule Listener)
它的目的是监听规则的执行的效果。
注解名词解释
- name:名称,规则名称。名称必须 唯一不能重复 。
- Description:规则的描述,有利于开发者理解规则的含义。
- Priority:优先级,给规则设定优先级别,0 的优先级高于 1。
- Conditions:条件,业务规则里面的一种条件。通常会有多个业务规则组合。
- Actions:动作,满足 某个业务规则后,会执行的动作。
Easy Rules 的原理是什么?
原理:
- 创建事实库,将业务对象添加到事实库里面。
- 创建规则库,加载业务 rule 到内存当中。
- 规则引擎,将事实库和规则库进行匹配,获取符合要求的规则。
- 执行器执获取到匹配的规则后,执行规则中的 action。
规则引擎通过匹配规则库和事实库,挑选出对应的规则,最后执行对应的 action。如下图:
Easy Rules 怎么使用?如何创建 rule、fact 等?
创建一个默认规则引擎
1. 先创建引擎参数 RulesEngineParameters
- priorityThreshold 属性:大于指定优先级的规则不执行
- skipOnFirstAppliedRule 属性:默认为 false,如果为 true 从一条开始,只执行匹配成功的第一条。
- skipOnFirstFailedRule 属性:默认为 false,如果为 true,规则中抛出异常,就会停止执行后续规则。
- skipOnFirstNonTriggeredRule 属性:默认为 false,如果为 true,从第一条开始匹配,匹配成功才会继续执行后续规则。
2. 再创建默认引擎,并传递入参
RulesEngineParameters parameters = new RulesEngineParameters()
.priorityThreshold(1000000)
.skipOnFirstAppliedRule(true)
.skipOnFirstFailedRule(true)
.skipOnFirstNonTriggeredRule(true);
rulesEngine = new DefaultRulesEngine(parameters);
创建 facts
facts 实际就是我们的业务对象。
我们创建了一个商品对象,并且定义了商品名称和价格。
/**
* 创建一个商品对象作为 fact,其中包含商品名称和价格
*/
public class Product {
private String productName;
private BigDecimal prices;
public String getProductName() {
return productName;
}
public BigDecimal getPrices() {
return prices;
}
public void setProductName(String productName) {
this.productName = productName;
}
public void setPrices(BigDecimal prices) {
this.prices = prices;
}
}
定义规则
定义规则可以有多种方式,如下挨个介绍。我们可以根据项目情况,选择适合自己的方式。
第一个方式:注解方式定义一个规则
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
@Rule(name = "rules01", description = "1 号规则-满 100 元,赠送 20 元购物券")
public class Rules01 {
/**
* 判断条件是否满足
*/
@Condition
public boolean productPrice(@Fact("product")Product product) {
return product.getPrices().doubleValue() > 100;
}
/**
* 执行行动
*/
@Action
public void send() {
System.out.println(">>>>>>>>>>>>>满 100 元,赠送 20 元购物券");
}
}
第二个方式:链式编程的方式定义规则
public static void main(String[] args) {
//创建规则 02
Rule Rule02 = new RuleBuilder()
.name("rule02")
.description("规则 02-满 300 元,送 40 的优惠券")
.when(facts -> {
Product product = (Product)facts.get("product");
return product.getPrices().doubleValue()>=300;
})
.then(facts -> {
Product product = (Product)facts.get("product");
System.out.println("商品名称:"+product.getProductName()+",优惠价格是:"+product.getPrices().add(new BigDecimal(-40)));
}).build();
//创建规则 03
Rule Rule03 = new RuleBuilder()
.name("rule03")
.description("规则 03-满 500 元,送 80 的优惠券")
.when(facts -> {
Product product = (Product)facts.get("product");
return product.getPrices().doubleValue()>=500;
})
.then(facts -> {
Product product = (Product)facts.get("product");
System.out.println("商品名称:"+product.getProductName()+",优惠价格是:"+product.getPrices().add(new BigDecimal(-80)));
}).build();
//添加事实对象
Facts facts = new Facts();
Product product = new Product();
product.setPrices(new BigDecimal(500));
product.setProductName("衣服");
facts.put("product", product);
//注册规则
Rules rules = new Rules();
rules.register(Rule02);
rules.register(Rule03);
//规则引擎执行判断
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, facts);
}
第三个方式:yml 配置文件的方式获取规则
name: "rule4"
description: "商品的规则 4"
condition: "product.prices >= 600"
actions:
- "System.out.println(\"商品价格大于 600,优惠 100 元\");"
main 方法测试:
public static void main(String[] args) throws Exception {
//读取规则 yml 配置文件
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
Rule rule04 = ruleFactory.createRule(new FileReader("D:\\work\\rules\\src\\main\\resources\\rules\\rule4.yml"));
//创建 fact
Product product = new Product();
product.setPrices(new BigDecimal(600));
product.setProductName("裤子");
Facts facts = new Facts();
facts.put("product", product);
//创建规则
Rules rules = new Rules();
rules.register(rule04);
//执行器执行规则
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, facts);
}
手把手写 Demo 使用 Easy Rules 规则引擎
项目目录结构如下:主要是规则引擎工具类、实体 fact 类,以及规则目录。
第一步,采用 Spring 初始化项目的方式,创建一个 Spring Boot 项目,增加 Maven 依赖。
Maven 依赖:
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-support</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-mvel</artifactId>
<version>4.0.0</version>
</dependency>
第二步,创建规则引擎工具类,它的目的是读取规则,并实例化。创建规则引擎。
示例代码如下:
public class RuleEngineUtil {
/**
* 创建默认规则引擎执行器
*/
private static final RulesEngine rulesEngine;
static {
RulesEngineParameters parameters = new RulesEngineParameters()
//.priorityThreshold(100) //大于指定优先级的规则不执行。
.skipOnFirstAppliedRule(true)//默认为 false,如果为 true 从一条开始,只执行匹配成功的第一条。
.skipOnFirstFailedRule(true);//默认为 false,如果为 true,规则中抛出异常,就会停止执行后续规则。
//.skipOnFirstNonTriggeredRule(true);//默认为 false,如果为 true,从第一条开始匹配,匹配成功才会继续执行后续规则。
rulesEngine = new DefaultRulesEngine(parameters);
}
/**
* 规则的包路径
*/
private static final String PACKAGE_NAME = "com.xx.rules.rule";
/**
* 初始化规则库
*/
public static Rules rules = handleRules();
/**
* 执行规则判断
*/
public static void executeEngine(Facts facts){
//规则引擎执行
rulesEngine.fire(rules, facts);
}
/**
* 初始化规则库
*/
private static Rules handleRules(){
Rules rules = new Rules();
List<String> list = getRulesNameList();
for (String name : list) {
BaseRule baseRule = manualInitialize(name);
rules.register(baseRule);
}
return rules;
}
/**
* 获取所有规则子类的类名
*/
private static List<String> getRulesNameList() {
Reflections reflections = new Reflections(PACKAGE_NAME);
Set<Class<? extends BaseRule>> classes = reflections.getSubTypesOf(BaseRule.class);
for (Class<? extends BaseRule> config: classes) {
nameList.add(config.getName());
}
return nameList;
}
/**
* 通过反射的方式获取 rule 规则对象
*/
private static BaseRule manualInitialize(String fullClassName) {
BaseRule baseRule = null;
try {
Class clazz = Class.forName(fullClassName);
Constructor[] constructors = clazz.getDeclaredConstructors();
AccessibleObject.setAccessible(constructors, true);
for (Constructor con : constructors) {
if (con.isAccessible()) {
baseRule = (BaseRule)con.newInstance();
System.out.println(baseRule);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return baseRule;
}
}
第三步,创建业务类,也就是 fact 类。
代码如下:
//商品类
public class Product {
private String productName;
private BigDecimal prices;
private BigDecimal res;
public Product(String productName, BigDecimal prices, BigDecimal res) {
this.productName = productName;
this.prices = prices;
this.res = res;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public BigDecimal getPrices() {
return prices;
}
public void setPrices(BigDecimal prices) {
this.prices = prices;
}
public BigDecimal getRes() {
return res;
}
public void setRes(BigDecimal res) {
this.res = res;
}
}
购物车类:
/**
* 购物车
*/
public class ShoppingCart {
/**
* 总结算价格
*/
private BigDecimal sumPrice = new BigDecimal(0);
/**
* 用户
*/
private User user;
/**
* 商品
*/
private List<Product> productList;
public BigDecimal getSumPrice() {
return sumPrice;
}
public void setSumPrice(BigDecimal sumPrice) {
this.sumPrice = sumPrice;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public List<Product> getProductList() {
return productList;
}
public void setProductList(List<Product> productList) {
this.productList = productList;
}
}
用户类:
/**
* 用户
*/
public class User {
private String name;
private String sex;
public User(String name, String sex) {
this.name = name;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
第四步,创建规则、规则类。
规则基类:
public interface BaseRule {
static final String SHOPPING_CART = "shoppingCart";
boolean productPrice(@Fact(SHOPPING_CART) ShoppingCart shoppingCart);
void send(@Fact(SHOPPING_CART) ShoppingCart shoppingCart);
}
@Rule(name = "rules01", description = "1 号规则-男士,商品数量满 3 件,总价减少 20 元",priority= 1)
@Component
public class BaseRule01 implements BaseRule {
/**
* 判断条件是否满足
*/
@Condition
public boolean productPrice(@Fact(SHOPPING_CART) ShoppingCart shoppingCart) {
int size = shoppingCart.getProductList().size();
User user = shoppingCart.getUser();
return size >= 3 && user.getSex().equals("男");
}
/**
* 执行行动
*/
@Action
public void send(@Fact(SHOPPING_CART) ShoppingCart shoppingCart) {
System.out.println("执行了-1 号规则-男士,商品数量满 3 件,总价减少 20 元");
List<Product> productList = shoppingCart.getProductList();
BigDecimal sum = new BigDecimal(0);
for(Product product : productList){
sum = sum.add(product.getPrices());
}
shoppingCart.setSumPrice(sum);
BigDecimal sumPrice = shoppingCart.getSumPrice();
shoppingCart.setSumPrice(sumPrice.subtract(new BigDecimal(20)));
}
}
@Rule(name = "rules02", description = "2 号规则-女士,商品数量满 4 件,总价减少 40 元",priority= 2)
@Component
public class BaseRule02 implements BaseRule {
/**
* 判断条件是否满足
*/
@Condition
public boolean productPrice(@Fact(SHOPPING_CART) ShoppingCart shoppingCart) {
int size = shoppingCart.getProductList().size();
return size >= 4 && shoppingCart.getUser().getSex().equals("女");
}
/**
* 执行行动
*/
@Action
public void send(@Fact(SHOPPING_CART) ShoppingCart shoppingCart) {
System.out.println("执行了-2 号规则-女士,商品数量满 4 件,总价减少 40 元");
List<Product> productList = shoppingCart.getProductList();
BigDecimal sum = new BigDecimal(0);
for(Product product : productList){
sum = sum.add(product.getPrices());
}
shoppingCart.setSumPrice(sum);
BigDecimal sumPrice = shoppingCart.getSumPrice();
shoppingCart.setSumPrice(sumPrice.subtract(new BigDecimal(20)));
}
}
@Rule(name = "rules03", description = "3 号规则-女士商品价格满 500 元,赠送 60 元购物券",priority= 3)
@Component
public class BaseRule03 implements BaseRule {
/**
* 判断条件是否满足
*/
@Condition
public boolean productPrice(@Fact(SHOPPING_CART) ShoppingCart shoppingCart) {
List<Product> productList = shoppingCart.getProductList();
BigDecimal sum = new BigDecimal(0);
for(Product product : productList){
sum = sum.add(product.getPrices());
}
shoppingCart.setSumPrice(sum);
BigDecimal sumPrice = shoppingCart.getSumPrice();
return sumPrice.doubleValue() >= 500 && shoppingCart.getUser().getSex().equals("女");
}
/**
* 执行行动
*/
@Action
public void send(@Fact(SHOPPING_CART) ShoppingCart shoppingCart) {
System.out.println("执行了-3 号规则-女士商品价格满 500 元,赠送 60 元购物券");
List<Product> productList = shoppingCart.getProductList();
BigDecimal sum = new BigDecimal(0);
for(Product product : productList){
sum = sum.add(product.getPrices());
}
shoppingCart.setSumPrice(sum);
BigDecimal sumPrice = shoppingCart.getSumPrice();
shoppingCart.setSumPrice(sumPrice.subtract(new BigDecimal(60)));
}
}
最后,执行测试。
测试类:
public class TestRules {
public static void main(String[] args) throws Exception {
ShoppingCart shoppingCart= mockData01();
Facts facts = new Facts();
facts.put(BaseRule.SHOPPING_CART, shoppingCart);
RuleEngineUtil.executeEngine(facts);
System.out.println("规则处理后:"+shoppingCart.getSumPrice());
}
/**
* 模拟数据 1
*/
public static ShoppingCart mockData01(){
ShoppingCart shoppingCart = new ShoppingCart();
//购物车添加商品
List<Product> productList = new ArrayList<>();
Product product = new Product("裤子",new BigDecimal(200),new BigDecimal(0));
Product product1 = new Product("鞋子",new BigDecimal(300),new BigDecimal(0));
Product product2 = new Product("衣服",new BigDecimal(400),new BigDecimal(0));
Product product3 = new Product("裙子",new BigDecimal(200),new BigDecimal(0));
productList.add(product);
productList.add(product1);
productList.add(product2);
productList.add(product3);
shoppingCart.setProductList(productList);
//设置用户为 李三
User user = new User("李三", "女");
shoppingCart.setUser(user);
return shoppingCart;
}
/**
* 模拟数据 1
*/
public static ShoppingCart mockData02(){
ShoppingCart shoppingCart = new ShoppingCart();
//购物车添加商品
List<Product> productList = new ArrayList<>();
Product product = new Product("裤子",new BigDecimal(200),new BigDecimal(0));
Product product1 = new Product("鞋子",new BigDecimal(300),new BigDecimal(0));
Product product3 = new Product("裙子",new BigDecimal(200),new BigDecimal(0));
productList.add(product);
productList.add(product1);
productList.add(product3);
shoppingCart.setProductList(productList);
//设置用户为 李三
User user = new User("李四", "男");
shoppingCart.setUser(user);
return shoppingCart;
}
}
执行结果
先会打印规则信息、参数,运行结果:
17:12:22.661 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Engine parameters { skipOnFirstAppliedRule = true, skipOnFirstNonTriggeredRule = false, skipOnFirstFailedRule = true, priorityThreshold = 2147483647 }
17:12:22.661 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Registered rules:
17:12:22.662 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Rule { name = 'rules01', description = '1 号规则-男士,商品数量满 3 件,总价减少 20 元', priority = '1'}
17:12:22.664 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Rule { name = 'rules02', description = '2 号规则-女士,商品数量满 4 件,总价减少 40 元', priority = '2'}
17:12:22.664 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Rule { name = 'rules01', description = '3 号规则-女士商品价格满 500 元,赠送 60 元购物券', priority = '3'}
17:12:22.664 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Known facts:
17:12:22.664 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Fact{name='shoppingCart', value=com.xx.rules.entity.ShoppingCart@48eff760}
17:12:22.664 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Rules evaluation started
17:12:22.667 [main] DEBUG org.jeasy.rules.core.DefaultRulesEngine - Rule 'rules01' triggered
执行了-1 号规则-男士,商品数量满 3 件,总价减少 20 元
规则处理后:680
总结
相比于 Drools 来说,Easy Rules 的规则相对简单。在项目中,我也喜欢简单直接的利用 注解的方式来创建规则。项目的目录里面,我也会统一一个目录存放规则文件。
这样做的目的,将业务规则统一抽象出来,统一管理。 当产品要求我更换规则,我就不需要从复杂的 Service 层里面去寻找 if else。上万行的代码去找某一个逻辑,难度很大,而且还容易出错。
为了代码的安全,为了逻辑的清晰,为了维护人员的脑细胞。建议大家复杂的逻辑,都采用规则引擎的方式来实现,体验它的魅力。
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站不拥有所有权,不承担相关法律责任。如发现有侵权/违规的内容, 联系QQ15101117,本站将立刻清除。