半小時搞定,規則引擎Drools 集成 springboot 熱加載

前言:

  如果對drools還不是特別熟悉的,可以看下 《規則引擎Drools 之 初識drools》這篇文章;

  本文源碼,github 傳送門:https://github.com/vincent9309/drools

  系統架構如下:

 

一、項目搭建流程

在這裏插入圖片描述

 

二、項目目錄結構

三、springboot集成drools 

1.pom文件引入依賴

   <dependency>
			<groupId>org.drools</groupId>
			<artifactId>drools-core</artifactId>
			<version>7.0.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.drools</groupId>
			<artifactId>drools-compiler</artifactId>
			<version>7.0.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.drools</groupId>
			<artifactId>drools-decisiontables</artifactId>
			<version>7.0.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.drools</groupId>
			<artifactId>drools-templates</artifactId>
			<version>7.0.0.Final</version>
		</dependency>
 
		<dependency>
			<groupId>org.kie</groupId>
			<artifactId>kie-api</artifactId>
			<version>7.0.0.Final</version>
		</dependency>

2. 在src/main/resources/ruls 新建規則文件:

package plausibcheck.adress

import com.neo.drools.model.Address;
import com.neo.drools.model.fact.AddressCheckResult;

rule "[address] Postcode should be filled with exactly 5 numbers"
    agenda-group "address" // rule 分組
//    activation-group "address" //group裏面只能執行一個rule
//    no-loop true // 避免循環調用
//    lock-on-active true  //ruleflow-group屬性或agenda-group屬性的時候, 避免循環調用
//    salience 3 //權重
//    dialect "java" //方言
//    date-effective "20-Jul-2018" //生效日期
//    date-expires "21-Jul-2019" //失效日期
//    enabled true //是否可用
    when
        address : Address(postcode != null, postcode matches "([0-9]{5})")
        checkResult : AddressCheckResult();
    then
        checkResult.setPostCodeResult(true);
		System.out.println("[address]規則中打印日誌:校驗通過!");
end

rule "[copy-address] --> Postcode should be filled with exactly 5 numbers"
    agenda-group "address"
    activation-group "address"
    no-loop true
    lock-on-active true
    salience 1
    when
        address : Address(postcode != null, postcode matches "([0-9]{5})")
        checkResult : AddressCheckResult();
    then
        checkResult.setPostCodeResult(true);
		System.out.println("[copy-address]規則中打印日誌:校驗通過!");
end

3、src/main/resources/META-INF新建配置文件kmodule.xml:

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://www.drools.org/xsd/kmodule">

    <kbase name="address-testKB" packages="rules">
        <ksession name="address-testKS"/>
    </kbase>

    <kbase name="addressKB" packages="rules">
        <ksession name="addressKS"/>
    </kbase>

    <kbase name="rules" packages="rules">
        <ksession name="ksession-rule"/>
    </kbase>

</kmodule>

4.創建tKieUitl

public class KieUtils {
    private static KieContainer kieContainer;

    private static KieSession kieSession;

    public static KieContainer getKieContainer() {
        return kieContainer;
    }

    public static void setKieContainer(KieContainer kieContainer) {
        KieUtils.kieContainer = kieContainer;
        kieSession = kieContainer.newKieSession();
    }

    public static KieSession getKieSession() {
        return kieSession;
    }

    public static void setKieSession(KieSession kieSession) {
        KieUtils.kieSession = kieSession;
    }

    public static KieServices getKieServices() {
        return KieServices.Factory.get();
    }
}

5.通過restful API即可調用

@RequestMapping("/address-test")
    public void addressTest(int num){
        Address address = new Address();
        address.setPostcode(generateRandom(num));

        KieServices ks = KieServices.Factory.get();
        KieContainer kc = ks.getKieClasspathContainer();
        KieSession kieSession = kc.newKieSession("address-testKS");

        //選擇agenda-group
        kieSession.getAgenda().getAgendaGroup("address-test").setFocus();
        //增加自定義AgendaFilter
        AgendaFilter filter = new MyAgendaFilter("[Address-Test]");

        AddressCheckResult result = new AddressCheckResult();
        FactHandle faceHandle = kieSession.insert(address);
        kieSession.insert(result);

        //更新work memory 中的對象信息
//        address.setPostcode("123");
//        kieSession.update(faceHandle,address);

        int ruleFiredCount = kieSession.fireAllRules(filter);
        kieSession.destroy();
        System.out.println("觸發了" + ruleFiredCount + "條規則");

        if(result.isPostCodeResult()){
            System.out.println("規則校驗通過");
        }
    }

    /**
     * 生成隨機數
     * @param num
     * @return
     */
    public String generateRandom(int num) {
        String chars = "0123456789";
        StringBuffer number=new StringBuffer();
        for (int i = 0; i < num; i++) {
            int rand = (int) (Math.random() * 10);
            number=number.append(chars.charAt(rand));
        }
        return number.toString();
    }

