easy-rules spring boot集成使用
以下是一个easy-rules 与spring boot集成的一个简单demo,主要目的是简单的集成以及一些集成
上的一些思考
项目准备
- 项目结构
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── appdemo
│ │ └── demo
│ │ ├── ConfigRules.java
│ │ ├── DemoApplication.java
│ │ ├── MyRule.java
│ │ ├── MyRuleEngineListener.java
│ │ ├── MyRulesListener.java
│ │ ├── MyService.java
│ │ ├── ProtoRulesEngineFactoryBean.java
│ │ ├── RulesEngineFactoryBean.java
│ │ ├── SimpleBeanResovler.java
│ │ ├── SpringBeanUtil.java
│ │ ├── User.java
│ │ ├── UserApi.java
│ │ └── UserService.java
│ └── resources
│ ├── application.properties
│ ├── rules-json.json
│ ├── rules-json2.json
│ ├── static
│ └── templates
- 简单说明
就是一个基于spring boot 的rest 项目,基于easy-rules 规则处理请求体中的数据,对于请求体中的数据进行重写
代码说明
- pom.xml
主要是依赖包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.appdemo</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-mvel</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-spel</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- rule 配置
基于json 的配置加载,同时集成了mvel 表达式引擎,可以支持灵活的基于脚本语言的数据操作
rules-json2.json:
[{
"name": "1",
"description": "1",
"priority": 1,
"compositeRuleType": "UnitRuleGroup",
"composingRules": [
{
"name": "2",
"description": "2",
"condition": "user.getAge()<28",
"priority": 2,
"actions": [
"System.out.println(\"UnitRuleGroup rule2 \")"
]
},
{
"name": "3",
"description": "3",
"condition": "user.name.length<10",
"priority": 3,
"actions": [
"System.out.println(\"UnitRuleGroup rule3 \")"
]
},
{
"name": "4",
"description": "4",
"condition": "user.name.length<10",
"priority": 4,
"actions": [
"System.out.println(\"UnitRuleGroup rule4 \")"
]
},
{
"name": "5",
"description": "5",
"condition": "user.name.length<10",
"priority": 5,
"actions": [
"System.out.println(\"UnitRuleGroup rule5 \");UserService.doAction4(user.userinfo)"
]
}
]},
{
"name": "3",
"description": "3",
"condition": "user.name.length<50",
"priority": 3,
"actions": [
"UserService.appendName(user)"
]
},
{
"name": "9",
"description": "9",
"condition": "user.name.length<50",
"priority": 3,
"actions": [
"System.out.println(\"default rule3 \")"
]
}
]
- 实体说明
User 实体,就是一个普通的pojo,同时为了方便基于rule以及规则数据的监控,添加了一个唯一id,
唯一id 的目的是我们可以基于添加的监听事件,可视化以及分析我们的业务流程数据以及状态(类似opentracing)
User.java
package com.appdemo.demo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.io.Serializable;
import java.util.Map;
public class User implements Serializable {
private String name;
private int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", uniqueId='" + uniqueId + '\'' +
", userinfo=" + userinfo +
'}';
}
public String getUniqueId() {
return uniqueId;
}
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
@JsonIgnore
private transient String uniqueId;
public Map<Object, Object> getUserinfo() {
return userinfo;
}
public void setUserinfo(Map<Object, Object> userinfo) {
this.userinfo = userinfo;
}
private Map<Object, Object> userinfo;
public String getName() {
return name;
}
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
- 数据服务协助类
主要是暴露静态类,方便操作spring bean以及实体模型
UserService.java
package com.appdemo.demo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class UserService {
static Log log = LogFactory.getLog(UserService.class);
public static void doAction1(User user){
log.info("------------do action1------------");
log.info(user.toString());
}
public static void doAction2(User user){
log.info("------------do action2------------");
user.setName("sssssssssssssssssss");
log.info(user.toString());
}
public static void doAction3(Map user){
log.info("------------do action3------------");
log.info(user.toString());
}
public static void appendName(User user){
/**
* 通过静态全局ApplicationConext 获取bean引用
*/
MyService myService =SpringBeanUtil.getBean(MyService.class);
myService.setInfo(user);
}
public static void doAction4(Object user){
/**
* 基于mvel 修改数据,为了复用doAction4,做了数据兼容处理
*/
if (user instanceof Map ){
log.info("------------do action4------------");
((Map)user).put("name2","rule do action4");
}
if (user instanceof User) {
((User)user).setName("dalong demo appapapappapa");
}
log.info(user.toString());
}
}
- spring beas 工具类
方便引用applicationcontext
SpringBeanUtil.java
package com.appdemo.demo;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringBeanUtil implements ApplicationContextAware {
/**
* 上下文对象实例
*/
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 获取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取 Bean.
*
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
- 创建的数据操作bean
MyService.java
package com.appdemo.demo;
import org.springframework.stereotype.Component;
/**
* @author dalonng
*
* mybean for ref
*/
@Component
public class MyService {
public void setInfo(User user){
user.setName("rongfengliang");
}
}
- rule 引擎的一些监听方法
主要是MyRuleEngineListener.java以及MyRulesListener.java具体参考下边链接查看源码 - 规则加载
因为方便规则的处理以及与spring 的集成,提取了一个规则配置bean
ConfigRules.java
package com.appdemo.demo;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.mvel.MVELRuleFactory;
import org.jeasy.rules.support.JsonRuleDefinitionReader;
import org.mvel2.ParserContext;
import org.springframework.stereotype.Component;
import java.io.FileNotFoundException;
import java.io.FileReader;
/**
* @author dalong
* 加载rule文件
*/
@Component
public class ConfigRules {
public Rules fetchConfigRules() throws Exception {
MVELRuleFactory ruleFactory = new MVELRuleFactory(new JsonRuleDefinitionReader());
ParserContext context =new ParserContext();
context.addImport("UserService", UserService.class);
Rules jsonRules = ruleFactory.createRules(new FileReader(DemoApplication.class.getClassLoader().getResource("rules-json2.json").getFile()),context);
return jsonRules;
}
}
- ruleEngine bean
为了方便处理以及线程安全,我们使用了原型bean
ProtoRulesEngineFactoryBean.java
package com.appdemo.demo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.springframework.beans.factory.FactoryBean;
/**
* @author dalong
* 原型RulesEngine FactoryBean
*/
public class ProtoRulesEngineFactoryBean implements FactoryBean<RulesEngine> {
Log log = LogFactory.getLog(MyRulesListener.class);
public RulesEngine init() {
log.info("create rule Engine");
DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.registerRuleListener(new MyRulesListener());
rulesEngine.registerRulesEngineListener(new MyRuleEngineListener());
return rulesEngine;
}
@Override
public RulesEngine getObject() throws Exception {
return this.init();
}
@Override
public Class<?> getObjectType() {
return RulesEngine.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
- spring boot 应用入口
DemoApplication.java
package com.appdemo.demo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
@SpringBootApplication
public class DemoApplication {
Log log = LogFactory.getLog(MyRulesListener.class);
@Bean(name = "myEngine")
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public RulesEngine myEngine(MyRulesListener myRulesListener,MyRuleEngineListener myRuleEngineListener) {
log.info("create rule Engine");
DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.registerRuleListener(myRulesListener);
rulesEngine.registerRulesEngineListener(myRuleEngineListener);
return rulesEngine;
}
@Bean("myEngine2")
ProtoRulesEngineFactoryBean rulesEngineFactoryBean()
{
return new ProtoRulesEngineFactoryBean();
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- 获取rule 结果的帮助rule
rule的目的比较简单,因为很多时候我们需要获取结果数据,所以规则运行的时候会注册一个帮助rule,方便获取结果数据
MyRule.java
package com.appdemo.demo;
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 = "100", description = "my rule description", priority = 100)
public class MyRule<T> {
private boolean executed;
private T result;
@Condition
public boolean when(@Fact("user") T fact) {
//my rule conditions
return true;
}
@Action(order = 1)
public void then(@Fact("user") T facts) throws Exception {
try {
System.out.println("my rule has been executed");
result = (T)facts; // assign your result here
executed = true;
} catch (Exception e) {
// executed flag will remain false if an exception occurs
throw e;
}
}
@Action(order = 2)
public void finaly() throws Exception {
try {
System.out.println("all is ok ");
} catch (Exception e) {
// executed flag will remain false if an exception occurs
throw e;
}
}
public boolean isExecuted() {
return executed;
}
public T getResult() {
return result;
}
}
- rest api
一个简单的规则引擎使用的方法
UserApi.java
package com.appdemo.demo;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.mvel.MVELRuleFactory;
import org.jeasy.rules.support.JsonRuleDefinitionReader;
import org.mvel2.ParserContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.Random;
import java.util.UUID;
@RestController
public class UserApi {
@Autowired
RulesEngine myEngine;
@Autowired
ConfigRules configRules;
@RequestMapping(value = "/", method = RequestMethod.POST)
public Object info(@RequestBody User user) throws Exception {
Rules rules = configRules.fetchConfigRules();
Facts facts = new Facts();
// 生成一个唯一id,方便基于数据id规则流程查询
user.setUniqueId(UUID.randomUUID().toString());
MyRule<User> rule = new MyRule<User>();
rules.register(rule);
facts.put("user",user);
// 默认模式
// myEngine.fire(rules,facts);
// 应该使用原型模式
SpringBeanUtil.getBean("myEngine",RulesEngine.class).fire(rules,facts);
if(rule.isExecuted()){
User userResult= rule.getResult();
System.out.println("result from final ruls"+userResult.toString());
return userResult;
}
else {
return null;
}
}
}
运行&&效果
- 运行
mvn spring-boot:run
- 访问
curl --location --request POST 'http://localhost:8080' \
--header 'Content-Type: application/json' \
--data-raw '{
"name":"dalonng",
"age":27,
"userinfo":{
"name2":"demo",
"age2":333
}
}' | jq .
效果
{
"name": "rongfengliang",
"age": 27,
"userinfo": {
"name2": "rule do action4",
"age2": 333
}
}
日志:
说明
以上是一个简单的集成学习,实际上如果我么的业务都是基于此模式开发的,那么我们可以基于配置的规则以及记录的事件,提供一个
便捷的业务状态追踪系统(事件,规则,数据。。。),类似的工作流模式也是一种不错的实践
参考资料
https://github.com/j-easy/easy-rules
https://github.com/rongfengliang/easy-rules-spring-learning