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