美文网首页
Spring Boot + Drools 动态加载规则执行

Spring Boot + Drools 动态加载规则执行

作者: 一生逍遥一生 | 来源:发表于2023-06-06 22:17 被阅读0次

    Spring Boot 与Drools结合,将规则存储在数据库中,增删改差规则,然后按照名字来执行规则。

    引入pom

    <?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.edu.drool</groupId>
        <artifactId>spring-boot-drool-jpa</artifactId>
        <version>1.0-SNAPSHOT</version>
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.7.12</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
                <version>2.7.12</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.30</version>
            </dependency>
            <dependency>
                <groupId>org.kie</groupId>
                <artifactId>kie-spring</artifactId>
                <version>7.73.0.Final</version> <!--引入drool-->
                <exclusions>
                    <exclusion>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-tx</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-beans</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-core</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-context</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.drools</groupId>
                <artifactId>drools-compiler</artifactId>
                <version>7.73.0.Final</version> <!--引入drool-->
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.26</version>
            </dependency>
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>5.8.18</version>
            </dependency>
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.9</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>2.0.29</version>
            </dependency>
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>4.5.14</version>
            </dependency>
        </dependencies>
    </project>
    

    启动程序

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    @SpringBootApplication
    public class DroolApplication {
        public static void main(String[] args) {
            // 因为有的规则需要对时间进行比较,需要先设置环境变量
            System.setProperty("drools.dateformat","yyyy-MM-dd");
            SpringApplication.run(DroolApplication.class, args);
        }
    }
    

    自动配置

    import org.kie.api.KieBase;
    import org.kie.api.KieServices;
    import org.kie.api.builder.*;
    import org.kie.api.runtime.KieContainer;
    import org.kie.api.runtime.KieSession;
    import org.kie.internal.io.ResourceFactory;
    import org.kie.spring.KModuleBeanFactoryPostProcessor;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.Resource;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    import org.springframework.core.io.support.ResourcePatternResolver;
    import java.io.IOException;
    @Configuration
    public class DroolsAutoConfiguration {
        private static final String RULES_PATH = "rules/";
        @Bean
        @ConditionalOnMissingBean(KieFileSystem.class)
        public KieFileSystem kieFileSystem() throws IOException {
            KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
            for (Resource file : getRuleFiles()) {
                kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
            }
            return kieFileSystem;
        }
        private Resource[] getRuleFiles() throws IOException {
            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
        }
        @Bean
        @ConditionalOnMissingBean(KieContainer.class)
        public KieContainer kieContainer() throws IOException {
            final KieRepository kieRepository = getKieServices().getRepository();
            kieRepository.addKieModule(new KieModule() {
                public ReleaseId getReleaseId() {
                    return kieRepository.getDefaultReleaseId();
                }
            });
            KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
            kieBuilder.buildAll();
    
            KieContainer kieContainer = getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
            return kieContainer;
        }
        private KieServices getKieServices() {
            return KieServices.Factory.get();
        }
        @Bean
        @ConditionalOnMissingBean(KieBase.class)
        public KieBase kieBase() throws IOException {
            return kieContainer().getKieBase();
        }
        @Bean
        @ConditionalOnMissingBean(KieSession.class)
        public KieSession kieSession() throws IOException {
            return kieContainer().newKieSession();
        }
        @Bean
        @ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
        public KModuleBeanFactoryPostProcessor kiePostProcessor() {
            return new KModuleBeanFactoryPostProcessor();
        }
    }
    

    规则实体

    因为要将规则存入数据,先选择一个实例

    import lombok.Data;
    import org.apache.commons.lang3.StringUtils;
    import javax.persistence.*;
    import java.io.Serializable;
    import java.util.Date;
    @Entity
    @Data
    public class DroolRule implements Serializable {
        /**
         * 规则id
         */
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        /**
         * kbase的名字
         */
        @Column(name = "kieBaseName")
        private String kieBaseName;
        /**
         * 设置该kbase需要从那个目录下加载文件,,相这个是一个虚拟的目录对于 `src/main/resources`
         * 比如:kiePackageName=rules/rule01 那么当前规则文件写入路径为: kieFileSystem.write("src/main/resources/rules/rule01/1.drl")
         */
        @Column(name = "kiePackageName")
        private String kiePackageName;
        /**
         * 规则内容
         */
        @Column(name = "ruleContent")
        private String ruleContent;
        public void validate() {
            if (this.id == null || isBlank(kieBaseName) || isBlank(kiePackageName) || isBlank(ruleContent)) {
                throw new RuntimeException("参数有问题");
            }
        }
        private boolean isBlank(String str) {
            return StringUtils.isBlank(str);
        }
    }
    
    import com.edu.drool.domain.DroolRule;
    import org.springframework.data.jpa.repository.JpaRepository;
    public interface DroolRuleRepository extends JpaRepository<DroolRule, Long> {
    }
    

    存储规则、修改规则

    在使用Drools的过程中,创建kiebase的成本很多,需要很多资源,需要将kiebase存储起来。

    import com.edu.drool.domain.DroolRule;
    import com.edu.drool.repository.DroolRuleRepository;
    import lombok.extern.slf4j.Slf4j;
    import org.drools.compiler.kie.builder.impl.InternalKieModule;
    import org.drools.compiler.kie.builder.impl.KieContainerImpl;
    import org.kie.api.KieBase;
    import org.kie.api.KieServices;
    import org.kie.api.builder.KieBuilder;
    import org.kie.api.builder.KieFileSystem;
    import org.kie.api.builder.Message;
    import org.kie.api.builder.Results;
    import org.kie.api.builder.model.KieBaseModel;
    import org.kie.api.builder.model.KieModuleModel;
    import org.kie.api.runtime.KieContainer;
    import org.kie.api.runtime.KieSession;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @Component
    @Slf4j
    public class DroolRuleManager {
        // 此类本身就是单例的
        private final KieServices kieServices = KieServices.Factory.get();
        // kie文件系统,需要缓存,如果每次添加规则都是重新new一个的话,则可能出现问题。即之前加到文件系统中的规则没有了
        private final KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        // 可以理解为构建 kmodule.xml
        private final KieModuleModel kieModuleModel = kieServices.newKieModuleModel();
        // 需要全局唯一一个,如果每次加个规则都新创建一个,那么旧需要销毁之前创建的kieContainer,如果此时有正在使用的KieSession,则可能有问题
        private KieContainer kieContainer;
        @Autowired
        private DroolRuleRepository droolRuleRepository;
    
        /**
         * 判断该kbase是否存在
         */
        public boolean existsKieBase(String kieBaseName) {
            if (null == kieContainer) {
                return false;
            }
            Collection<String> kieBaseNames = kieContainer.getKieBaseNames();
            if (kieBaseNames.contains(kieBaseName)) {
                return true;
            }
            log.info("需要创建KieBase:{}", kieBaseName);
            return false;
        }
        //删除规则,也可以将数据中的数据删除
        public void deleteDroolsRule(String kieBaseName, String packageName, String ruleName) {
            if (existsKieBase(kieBaseName)) {
                KieBase kieBase = kieContainer.getKieBase(kieBaseName);
                kieBase.removeRule(packageName, ruleName);
                log.info("删除kieBase:[{}]包:[{}]下的规则:[{}]", kieBaseName, packageName, ruleName);
            }
        }
    
        /**
         * 添加或更新 drools 规则
         */
        public void addOrUpdateRule(DroolRule droolsRule) {
            // 获取kbase的名称
            String kieBaseName = droolsRule.getKieBaseName();
            // 判断该kbase是否存在
            boolean existsKieBase = existsKieBase(kieBaseName);
            // 该对象对应kmodule.xml中的kbase标签
            KieBaseModel kieBaseModel;
            if (!existsKieBase) {
                // 创建一个kbase
                kieBaseModel = kieModuleModel.newKieBaseModel(kieBaseName);
                // 不是默认的kieBase
                kieBaseModel.setDefault(false);
                // 设置该KieBase需要加载的包路径
                kieBaseModel.addPackage(droolsRule.getKiePackageName());
                // 设置kieSession
                kieBaseModel.newKieSessionModel(kieBaseName + "-session")
                        // 不是默认session
                        .setDefault(false);
            } else {
                // 获取到已经存在的kbase对象
                kieBaseModel = kieModuleModel.getKieBaseModels().get(kieBaseName);
                // 获取到packages
                List<String> packages = kieBaseModel.getPackages();
                if (!packages.contains(droolsRule.getKiePackageName())) {
                    kieBaseModel.addPackage(droolsRule.getKiePackageName());
                    log.info("kieBase:{}添加一个新的包:{}", kieBaseName, droolsRule.getKiePackageName());
                } else {
                    kieBaseModel = null;
                }
            }
            String file = "src/main/resources/rules/" + droolsRule.getKieBaseName() + ".drl";
            log.info("加载虚拟规则文件:{}", file);
            kieFileSystem.write(file, droolsRule.getRuleContent());
            if (kieBaseModel != null) {
                String kmoduleXml = kieModuleModel.toXML();
                log.info("加载kmodule.xml:[\n{}]", kmoduleXml);
                kieFileSystem.writeKModuleXML(kmoduleXml);
            }
            KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
            // 通过KieBuilder构建KieModule下所有的KieBase
            kieBuilder.buildAll();
            // 获取构建过程中的结果
            Results results = kieBuilder.getResults();
            // 获取错误信息
            List<Message> messages = results.getMessages(Message.Level.ERROR);
            if (null != messages && !messages.isEmpty()) {
                for (Message message : messages) {
                    log.error(message.getText());
                }
                throw new RuntimeException("加载规则出现异常");
            }
            // KieContainer只有第一次时才需要创建,之后就是使用这个
            if (null == kieContainer) {
                kieContainer = kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());
            } else {
                // 实现动态更新
                ((KieContainerImpl) kieContainer).updateToKieModule((InternalKieModule) kieBuilder.getKieModule());
            }
        }
        // 根据kiebase 名字获取kiesession
        public KieSession getKieSessionBySessionName(String kieBaseName) {
            KieSession kieSession = kieContainer.newKieSession(kieBaseName + "-session");
            return kieSession;
        }
    }
    

    请求器

    import com.edu.drool.domain.CalculationReq;
    import com.edu.drool.domain.CalculationVo;
    import com.edu.drool.domain.CreditCardApplyInfo;
    import com.edu.drool.service.DroolRuleService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import java.io.IOException;
    import java.util.List;
    
    
    @RequestMapping("/test")
    @Controller
    public class DroolRuleController {
        @Autowired
        private DroolRuleService droolRuleService;
        @RequestMapping("/calculate")
        @ResponseBody
        public List<CalculationVo> calculate(@RequestBody CalculationReq req) {
            List<CalculationVo> calculationVoList = droolRuleService.calculate(req);
            for (CalculationVo calculationVo : calculationVoList) {
                System.out.println(calculationVo);
            }
            return calculationVoList;
        }
        @RequestMapping("/creditCardApply")
        @ResponseBody
        public CreditCardApplyInfo creditCardApply(@RequestBody
                                                           CreditCardApplyInfo creditCardApplyInfo) throws IOException {
            creditCardApplyInfo = droolRuleService.creditCardApply(creditCardApplyInfo);
            System.out.println(creditCardApplyInfo);
            return creditCardApplyInfo;
        }
        //在生产上使用的时候,需要使用CommandLineRunner在启动的时候,将所有规则加载。
        @ResponseBody
        @RequestMapping("/reload")
        public String reload() throws IOException {
            droolRuleService.reloadAllDroolRule();
            return "ok";
        }
    }
    

    预加载规则

    import com.edu.drool.service.DroolRuleService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.stereotype.Component;
    @Component
    public class ReloadDroolCommandLine implements CommandLineRunner {
        @Autowired
        private DroolRuleService droolRuleService;
        @Override
        public void run(String... args) throws Exception {
            droolRuleService.reloadAllDroolRule();
        }
    }
    

    相应接口的实现

    import com.edu.drool.domain.CalculationReq;
    import com.edu.drool.domain.CalculationVo;
    import com.edu.drool.domain.CreditCardApplyInfo;
    import com.edu.drool.domain.DroolRule;
    import java.util.List;
    public interface DroolRuleService {
        public String reloadAllDroolRule();
        List<CalculationVo> calculate(CalculationReq req);
        CreditCardApplyInfo creditCardApply(CreditCardApplyInfo creditCardApplyInfo);
        public List<DroolRule> findAll();
    }
    

    实现类

    import cn.hutool.core.bean.BeanUtil;
    import com.edu.drool.domain.*;
    import com.edu.drool.manage.DroolRuleManager;
    import com.edu.drool.repository.DroolRuleRepository;
    import com.edu.drool.service.DroolRuleService;
    import lombok.extern.slf4j.Slf4j;
    import org.kie.api.runtime.KieSession;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import javax.annotation.Resource;
    import java.util.List;
    @Service
    @Slf4j
    public class DroolRuleServiceImpl implements DroolRuleService {
        // 将增加、删除、修改放在DroolRuleManager,并且写入到本地,本文的代码没有保存到数据库,
       //其他读者可自行实现
        @Resource
        private DroolRuleManager droolRuleManager;
        @Autowired
        private DroolRuleRepository droolRuleRepository;
    
        @Override
        public String reloadAllDroolRule() {
            List<DroolRule> droolRuleList = droolRuleRepository.findAll();
            for (DroolRule droolRule : droolRuleList) {
                droolRuleManager.addOrUpdateRule(droolRule);
            }
            return "ok";
        }
        @Override
        public List<CalculationVo> calculate(CalculationReq req) {
            CalculationDto calculationDto = BeanUtil.copyProperties(req, CalculationDto.class);
            calculationDto.setWageDeductedTax(calculationDto.getWageBeforeTax());
            KieSession session = droolRuleManager.getKieSessionBySessionName(req.getName());
            session.setGlobal("calculationGlobalDto", calculationDto);
            session.insert(calculationDto);
            session.fireAllRules();
            session.dispose();
            return calculationDto.getCalculationVoList();
        }
        @Override
        public CreditCardApplyInfo creditCardApply(CreditCardApplyInfo creditCardApplyInfo) {
            KieSession session = droolRuleManager.getKieSessionBySessionName(creditCardApplyInfo.getRuleName());
            session.insert(creditCardApplyInfo);
            session.fireAllRules();
            session.dispose();
            return creditCardApplyInfo;
        }
        @Override
        public List<DroolRule> findAll() {
            return droolRuleRepository.findAll();
        }
    }
    

    连接数据库的配置文件,请读者自行添加。

    参考文献

    第2-4-8章 规则引擎Drools实战(1)-个人所得税计算器
    第2-4-9章 规则引擎Drools实战(2)-信用卡申请
    drools动态增加、修改、删除规则
    Drools官方文档

    相关文章

      网友评论

          本文标题:Spring Boot + Drools 动态加载规则执行

          本文链接:https://www.haomeiwen.com/subject/tmzgedtx.html