Drools集成SpringBoot


1.說明

為了更好的在項目中使用Drools,
需要把Drools集成到Spring Boot,
下面介紹集成的方法,
並且開發簡單的Demo和測試用例。

2.創建Maven工程

pom.xml工程信息:

<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.ai.prd</groupId>
    <artifactId>drools-spring-boot-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <description>Drools integrats into the Spring Boot</description>
</project>

3.引入Spring Boot相關依賴

引入spring-boot-starter-web作為Web工程,對外提供Rest服務,
引入spring-boot-starter-log4j2日志框架,打印測試匹配結果,
引入spring-boot-starter-test測試框架,開發Junt5測試用例:

<properties>
    <spring-boot.version>2.3.1.RELEASE</spring-boot.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <exclusions>
            <!-- 單元測試不使用Junit4,使用Junit5 -->
            <exclusion>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

4.引入Drools相關依賴

通過kie-spring引入Drools相關的jar包,
其依賴的spring版本都排除掉,
以上一步的spring boot依賴為准。

<properties>
    <drools.version>7.47.0.Final</drools.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.kie</groupId>
        <artifactId>kie-spring</artifactId>
        <version>${drools.version}</version>
        <exclusions>
            <!-- 依賴的spring版本全部以spring boot依賴的為准 -->
            <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>
</dependencies>

5.開發啟動類

啟動類DroolsApplication.java:

package com.ai.prd.drools;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DroolsApplication {
    public static void main(String[] args) {
        SpringApplication.run(DroolsApplication.class, args);
    }
}

6.開發Rest服務

操作的對象Person,
Person.java:

package com.ai.prd.drools.entity;

public class Person {
    private String name;

    private int age;
}

對外提供的Rest服務,
可以對Person對象進行規則匹配,
提供了兩個接口,
單個和批量的操作接口:
PersonRuleController.java:

package com.ai.prd.drools.controller;

import java.util.List;

import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.ai.prd.drools.entity.Person;

@RestController
@RequestMapping("rule/person")
public class PersonRuleController {

    @Autowired
    private KieContainer kieContainer;

    @PostMapping("one")
    public void fireAllRules4One(@RequestBody Person person) {
        KieSession kSession = kieContainer.newKieSession();
        try {
            kSession.insert(person);
            kSession.fireAllRules();
        } finally {
            kSession.dispose();
        }
    }

    @PostMapping("list")
    public void fireAllRules4List(@RequestBody List<Person> persons) {
        KieSession kSession = kieContainer.newKieSession();
        try {
            for (Person person : persons) {
                kSession.insert(person);
            }
            kSession.fireAllRules();
        } finally {
            kSession.dispose();
        }
    }
}

7.初始化Drools

在上面PersonRuleController中需要用到KieContainer,
這個必須在Spring中先初始化才能使用,
相關功能由DroolsAutoConfiguration.java提供:

package com.ai.prd.drools.config;

import java.io.IOException;

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.KieModule;
import org.kie.api.builder.KieRepository;
import org.kie.api.builder.ReleaseId;
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;

/**
 * 配置Drools的服務類,方便在Rest接口中調用。 該類負責加載具體的drl規則文件, 不再需要kmodule.xml配置文件了。
 */
@Configuration
public class DroolsAutoConfiguration {

    private static final String RULES_PATH = "rules/com/ai/prd/";

    @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() {
            @Override
            public ReleaseId getReleaseId() {
                return kieRepository.getDefaultReleaseId();
            }
        });

        KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
        kieBuilder.buildAll();

        return getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
    }

    private KieServices getKieServices() {
        return KieServices.Factory.get();
    }

    @Bean
    @ConditionalOnMissingBean(KieBase.class)
    public KieBase kieBase() throws IOException {
        return kieContainer().getKieBase();
    }

    // 不能反復被使用,釋放資源后需要重新獲取。
    // @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean
    @ConditionalOnMissingBean(KieSession.class)
    public KieSession kieSession() throws IOException {
        return kieContainer().newKieSession();
    }

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

8.開發規則文件

在DroolsAutoConfiguration中指定了drl規則文件
所在目錄rules/com/ai/prd/,
在src/main/resources/rules/com/ai/prd/目錄下新建文件
ai-rules.drl:

package com.ai.prd
 
import com.ai.prd.drools.entity.Person;
import com.ai.prd.drools.action.PersonRuleAction;

// 根據名字匹配指定的人
rule "1.find target person"
    when
        $p : Person( name == "bob" )
    then
        PersonRuleAction.doParse($p, drools.getRule());
        System.out.println("Rule name is [" + drools.getRule().getName() + "]"); 
        System.out.println("Rule package is [" + drools.getRule().getPackageName() + "]");
