easy-rules spring boot 一个简单的starter

以下是一个简单的包装的一个easy-rules spring boot starter,以及使用说明

easy-rules spring boot starter 项目

  • 说明
    就是一个简单的spring boot starter,包装了easy rules 同时基于配置文件进行rule 的加载,注意此版本使用了4.0 de snapshot
    (使用了beanresolver),当前版本只处理了基于本地文件的加载模式,以及对于spel expression 的支持(因为可以更好的集成
    spring)
  • 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.github.rongfengliang</groupId>
    <artifactId>easy-rules-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starters</artifactId>
        <version>2.2.3.RELEASE</version>
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <encoding>UTF-8</encoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jeasy</groupId>
            <artifactId>easy-rules-core</artifactId>
            <version>4.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.jeasy</groupId>
            <artifactId>easy-rules-mvel</artifactId>
            <version>4.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.jeasy</groupId>
            <artifactId>easy-rules-spel</artifactId>
            <version>4.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <configuration>
                    <attach>true</attach>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
  • starter spi 配置
    src/main/resources/META-INF/spring.factories
 
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.rongfengliang.EasyRulesAutoConfiguration
  • starter 核心入口
    EasyRulesAutoConfiguration.java 此文件比较简单,主要是暴露一个通用的bean,以及基于配置生成spring bean
 
