📖目录
- 引言
- [一、Easy Rules 基础知识](#一、Easy Rules 基础知识)
-
- [1.1 Easy Rules 是什么?](#1.1 Easy Rules 是什么?)
- [1.2 核心概念介绍](#1.2 核心概念介绍)
- [1.3 解耦业务逻辑的优势](#1.3 解耦业务逻辑的优势)
- 二、源码结构与示例分析
- 三、关键代码解析
-
- [3.1 AutoCreateFactory 工厂类详解](#3.1 AutoCreateFactory 工厂类详解)
- [3.2 核心功能](#3.2 核心功能)
-
- [3.2.1 Sign 标签分类机制](#3.2.1 Sign 标签分类机制)
- [3.2.2 工作原理](#3.2.2 工作原理)
- [3.3 使用工厂类创建规则的优势](#3.3 使用工厂类创建规则的优势)
- [3.4 关键代码](#3.4 关键代码)
- 四、运行效果
- 五、总结
- 六、福利资源
引言
在现代软件开发过程中,业务规则往往复杂且多变。为了应对这些挑战,规则引擎成为了一种有效的解决方案。本文将介绍如何使用 Easy Rules 框架,在 Spring Boot 项目中实现一个可扩展、易维护的业务规则管理系统。
一、Easy Rules 基础知识
1.1 Easy Rules 是什么?
Easy Rules 是一个简单而强大的 Java 规则引擎,它允许你定义和执行一系列业务规则。其核心思想是将复杂的业务逻辑从代码中分离出来,使得规则可以独立于应用程序代码进行管理和修改。
1.2 核心概念介绍
- Rule :表示一条具体的业务规则,通过
@Rule注解标识。 - Condition :定义了规则触发的条件,通过
@Condition注解标识的方法返回 boolean 值。 - Action :当条件满足时要执行的动作,通过
@Action注解标识。 - Priority :规则的优先级,通过
@Priority注解标识,数值越小优先级越高。 - RulesEngineParameters:用于配置规则引擎的行为参数,如是否跳过后续规则等。
1.3 解耦业务逻辑的优势
通过引入规则引擎,我们可以实现以下目标:
- 将业务规则与程序代码解耦
- 提高系统的灵活性和可维护性
- 支持动态加载和更新规则
- 易于测试和调试
二、源码结构与示例分析
本项目提供了两个示例来演示 Easy Rules 的不同应用场景:
示例一:基础用法展示
第一个示例是一个简单的规则应用案例,展示了如何使用 Easy Rules 定义基本的规则、设置条件和指定优先级。
核心代码实现
java
// Priority100RequestService.java
@Rule(name = "执行STEP_1", description = "执行第一步处理")
public class Priority100RequestService {
@Condition
public boolean condition() {
return true;
}
@Action
public void action(Facts facts) {
System.out.println("规则引擎执行STEP_1:开始处理");
String step1Input = (String) facts.get("STEP_1_INPUT");
System.out.println("STEP_1入参:" + step1Input);
// 设置STEP_2的输入参数
facts.put("STEP_2_INPUT", "来自STEP_1的数据");
System.out.println("规则引擎执行STEP_1:结束处理");
}
@Priority
public int priority() {
return 100;
}
}
java
// RuleController.java
@RestController
@RequestMapping("/api/rules")
public class RuleController {
@GetMapping("/execute")
public String executeRules() {
// 创建规则引擎参数
RulesEngineParameters parameters = new RulesEngineParameters()
.skipOnFirstAppliedRule(false);
// 创建规则引擎
RulesEngine rulesEngine = new DefaultRulesEngine(parameters);
// 创建规则集
Rules rules = new Rules();
rules.register(new Priority100RequestService());
rules.register(new Priority101RequestService());
// 创建事实
Facts facts = new Facts();
facts.put("STEP_1_INPUT", "初始输入数据");
// 执行规则
rulesEngine.fire(rules, facts);
// 返回最终结果
return facts.get("FINAL_OUTPUT");
}
}
示例二:高级集成模式
第二个示例更加贴近实际生产环境的需求,采用了工厂方法设计模式来创建规则实例,并结合 Spring 进行依赖注入管理。
关键组件说明
| 组件 | 描述 |
|---|---|
| AppCenter | 应用中心,负责协调整个规则执行流程,初始化和管理 |
| AutoCreateFactory | 工厂类,根据配置自动创建规则对象实例,支持注解扫描 |
| RobotAdapter | 机器人适配器,封装多个流程步骤,作为规则执行的入口点 |
| ProcessAdapter | 流程适配器,组织多个具体规则,管理规则引擎和规则集 |
| AdapterRule | 所有自定义规则需实现此接口 |
| AutoCreateFactory | 工厂类,基于@AutoCreate注解自动扫描和创建规则实例,支持按类型和标记分类进行实例化管理,实现规则的动态加载和配置 |
| RefUtils | 反射工具类,负责包扫描和类加载,支持从指定包路径下查找带有特定注解的类,为AutoCreateFactory提供底层类扫描支持 |
核心实现代码
规则接口:
java
public interface AdapterRule {
}
规则1:
java
// Priority100RequestService.java - 带注解的规则实现
@AutoCreate(value = "MyAppCode", sign = {"STEP_1", "DEFAULT"}, isSingleton = true)
@Rule(name = "执行STEP_1", description = "执行STEP_1")
public class Priority100RequestService implements AdapterRule {
@Condition
public boolean condition() {
return true;
}
@Action
public void action(Facts facts) {
System.out.println("执行STEP_1:开始");
String step1Input = (String) facts.get("STEP_1_INPUT");
System.out.println("STEP_1入参:" + step1Input);
facts.put("STEP_2_INPUT", "STEP_1出参=STEP_2入参");
System.out.println("执行STEP_1:结束");
}
@Priority
public int priority() {
return 100;
}
}
规则2:
java
@AutoCreate(value = "MyAppCode", sign = {"STEP_1", "DEFAULT"}, isSingleton = true)
@Rule(name = "执行STEP_1_1", description = "执行STEP_1_1")
public class Priority101RequestService implements AdapterRule {
@Condition
public boolean condition() {
return true;
}
@Action
public void action(Facts facts) {
System.out.println("执行STEP_1_1:开始");
String step2Input = (String) facts.get("STEP_2_INPUT");
System.out.println("STEP_2入参:" + step2Input);
facts.put("FINAL_OUTPUT", "STEP_2出参");
System.out.println("执行STEP_1_1:结束");
}
@Priority
public int priority() {
return 101;
}
}
规则应用中心:
java
// AppCenter.java - 应用中心
@Service
public class AppCenter {
private static final Map<String, RobotAdapter> APP_MAP = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
APP_MAP.put("MyAppCode", refresh());
}
private static final RulesEngineParameters rulesEngineParameters = new RulesEngineParameters()
.skipOnFirstAppliedRule(false)
.skipOnFirstFailedRule(true)
.skipOnFirstNonTriggeredRule(true);
private static final RulesEngine rulesEngine = new DefaultRulesEngine(rulesEngineParameters);
public RobotAdapter refresh() {
RobotAdapter robotAdapter = new RobotAdapter();
for(ProcessAdapterEnum adapterEnum : ProcessAdapterEnum.values()) {
Rules rules = new Rules();
String robotAdapterTemplateSign = adapterEnum.getCode();
String methodSign = "DEFAULT";
List<AdapterRule> adapterRules = AutoCreateFactory.getInstanceList(
AdapterRule.class, "MyAppCode", methodSign, robotAdapterTemplateSign);
rules.register(adapterRules.toArray());
ProcessAdapter processAdapter = ProcessAdapter.builder()
.appCode("appCode")
.rulesEngine(rulesEngine)
.rules(rules)
.build();
robotAdapter.getAdapters().put(adapterEnum, processAdapter);
}
return robotAdapter;
}
}
规则适配器:
JAVA
@Service
@Getter
public class RobotAdapter {
@Autowired
private AppCenter appCenter;
private final Map<ProcessAdapterEnum, ProcessAdapter> adapters = new EnumMap<>(ProcessAdapterEnum.class);
public <T, U> U adapter(ProcessAdapterEnum processAdapterService, T param) {
ProcessAdapter processAdapter = this.getAdapters().get(ProcessAdapterEnum.STEP_1);
if(processAdapter == null) {
throw new IllegalArgumentException("未注册的ProcessAdapterEnum:" + processAdapterService);
}
return processAdapter.adapter(param);
}
public <T, U> U adapter(ProcessAdapterEnum processAdapterService, T param, U returnType) {
U processAdapter = adapter(processAdapterService, param);
return processAdapter == null ? returnType : processAdapter;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
流程适配器:
JAVA
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProcessAdapter {
private String appCode;
private String businessLineId;
private String businessLineName;
private String robotId;
private RulesEngine rulesEngine;
private Rules rules;
public <T, U> U adapter(T param) {
Facts facts = new Facts();
facts.put("STEP_1_INPUT", "STEP_1入参");
rulesEngine.fire(rules, facts);
System.out.println("执行结果:" + facts.get("FINAL_OUTPUT"));
return facts.get("FINAL_OUTPUT");
}
}
控制器:
java
// AdapterController.java - 控制器调用
@RestController
@RequestMapping("/demos/adapter")
public class AdapterController {
@Autowired
private AppCenter appCenter;
@RequestMapping("/execute")
public String execute() {
RobotAdapter robotAdapter = appCenter.getRobotAdapter("MyAppCode");
robotAdapter.adapter(ProcessAdapterEnum.STEP_1, "入参");
return "";
}
}
三、关键代码解析
3.1 AutoCreateFactory 工厂类详解
AutoCreateFactory 是一个通用的自动创建工厂类,它提供了一种灵活的机制来动态创建和管理带有 @AutoCreate 注解的类实例。
3.2 核心功能
- 动态类扫描 : 利用 RefUtils 工具类扫描指定包路径下所有带有 @AutoCreate 注解的类
- 实例创建管理 : 根据类型 value 来创建和管理类实例
- 灵活的分类机制 : 支持通过 sign 关键词对实例进行分类和筛选
3.2.1 Sign 标签分类机制
@AutoCreate 属性提供了强大的分类功能,可以理解为给类实例"打标签":
java
@AutoCreate(value = "MyAppCode", sign = {"STEP_1", "DEFAULT"}, isSingleton = true)
在上面的例子中,** Priority100RequestService** 类被打上了两个标签:"STEP_1" 和 "DEFAULT"。
3.2.2 工作原理
- 注册阶段 : AutoCreateFactory 扫描所有带有 @AutoCreate 注解的类,并根据 value 和 sign 属性建立映射关系
- 查询阶段 : 当调用 getInstanceList 方法时,可以通过指定 type 和 signs 参数来筛选符合条件的实例
JAVA
List<AdapterRule> adapterRules = AutoCreateFactory.getInstanceList(
AdapterRule.class,
"MyAppCode", // 对应 @AutoCreate 的 value
"DEFAULT", // 对应 @AutoCreate 的 sign
robotAdapterTemplateSign // 另一个维度的 sign 筛选
);
- 匹配逻辑 : 工厂会查找同时满足以下条件的类:
- value 匹配指定的类型
- sign 包含所有指定的标签
3.3 使用工厂类创建规则的优势
- 解耦: 业务逻辑与具体实现类解耦,通过配置即可改变行为
- 可扩展: 新增规则只需添加带注解的类,无需修改现有代码
- 灵活筛选: 多维度的标签系统支持复杂的实例筛选需求
- 性能优化: 支持单例模式,避免重复创建实例
3.4 关键代码
注解,加到每个规则类的上方
JAVA
// 自动注册到工厂
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface AutoCreate {
/**
* 标记类型名称 支持多个
* 简单工厂[必须]指定创建类型名称自动创建实例
*/
String[] value();
/**
* 标记分类名称 支持多个
* 简单工厂[可以]指定标记分类名称自动创建实例
*/
String[] sign() default {};
boolean isSingleton() default false;
}
工厂类,自动扫描和创建规则服务的实例
JAVA
/**
* 简单的自动创建工厂
* 线程安全
* 支持自动扫描@AutoCreate注解的实现类生成通用工具
* 不需要在new对象时写繁琐的if else 判断语句
*/
@Slf4j
public class AutoCreateFactory {
private AutoCreateFactory() {
}
/**
* Map<Class名称,Map<type, Map<二级type,List<实例提供方法>>>>
*/
private static final Map<String, Map<String, Map<String, Set<Supplier<?>>>>> classzzCostructorMap = new ConcurrentHashMap<>();
/**
* 创建单个实例
* 默认扫描实例父类class所在的包
*/
public static <T> T getInstance(Class<?> clazz, String type, String... signs) {
List<T> list = getInstanceList(clazz, type, signs);
if(CollectionUtils.isEmpty(list)) {
return null;
}
return list.get(0);
}
public static <T> T getInstanceInPackage(Package scanPackage, Class<?> clazz, String type, String... signs) {
List<T> list = getInstanceListInPackage(scanPackage, clazz, type, signs);
if(CollectionUtils.isEmpty(list)) {
return null;
}
return list.get(0);
}
public static <T> List<T> getInstanceList(Class<?> clazz, String type, String... signs) {
return getInstanceListInPackage(clazz.getPackage(), clazz, type, signs);
}
private static <T> List<T> getInstanceListInPackage(Package scanPackage, Class<?> clazz, String type, String... signs) {
Assert.notNull(clazz, "auto create instance class can not be null");
Assert.notNull(type, "auto create instance type can not be null");
String scanPackageName = scanPackage.getName();
String key = scanPackageName + "-" + clazz.getSimpleName();
Map<String, Map<String, Set<Supplier<?>>>> constructorMap = classzzCostructorMap.computeIfAbsent(key, k -> getConstructorMap(scanPackageName, clazz));
Map<String, Set<Supplier<?>>> signConstructorMap = constructorMap.get(type);
if(signConstructorMap == null) {
return new ArrayList<>();
}
List<String> signList;
if(ArrayUtils.isEmpty(signs)) {
signList = new ArrayList<>();
signList.add(null);
} else {
signList = Arrays.asList(signs);
}
Set<Supplier<?>> signFitSet = Collections.emptySet();
for(int i = 0; i < signList.size(); i++) {
String sign = signList.get(i);
Set<Supplier<?>> supplierSet = signConstructorMap.computeIfAbsent(sign, k -> new HashSet<>());
if(i == 0) {
signFitSet = supplierSet;
} else {
signFitSet = Sets.intersection(signFitSet, supplierSet);
}
}
return signFitSet.stream().map(supplier -> (T) supplier.get()).collect(Collectors.toList());
}
/**
* 扫描所有标记了@AutoCreate注解的实践类并自动注册到工厂
*/
private static <T> Map<String, Map<String, Set<Supplier<?>>>> getConstructorMap(String scanPackage, Class<T> parentClazz) {
Map<String, Map<String, Set<Supplier<?>>>> constructorMap = new HashMap<>();
// 获取制定包路径下的所有实现类@AutoCreate注解的class子类
Set<Class<T>> allClazzSet = RefUtils.loadClasses(scanPackage, parentClazz, AutoCreate.class);
for(Class<? extends T> clazz : allClazzSet) {
try {
initConstructorMap(constructorMap, clazz);
} catch (Exception e) {
log.error("", e);
}
}
return Collections.unmodifiableMap(constructorMap);
}
private static <T> void initConstructorMap(Map<String, Map<String, Set<Supplier<?>>>> constructorMap, Class<? extends T> clazz) throws Exception {
AutoCreate autoCreate = clazz.getAnnotation(AutoCreate.class);
Constructor<? extends T> constructor = clazz.getDeclaredConstructor () ;
if(!constructor.isAccessible()) {
constructor.setAccessible(true);
}
Supplier<?> consumer = getSupplier(autoCreate, constructor);
String[] types = autoCreate.value();
String[] signs = autoCreate.sign();
for(String type: types) {
boolean isInitNulSign = true;
for(String sign : signs) {
Map<String, Set<Supplier<?>>> signMap = constructorMap.computeIfAbsent(type, k -> new HashMap<>());
signMap.computeIfAbsent(sign, k -> new HashSet<>()).add(consumer);
if (sign == null) {
isInitNulSign = false;
}
}
if(isInitNulSign) {
Map<String, Set<Supplier<?>>> signMap = constructorMap.computeIfAbsent(type, k -> new HashMap<>());
signMap.computeIfAbsent(null, key -> new HashSet<>()).add(consumer);
}
}
}
// 获取父类
private static <T> Supplier<?> getSupplier(AutoCreate autoCreate, Constructor<? extends T> constructor) throws InstantiationException, IllegalAccessException, InvocationTargetException {
Supplier<?> consumer;
if(autoCreate.isSingleton()) {
final T singletonInstance = constructor.newInstance();
// 单例
consumer = () -> singletonInstance;
} else {
// 原型
consumer = () -> {
try {
//T instance = constructor.newInstance();
//autowiredProcess(instance, instance.getClass());
//return instance;
return constructor.newInstance();
} catch (Exception e) {
log.warn(e.getMessage());
}
return null;
};
}
return consumer;
}
}
反射工具类,负责包扫描和类加载
JAVA
public class RefUtils {
public static <T, A extends Annotation> Set<Class<T>> loadClasses(String pack, Class<T> parentClazz, Class<A> annotationClass) {
Set<Class<T>> clazzSet = new LinkedHashSet<>();
Set<Class<T>> allClazzSet = loadClasses(pack);
for (Class<T> clazz : allClazzSet) {
try {
if(parentClazz != null && !parentClazz.isAssignableFrom(clazz)) {
continue;
}
if(annotationClass != null && clazz.getAnnotation(annotationClass) == null) {
continue;
}
clazzSet.add(clazz);
} catch (Exception e) {
e.printStackTrace();
}
}
return clazzSet;
}
public static <T> Set<Class<T>> loadClasses(String pack) {
Set<Class<T>> classes = new LinkedHashSet<>();
boolean recursive = true;
String packageDirName = pack.replace('.', '/');
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String protocol = url.getProtocol();
if ("file".equals(protocol)) {
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
findClassesInPackageByFile(pack, filePath, recursive, classes);
}
if("jar".equals(protocol)) {
findClassesInPackageByJar(url, pack, packageDirName, recursive, classes);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return classes;
}
private static <T> void findClassesInPackageByFile(String packageName, final String packagePath, boolean recursive, Set<Class<T>> classes) {
java.io.File dir = new java.io.File(packagePath);
if (!dir.exists() || !dir.isDirectory()) {
return;
}
java.io.File[] dirfiles = dir.listFiles(file -> (recursive && file.isDirectory()) || (file.getName().endsWith(".class")));
for(File file : dirfiles) {
if (file.isDirectory()) {
findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
} else {
String className = file.getName().substring(0, file.getName().length() - 6);
try {
String classPath = packageName + '.' + className;
classes.add(loadClass(classPath));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
private static <T> void findClassesInPackageByJar(URL url, String packageName, String packageDirName, final boolean recursive, Set<Class<T>> classes) {
try {
java.util.jar.JarFile jar = ((java.net.JarURLConnection) url.openConnection()).getJarFile();
java.util.Enumeration<java.util.jar.JarEntry> entries = jar.entries();
while(entries.hasMoreElements()) {
packageDirName = findClassesByOneJar(packageName, packageDirName, recursive, classes, entries.nextElement());
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static <T> String findClassesByOneJar(String packageName, String packageDirName, boolean recursive, Set<Class<T>> classes, JarEntry jarEntry) {
String className = jarEntry.getName();
if (className.charAt(0) == '/') {
className = className.substring(1);
}
if(className.startsWith(packageDirName)) {
int idx = className.lastIndexOf('/');
if (idx != -1) {
packageName = className.substring(0, idx).replace('/', '.');
}
if(((idx != -1) && recursive) && className.endsWith(".class") && !jarEntry.isDirectory()) {
String classNameNew = className.substring(packageName.length() + 1, className.length() - 6);
try {
String classPath = packageName + '.' + classNameNew;
classes.add(loadClass(classPath));
} catch (Exception e) {
e.printStackTrace();
}
}
}
return packageName;
}
public static <T> Class<T> loadClass(String classPath) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Method forNameMethod = Class.class.getDeclaredMethod("forName", String.class, boolean.class, ClassLoader.class);
Class<T> clazz = (Class<T>) forNameMethod.invoke(null, classPath, false, classLoader);
return clazz;
}
}
四、运行效果
通过访问对应的接口:
- 示例一:
http://localhost:8080/api/rules/execute - 示例二:
http://localhost:8080/demos/adapter/execute
可以观察到规则按照预设的优先级顺序执行,并且能够正确传递参数和处理业务逻辑。
五、总结
本文通过对 Easy Rules 在 Spring Boot 中的应用进行了深入探讨,不仅介绍了其基本概念及工作原理,还给出了两种典型的应用场景及其对应的实现方案。希望读者能够借此掌握规则引擎的设计思路和技术要点,在今后的实际工作中加以运用。
六、福利资源
完整的代码已上传为附件,可在Intellij IDEA中导入并直接运行,欢迎交流讨论。如果你对该项目感兴趣或者有任何疑问,或者有更好的实现方案,欢迎留言交流!