如下是一個簡單的包裝的一個easy-rules spring boot starter,以及使用說明java
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 beangit
- 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:github
[{
"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 的一個擴展
web
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以及配置文件格式spring
參考資料
https://github.com/rongfengliang/easy-rules-spring-boot-starer數據庫