end

// 根據年齡匹配找到打工人
rule "2.find the work person"
    when
        $p : Person( age >= 25 && age < 65 )      
    then
        System.out.println( $p + " is a work person!" );
end

規則1匹配名字為bob的人,並且調用工具類PersonRuleAction打印相關日志,
同時打印規則的名稱和包路徑到控制台。
規則2匹配年齡在25到65之間的打工人,
然后把匹配到的人直接打印到控制台。

9.規則處理類

PersonRuleAction.java在匹配到相應規則時被調用,
此處僅實現日志打印的功能:

package com.ai.prd.drools.action;

import org.drools.core.definitions.rule.impl.RuleImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ai.prd.drools.entity.Person;

/**
 * 觸發Person相關的規則后的處理類
 * 
 * @author yuwen
 *
 */
public class PersonRuleAction {
    private static Logger LOG = LoggerFactory.getLogger(PersonRuleAction.class);

    // 目前只實現記錄日志功能
    public static void doParse(Person person, RuleImpl rule) {
        LOG.debug("{} is matched Rule[{}]!", person, rule.getName());
    }
}

10.新建日志配置文件

在src/main/resources目錄下,
新建日志配置文件Log4j2.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout
                pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
        <RollingFile name="RuleResultFile"
            fileName="log/rule_result.log"
            filePattern="log/backup/rule_result-%i.log">
            <PatternLayout
                pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level [%l] - %msg%n" />
            <Policies>
                <SizeBasedTriggeringPolicy size="10MB" />
            </Policies>
            <DefaultRolloverStrategy max="10" />
        </RollingFile>
    </Appenders>
    <Loggers>
        <Logger name="com.ai.prd.drools.action" level="DEBUG"
            additivity="true">
            <AppenderRef ref="RuleResultFile" />
        </Logger>
        <Root level="INFO">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

日志文件配置后,
PersonRuleAction類打印的日志
不僅會輸出到log/rule_result.log,
也會輸出到控制台。

11.開發Junit5測試用例

針對上面PersonRuleController提供的Rest接口,
開發兩個Junit5的測試用例,
在src/test/java/目錄下
創建PersonRuleControllerTest.java:

package com.ai.prd.drools.controller;

import java.util.Arrays;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.ai.prd.drools.entity.Person;

@SpringBootTest
public class PersonRuleControllerTest {

    @Autowired
    PersonRuleController controller;

    @Test
    public void testOnePerson() {
        Person bob = new Person();
        bob.setName("bob");

        controller.fireAllRules4One(bob);
    }

    @Test
    public void testTwoPerson() {
        Person bob = new Person();
        bob.setAge(33);

        Person other = new Person();
        other.setAge(88);

        controller.fireAllRules4List(Arrays.asList(bob, other));
    }
}

12.運行測試用例

PersonRuleControllerTest執行后,
控制台輸出:

14:22:04.851 [main] WARN  org.drools.compiler.kie.builder.impl.KieBuilderImpl - File 'rules/com/ai/prd/ai-rules.drl' is in folder 'rules/com/ai/prd' but declares package 'com.ai.prd'. It is advised to have a correspondance between package and folder names.
14:22:06.753 [main] INFO  org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Initializing ExecutorService 'applicationTaskExecutor'
14:22:07.063 [main] INFO  com.ai.prd.drools.controller.PersonRuleControllerTest - Started PersonRuleControllerTest in 3.279 seconds (JVM running for 4.452)
14:22:07.279 [main] DEBUG com.ai.prd.drools.action.PersonRuleAction - Person [name=bob, birthDay=null, age=0, address=null] is matched Rule[1.find target person]!
Rule name is [1.find target person]
Rule package is [com.ai.prd]
Person [name=null, birthDay=null, age=33, address=null] is a work person!
14:22:07.649 [SpringContextShutdownHook] INFO  org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Shutting down ExecutorService 'applicationTaskExecutor'

13.錯誤解決

cannot be cast to org.drools.compiler.kie.builder.impl.InternalKieModule
大概率是rule規則文件有問題,
格式,中英文字符,語法等問題,
請確保規則文件正確。
可以安裝相應插件打開規則文件,
請參考:Drools的Eclipse_IDEA插件安裝

14.參考文章

Drools規則引擎 系列教程(一)SpringBoot整合 & 快速集成上手《Drools7.0.0.Final規則引擎教程》之Springboot集成Drools創建Maven工程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM