使用centraldogma 作为easy-rules spring boot starter的规则存储

centraldogma 前边有介绍过,同时也说明过部署以及使用,以下是基于centraldogma 扩展easy-rules 的spring boot starter
方便快捷的支持rule 的修改以及实时更新,代码已经push github了,可以参考使用

开发流程

借鉴了以前easy-rules spring boot starter的开发,只是调整添加了对于centraldogma的支持

  • 核心代码结构
 
├── README.md
├── pom.xml
└── src
    ├── main
    ├── java
    └── com
    └── github
    └── rongfengliang
    ├── DefaultRuleEngineListener.java
    ├── DefaultRulesListener.java
    ├── EasyRulesAutoConfiguration.java
    ├── EasyRulesEngineConfiguration.java
    ├── EasyRulesEntities.java
    ├── RuleExpressionType.java
    ├── RulesConfig.java
    ├── RulesContentType.java
    ├── SimpleBeanResovler.java
    ├── SpringBeanUtil.java
    └── rulelocator
    ├── RulesLocator.java
    └── impl
    └── FileRulesLocator.java
    └── resources
    └── META-INF
    └── spring.factories
    └── test
        └── java
            └── com
                └── github
                    └── rongfengliang
                        └── CentraldogmaStringReader.java
 
 
 
  • 代码说明
    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-centraldogma-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <encoding>UTF-8</encoding>
        <java.version>1.8</java.version>
        <spring-boot.version>2.2.3.RELEASE</spring-boot.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <easy-rules>4.1.0</easy-rules>
        <centraldogma-client>0.51.1</centraldogma-client>
        <slf4j-simple>1.7.32</slf4j-simple>
        <lombok.version>1.18.20</lombok.version>
    </properties>
    <scm>
        <developerConnection>scm:git:https://github.com/rongfengliang/easy-rules-centraldogma-spring-boot-starer.git</developerConnection>
      <tag>HEAD</tag>
  </scm>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>${spring-boot.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.jeasy</groupId>
            <artifactId>easy-rules-core</artifactId>
            <version>${easy-rules}</version>
        </dependency>
        <dependency>
            <groupId>org.jeasy</groupId>
            <artifactId>easy-rules-mvel</artifactId>
            <version>${easy-rules}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.linecorp.centraldogma</groupId>
            <artifactId>centraldogma-client-spring-boot-starter</artifactId>
            <version>${centraldogma-client}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
            <version>${spring-boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.jeasy</groupId>
            <artifactId>easy-rules-spel</artifactId>
            <version>${easy-rules}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring-boot.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>${slf4j-simple}</version>
            <optional>true</optional>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <distributionManagement>
        <repository>
            <id>sonatype-releases</id>
            <name>sonatype Release Repository</name>
            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
        </repository>
        <snapshotRepository>
            <id>sonatype-snapshots</id>
            <name>sonatype Snapshot Repository</name>
            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
        </snapshotRepository>
    </distributionManagement>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-release-plugin</artifactId>
                <version>3.0.0-M4</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>3.2.0</version>
                <configuration>
                    <attach>true</attach>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
 
 

配置入口

package com.github.rongfengliang;
 
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.linecorp.centraldogma.client.CentralDogma;
import com.linecorp.centraldogma.client.Watcher;
import com.linecorp.centraldogma.common.Entry;
import com.linecorp.centraldogma.common.Query;
import com.linecorp.centraldogma.common.Revision;
import org.jeasy.rules.api.*;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.spel.SpELRuleFactory;
import org.jeasy.rules.support.reader.JsonRuleDefinitionReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.CommandLineRunner;
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.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
 
@Configuration
@EnableConfigurationProperties(EasyRulesEngineConfiguration.class)
public class EasyRulesAutoConfiguration {
    private Logger log = LoggerFactory.getLogger(DefaultRulesListener.class);
    private final EasyRulesEngineConfiguration properties; // 配置
    private Map<String, Rules> easyRules; // 基于map 存储bean 中间状态数据
    private EasyRulesEntities easyRulesEntities; //  基于map 存储bean 中间状态数据
    EasyRulesAutoConfiguration(EasyRulesEngineConfiguration properties) {
        this.properties = properties;
        this.easyRules = null;
        this.easyRulesEntities =new EasyRulesEntities();
    }
 
    @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();
    }
 
    @Bean 
    @ConditionalOnClass(value = {CentralDogma.class, ObjectMapper.class}) // 需要依赖jackson以及CentralDogma bean,此bean 主要实现加载之后的配置加载以及变动数据的实时更新,同时转换为对应的easy-rules
    public CommandLineRunner commandLineRunner(CentralDogma dogma, ObjectMapper objectMapper, BeanResolver beanResolver) {
        log.info("project:{}, repo:{},filename:{}", properties.getProject(), properties.getRepo(), properties.getConfName());
        Watcher watcher = dogma.fileWatcher(properties.getProject(), properties.getRepo(), Query.ofText(properties.getConfName()));
        watcher.watch((revision, value) -> {
            log.info("Updated to {} at {}", value, revision);
            try {
                List<RulesConfig> rulesConfigs = objectMapper.readValue(value.toString(), new TypeReference<List<RulesConfig>>() {
                });
                easyRulesEntities.setRulesConfigs(rulesConfigs);
                this.easyRules = rulesContent2EasyRules(beanResolver);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
                log.error("json convert  error", e);
            }
        });
        return args -> {
            log.info("init CentralDogma load easy rules conf");
        };
    }
 
    /**
     * centralDogmaRules with prototype for fetch new conf rules
     * bean name is <b>centralDogmaRules</b>
     *
     * @param beanResolver
     * @param centralDogma
     * @return
     * @throws Exception
     */
    @Bean(name = "centralDogmaRules")
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @ConditionalOnClass(value = {CentralDogma.class, ObjectMapper.class}) // 为了安全以及实时变动支持,基于了原型模式
    public Map<String, Rules> centralDogmaRules(BeanResolver beanResolver, CentralDogma centralDogma, ObjectMapper objectMapper) throws Exception {
        Map<String, Rules> rules = new HashMap<>();
        if (Objects.isNull(easyRules)) {
            CompletableFuture<Entry<String>> future =
                    centralDogma.getFile(properties.getProject(), properties.getRepo(), Revision.HEAD, Query.ofText(properties.getConfName()));
            log.info("load rule  content", future.join().content());
            List<RulesConfig> rulesConfigs = objectMapper.readValue(future.join().content(), new TypeReference<List<RulesConfig>>() {
            });
            easyRulesEntities.setRulesConfigs(rulesConfigs);
            return getStringRulesMap(beanResolver, rules);
        } else {
            return this.easyRules;
        }
 
    }
 
    /**
     * common method for Map<String, Rules> convert
     * @param beanResolver
     * @param rules
     * @return
     */
    private Map<String, Rules> getStringRulesMap(BeanResolver beanResolver, Map<String, Rules> rules) {
        easyRulesEntities.getRulesConfigs().forEach(new Consumer<RulesConfig>() {
            @Override
            public void accept(RulesConfig rulesConfig) {
                log.info("load rule conf to easy rules engine:{}", rulesConfig.getRulesContent());
                StringReader stringReader = new StringReader(rulesConfig.getRulesContent().toPrettyString());
                SpELRuleFactory jsonRuleFactory = new SpELRuleFactory(new JsonRuleDefinitionReader(), beanResolver);
                Rules jsonRules = null;
                try {
                    jsonRules = jsonRuleFactory.createRules(stringReader);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                rules.put(rulesConfig.getRulesId(), jsonRules);
            }
        });
        return rules;
    }
 
    /**
     * convert rulesContent2EasyRules
     *
     * @param beanResolver
     * @return
     */
    private Map<String, Rules> rulesContent2EasyRules(BeanResolver beanResolver) {
        Map<String, Rules> rules = new HashMap<>();
        return getStringRulesMap(beanResolver, rules);
    }
    /**
     * 获取配置额规则列表
     *
     * @param beanResolver spring beanResolver
     * @return Map<String, Rules>
     * @throws Exception
     */
    /**
     * 为了安全使用原型模式
     *
     * @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 配置主要包含了一些通用参数的支持

package com.github.rongfengliang;
 
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
 
 
@Data
@ConfigurationProperties(prefix = "easyrules")
public class EasyRulesEngineConfiguration {
    private boolean skipOnFirstAppliedRule;
    private boolean skipOnFirstNonTriggeredRule;
    private boolean skipOnFirstFailedRule;
    private int priorityThreshold;
    private String project;
    private String repo;
    private String contentType;
    private String confName;
}
 
 

规则存储实体定义RulesConfig(因为数据结构的多变,使用了jsonnode,实现灵活扩展)

package com.github.rongfengliang;
 
 
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
 
/**
 * @author dalong
 * rules文件配置信息
 */
 
@Data
public class RulesConfig {
    private String rulesId;
    private JsonNode rulesContent;
}
 
 

自定义beanresovler SimpleBeanResovler 主要是spel 需要

package com.github.rongfengliang;
 
import org.springframework.context.ApplicationContext;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.EvaluationContext;
 
/**
 * @author dalong
 * SimpleBeanResovler
 */
public class SimpleBeanResovler implements BeanResolver {
    private ApplicationContext applicationContext;
 
    public SimpleBeanResovler(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
 
    @Override
    public Object resolve(EvaluationContext context, String beanName) {
        return applicationContext.getBean(beanName);
    }
}
 

SpringBeanUtil bean 工具类,主要简化原型的处理

package com.github.rongfengliang;
 
import org.jeasy.rules.api.Rules;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
 
import java.util.Map;
 
public class SpringBeanUtil implements ApplicationContextAware {
    /**
     * 上下文对象实例
     */
    private static ApplicationContext applicationContext;
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
 
    public static Map<String, Rules> centralDogmaRules(){
        return SpringBeanUtil.getBean("centralDogmaRules",Map.class);
    }
 
    /**
     * 获取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);
    }
}

说明

一以上是具体的代码部分,关于使用,后边会有介绍

参考资料

https://github.com/rongfengliang/easy-rules-centraldogma-spring-boot-starter
https://github.com/line/centraldogma

posted on 2021-08-21 09:25  荣锋亮  阅读(227)  评论(0编辑  收藏  举报

导航