package com.github.rongfengliang;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jeasy.rules.api.RuleListener;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.api.RulesEngineListener;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.core.RulesEngineParameters;
import org.jeasy.rules.spel.SpELRuleFactory;
import org.jeasy.rules.support.JsonRuleDefinitionReader;
import org.jeasy.rules.support.YamlRuleDefinitionReader;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.expression.BeanResolver;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
@Configuration
@EnableConfigurationProperties(EasyRulesEngineConfiguration.class)
public class EasyRulesAutoConfiguration {
    Log log = LogFactory.getLog(EasyRulesAutoConfiguration.class);
    private final EasyRulesEngineConfiguration properties;
    EasyRulesAutoConfiguration(EasyRulesEngineConfiguration properties){
        this.properties=properties;
    }
    @Bean
    @ConditionalOnMissingBean
    public RuleListener defaultRulesListener(){
        return new DefaultRulesListener();
    }
    @Bean
    @ConditionalOnMissingBean
    public RulesEngineListener defaultRuleEngineListener(){
        return new DefaultRuleEngineListener();
    }
    @Bean
    @ConditionalOnMissingBean
    public BeanResolver defaultedResolver(SpringBeanUtil springBeanUtil){
        return new SimpleBeanResovler(SpringBeanUtil.getApplicationContext());
    }
    @Bean
    @ConditionalOnMissingBean
    public SpringBeanUtil springBeanUtil(){
        return new SpringBeanUtil();
    }
    /**
     * 获取配置额规则列表
     *
     * @param beanResolver spring beanResolver
     * @return Map<String,Rules>
     * @throws Exception
     */
    @Bean
    public Map<String,Rules> configRules(BeanResolver beanResolver) throws Exception {
        Map<String,Rules> rules = new HashMap<>();
        this.properties.getRules().forEach(new Consumer<RulesConfig>() {
            @Override
            public void accept(RulesConfig rulesConfig) {
              switch (rulesConfig.getContentType()){
                  case JSON:
                      SpELRuleFactory jsonRuleFactory = new SpELRuleFactory(new JsonRuleDefinitionReader(),beanResolver);
                      Rules jsonRules = null;
                      try {
                          jsonRules = jsonRuleFactory.createRules(new FileReader(this.getClass().getClassLoader().getResource(rulesConfig.getRulesLocation()).getFile()));
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                      rules.put(rulesConfig.getRulesId(),jsonRules);
                      break;
                  case YAML:
                       SpELRuleFactory yamlRuleFactory = new SpELRuleFactory(new YamlRuleDefinitionReader(),beanResolver);
                       Rules yamlRules = null;
                      try {
                          yamlRules = yamlRuleFactory.createRules(new FileReader(this.getClass().getClassLoader().getResource(rulesConfig.getRulesLocation()).getFile()));
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                      rules.put(rulesConfig.getRulesId(),yamlRules);
                      break;
                  default:
                      throw new IllegalStateException("Unexpected value: " + rulesConfig.getContentType());
              }
            }
        });
        return rules;
    }
    /**
     * 为了安全使用原型模式
     * @param defaultRulesListener
     * @param defaultRuleEngineListener
     * @return RulesEngine
     */
    @Bean
    @ConditionalOnMissingBean(RulesEngine.class)
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public RulesEngine rulesEngine(RuleListener defaultRulesListener, RulesEngineListener defaultRuleEngineListener) {
        log.info("create rule Engine");
        RulesEngineParameters parameters = new RulesEngineParameters();
        if(this.properties.getPriorityThreshold()>0){
            parameters.setPriorityThreshold(this.properties.getPriorityThreshold());
        }
        if(this.properties.isSkipOnFirstAppliedRule()){
            parameters.setSkipOnFirstAppliedRule(this.properties.isSkipOnFirstAppliedRule());
        }
        if(this.properties.isSkipOnFirstFailedRule()){
            parameters.setSkipOnFirstFailedRule(this.properties.isSkipOnFirstFailedRule());
        }
        if(this.properties.isSkipOnFirstNonTriggeredRule()){
            parameters.setSkipOnFirstNonTriggeredRule(this.properties.isSkipOnFirstNonTriggeredRule());
        }
        DefaultRulesEngine rulesEngine = new DefaultRulesEngine(parameters);
        rulesEngine.registerRuleListener(defaultRulesListener);
        rulesEngine.registerRulesEngineListener(defaultRuleEngineListener);
        return rulesEngine;
    }
}
 
 
  • EasyRulesEngineConfiguration
    EasyRulesEngineConfiguration.java
    easy rules 配置,spring boot 在启动的时候就基于此配置生成ruleEngine 以及加载定义好的rule 文件
 
package com.github.rongfengliang;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@ConfigurationProperties(prefix = "easyrules")
public class EasyRulesEngineConfiguration {
    private boolean skipOnFirstAppliedRule;
    private boolean skipOnFirstNonTriggeredRule;
    private boolean skipOnFirstFailedRule;
    private int priorityThreshold;
    private List<RulesConfig> rules;
    public boolean isSkipOnFirstAppliedRule() {
        return skipOnFirstAppliedRule;
    }
    public void setSkipOnFirstAppliedRule(boolean skipOnFirstAppliedRule) {
        this.skipOnFirstAppliedRule = skipOnFirstAppliedRule;
    }
    public boolean isSkipOnFirstNonTriggeredRule() {
        return skipOnFirstNonTriggeredRule;
    }
    public void setSkipOnFirstNonTriggeredRule(boolean skipOnFirstNonTriggeredRule) {
        this.skipOnFirstNonTriggeredRule = skipOnFirstNonTriggeredRule;
    }
    public boolean isSkipOnFirstFailedRule() {
        return skipOnFirstFailedRule;
    }
    public void setSkipOnFirstFailedRule(boolean skipOnFirstFailedRule) {
        this.skipOnFirstFailedRule = skipOnFirstFailedRule;
    }
    public int getPriorityThreshold() {
        return priorityThreshold;
    }
    public void setPriorityThreshold(int priorityThreshold) {
        this.priorityThreshold = priorityThreshold;
    }
    public List<RulesConfig> getRules() {
        return rules;
    }
    public void setRules(List<RulesConfig> rules) {
        this.rules = rules;
    }
}
 
  • 规则配置文件格式说明
    为了方便使用基于yaml配置,同时基于规则id关联规则文件,方便对规则管理
 
easyrules:
  skipOnFirstAppliedRule: false
  skipOnFirstNonTriggeredRule: false
  priorityThreshold: 1000000
// 多规则加载,基于rulesId 可以区分不同的业务,基于contentType 标识文件类型
  rules:
  - rulesId: "userlogin"
    rulesLocation: "rules-json.json"
    contentType: JSON
  • 处理规则的源码
    上边已经包含了,下边说明下
    数据返回的是一个hashmap,我们可以通过get方式,获取key 对应的规则,可以灵活选择
    同时支持了json以及yaml 格式的处理(官方api)规则配置使用spel,添加了beanResolver
    可以方便的引用项目中的spring bean
 
@Bean
    public Map<String,Rules> configRules(BeanResolver beanResolver) throws Exception {
        Map<String,Rules> rules = new HashMap<>();
        this.properties.getRules().forEach(new Consumer<RulesConfig>() {
            @Override
            public void accept(RulesConfig rulesConfig) {
              switch (rulesConfig.getContentType()){
                  case JSON:
                      SpELRuleFactory jsonRuleFactory = new SpELRuleFactory(new JsonRuleDefinitionReader(),beanResolver);
                      Rules jsonRules = null;
                      try {
                          jsonRules = jsonRuleFactory.createRules(new FileReader(this.getClass().getClassLoader().getResource(rulesConfig.getRulesLocation()).getFile()));
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                      rules.put(rulesConfig.getRulesId(),jsonRules);
                      break;
                  case YAML:
                       SpELRuleFactory yamlRuleFactory = new SpELRuleFactory(new YamlRuleDefinitionReader(),beanResolver);
                       Rules yamlRules = null;
                      try {
                          yamlRules = yamlRuleFactory.createRules(new FileReader(this.getClass().getClassLoader().getResource(rulesConfig.getRulesLocation()).getFile()));
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                      rules.put(rulesConfig.getRulesId(),yamlRules);
                      break;
                  default:
                      throw new IllegalStateException("Unexpected value: " + rulesConfig.getContentType());
              }
            }
        });
        return rules;
    }

使用

为了安全处理,ruleEngine 使用了原型模式,同时为了方便使用暴露了一个SpringBeanUtil bean

  • 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.dalong.easy-rules-demo</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>com.github.rongfengliang</groupId>
      <artifactId>easy-rules-spring-boot-starter</artifactId>
      <version>1.0-SNAPSHOT</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>
  • 配置文件
    src/main/resources/application.yml
 
easyrules:
  skipOnFirstAppliedRule: false
  skipOnFirstNonTriggeredRule: false
  priorityThreshold: 1000000
  rules:
  - rulesId: "userlogin"
    rulesLocation: "rules-json.json"
    contentType: JSON

rules-json.json:

[{
  "name": "1",
  "description": "1",
  "priority": 1,
  "compositeRuleType": "UnitRuleGroup",
  "composingRules": [
    {
      "name": "2",
      "description": "2",
      "condition": "#{#biz.age >= 18}",
      "priority": 2,
      "actions": [
        "#{@myService.setInfo(#biz)}",
        "#{T(com.dalong.easyrulesdemo.demo.UserServiceImpl).doAction4(#biz)}"
      ]
    }
  ]}
]
  • rest api 调用
    注意为了获取最后rule 的数据,已经添加了一个FinalRule ,注意数据的传递需要包含一个biz 的key,当然可以自己定义
 
@RestController
public class UserApi {
    @Autowired
    Map<String,Rules> configRules;
    @RequestMapping(value = "/", method = RequestMethod.POST)
    public Object info(@RequestBody User user) throws Exception {
        Rules rules = configRules.get("userlogin");
        Facts facts = new Facts();
        // 生成一个唯一id,方便基于数据id规则流程查询
        user.setUniqueId(UUID.randomUUID().toString());
        FinalRule<User> rule = new FinalRule<User>();
        // rules.register(spELRule);
        rules.register(rule);
        facts.put("biz",user);
        // 默认模式
        // myEngine.fire(rules,facts);
        // 应该使用原型模式
        SpringBeanUtil.getBean("rulesEngine",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;
        }
    }
}

一些扩展点

目前添加了基于listener的信息追踪的,只是简单的日志打印,实际上我们可以基于此扩展写入数据到一个时序数据库中
方便基于生成的业务追踪id,分析rule 链的执行情况,同时基于rule 配置文件,生成一个可视化的pipline,同时基于log
可以方便的分析数据,查看业务状态,后边会添加prometheus 以及key/value 存储配置的支持
参考listener 的一个扩展

 
Component
public class MyRuleListener implements RuleListener {
    Log log = LogFactory.getLog(MyRuleListener.class);
    @Override
    public boolean beforeEvaluate(Rule rule, Facts facts) {
        return true;
    }
    @Override
    public void afterEvaluate(Rule rule, Facts facts, boolean b) {
        log.info("-----------------afterEvaluate-----------------");
        log.info("my RulesListener: "+"rule name: "+rule.getName()+"rule desc: "+rule.getDescription()+facts.toString());
    }
    @Override
    public void beforeExecute(Rule rule, Facts facts) {
        log.info("-----------------beforeExecute-----------------");
        log.info("my RulesListener: "+"rule name: "+rule.getName()+"rule desc: "+rule.getDescription()+facts.toString());
    }
    @Override
    public void onSuccess(Rule rule, Facts facts) {
        log.info("-----------------onSuccess-----------------");
        log.info("my RulesListener: "+"rule name: "+rule.getName()+"rule desc: "+rule.getDescription()+facts.toString());
    }
    @Override
    public void onFailure(Rule rule, Facts facts, Exception e) {
        log.info("-----------------onFailure-----------------");
        log.info("my RulesListener: "+"rule name: "+rule.getName()+"rule desc: "+rule.getDescription()+facts.toString());
    }
}
 

一些说明

目前支持的功能还是比较少的,核心还是基于spel以及配置文件格式

参考资料

https://github.com/rongfengliang/easy-rules-spring-boot-starer

posted on 2020-04-16 20:29  荣锋亮  阅读(2539)  评论(0编辑  收藏  举报

导航