三、springboot熱刷新drools 規則文件

 

1.增加config配置

@Configuration
public class DroolsAutoConfig {

    public static final String RULES_PATH = "rules/";
    public static final String BASE_RULES_PATH = "classpath*:";

    @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(BASE_RULES_PATH + 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());
        KieUtils.setKieContainer(kieContainer);
        return getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
    }

    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 {
        KieSession kieSession = kieContainer().newKieSession();
        KieUtils.setKieSession(kieSession);
        return kieSession;
    }

    @Bean
    @ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
    public KModuleBeanFactoryPostProcessor kiePostProcessor() {
        return new KModuleBeanFactoryPostProcessor();
    }
}

2. 增加兩個API接口,一個是更新規則文件(此處就不詳細寫常規的上傳功能);另一個是根據文件刷新規則

   @RequestMapping("/reload")
    public String reload(String drlName) throws Exception {
        rules.reload(drlName);
        return "ok";
    }

    @RequestMapping("/updateDrlFile")
    public String updateDrlFile(MultipartFile file) throws Exception {
        //更新drl後,再調用reload方法重載。即可熱部署
        return "ok";
    }

3. 具體看service 實現 (此處也可以讀取數據庫,刷新規則)

@Service
public class ReloadDroolsRules {

    public void reload(String drlName) throws Exception {
        KieServices kieServices = KieUtils.getKieServices();
        KieFileSystem kfs = kieServices.newKieFileSystem();
        loadDBRules(drlName, kfs);
//        loadFileRules(drlName, kfs);
        KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();
        Results results = kieBuilder.getResults();
        if (results.hasMessages(Message.Level.ERROR)) {
            System.out.println(results.getMessages());
            throw new IllegalStateException("### errors ###");
        }

        KieUtils.setKieContainer(kieServices.newKieContainer(KieUtils.getKieServices().getRepository().getDefaultReleaseId()));
        System.out.println("新規則重載成功");
    }

    private void loadDBRules(String drlName, KieFileSystem kfs) {
        //        String path = "src/main/resources/rules/address.drl";
        String path = "src/main/resources/"+ DroolsAutoConfig.RULES_PATH + "/"+ drlName + ".drl";
        // 從數據庫加載的規則
        kfs.write(path, "package plausibcheck.adress\n\n import com.neo.drools.model.Address;\n import com.neo.drools.model.fact.AddressCheckResult;\n\n rule \"Postcode 6 numbers\"\n\n    when\n  then\n        System.out.println(\"打印日誌:更新rules成功!\");\n end");
    }

    private void loadFileRules(String drlName, KieFileSystem kfs) throws IOException{
        // 從classess/rules加載的規則
        for (Resource file : getRuleFiles(drlName)) {
            kfs.write(ResourceFactory.newClassPathResource(DroolsAutoConfig.BASE_RULES_PATH + DroolsAutoConfig.RULES_PATH + file.getFilename(), "UTF-8"));
        }
    }

    private Resource[] getRuleFiles(String drlName) throws IOException {
        if(StringUtils.isEmpty(drlName)){
            ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
            return resourcePatternResolver.getResources(DroolsAutoConfig.BASE_RULES_PATH + DroolsAutoConfig.RULES_PATH + "**/*.*");
        }
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        return resourcePatternResolver.getResources(DroolsAutoConfig.BASE_RULES_PATH + DroolsAutoConfig.RULES_PATH + "**/"+ drlName + ".*");
    }
}

這樣就可以實現規則熱加載了,完整代碼,可以參考文章頭部 github鏈接, 謝謝!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章