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

posted on 2020-04-14 15:01  荣锋亮  阅读(4938)  评论(0编辑  收藏  举报

导航