一、环境概述

JDK:openjdk version "1.8.0_452"
Maven: maven 3.8.8
Drools:Drools 7.73.0.Final
SpringBoot:SpringBoot 2.7.18

二、项目依赖
这里没有引入drools整合spring依赖了,是直接手动把组件IOC的。
POM.xml

<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot.version>2.7.18</spring-boot.version>
        <drools.version>7.73.0.Final</drools.version>
        <lombok.version>1.18.30</lombok.version>
        <junit.version>4.13.2</junit.version>
    </properties>

    <!-- 使用 dependencyManagement 管理 Spring Boot 和 Drools 版本 -->
    <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>
            <dependency>
                <groupId>org.drools</groupId>
                <artifactId>drools-bom</artifactId>
                <version>${drools.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Drools依赖 -->
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
        </dependency>
        <!-- 决策表要用 -->
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-decisiontables</artifactId>
        </dependency>

        <!-- SpringBoot依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <!-- 排除 JUnit 5(因为要用 JUnit 4) -->
            <exclusions>
                <exclusion>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 若为Web项目,添加此依赖 (二选一)-->
        <!--        <dependency>-->
        <!--            <groupId>org.springframework.boot</groupId>-->
        <!--            <artifactId>spring-boot-starter-web</artifactId>-->
        <!--        </dependency>-->
        <!-- 若为非Web项目,添加此依赖 (二选一) 这里我就不用web跑了,直接starter跑了-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- junit 4 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

三、实体类和配置类
注意:这里没有用读取kmodule.xml的方式创建KieBase了,是直接代码配置类方式配置的
src/main/java/com/online/admin/entity/Person.java

package com.online.admin.entity;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Person
{
    private String name;
    private int age;
}

src/main/java/com/online/admin/config/DroolsConfig.java

package com.online.admin.config;

import lombok.extern.slf4j.Slf4j;
import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.builder.model.KieBaseModel;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.builder.model.KieSessionModel;
import org.kie.api.conf.EqualityBehaviorOption;
import org.kie.api.conf.EventProcessingOption;
import org.kie.api.runtime.KieContainer;
import org.kie.internal.io.ResourceFactory;
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;

/**
 * Drools配置类
 */
@Slf4j
@Configuration
public class DroolsConfig {
    @Bean
    public KieServices getKieServices() {
        return KieServices.Factory.get();
    }

    /**
     * 这里就类似于读取kmodule.xml,创建模型类
     */
    @Bean
    public KieModuleModel kieModuleModel(KieServices kieServices) {
        KieModuleModel kieModuleModel = kieServices.newKieModuleModel();
        KieBaseModel kieBaseModel1 = kieModuleModel.newKieBaseModel("rule1")
                .setDefault(false)
                .addPackage("rule1")
                .setEqualsBehavior(EqualityBehaviorOption.EQUALITY)
                .setEventProcessingMode(EventProcessingOption.STREAM);
        //有状态类型的Session就是规则执行过程中会话会一直存在,改变工作内存对象会再次触发规则判断,重新计算,重新输出结果。
        //无状态类型的Session就要求你在开始计算的时候把所需所有事实对象全部传入,执行完计算以后会话就不存在了,只是会返回结果。
        // 这里是用的有状态演示
        // 有状态类型
        kieBaseModel1.newKieSessionModel("ksession-rules1")
                .setType(KieSessionModel.KieSessionType.STATEFUL);

        KieBaseModel kieBaseModel2 = kieModuleModel.newKieBaseModel("rule2")
                .setDefault(false)
                .addPackage("rule2")
                .setEqualsBehavior(EqualityBehaviorOption.EQUALITY)
                .setEventProcessingMode(EventProcessingOption.STREAM);
        // 有状态类型
        kieBaseModel2.newKieSessionModel("ksession-rules2")
                .setType(KieSessionModel.KieSessionType.STATEFUL);

        return kieModuleModel;
    }

    // 这里我打算根据数据库或者配置中心读取配置创建,先读取Module配置,
    // 然后根据配置读取映射的drl文件位置,根据模块区分开不同模块下的的Session。
    // 这里是写死的,组件生命周期管理有问题,玩玩可以,项目上千万别学
    @Bean
    public KieFileSystem kieFileSystem(KieServices kieServices,KieModuleModel kieModuleModel) throws IOException {
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        kieFileSystem.writeKModuleXML(kieModuleModel.toXML());
        // 1. 加载属于KieBase "rules1"的规则(路径:rule1/*.drl)
        Resource[] rule1Resources = resolver.getResources("classpath*:rule1/*.drl");
        for (Resource resource : rule1Resources) {
            // 规则文件路径对应KieBase1的package:"rule1"
            String resourcePath = "rule1/" + resource.getFilename();
            kieFileSystem.write(ResourceFactory.newClassPathResource(resourcePath, "UTF-8"));
            log.info("已加载KieBase(rules1)的规则文件: {}", resourcePath);
        }
        // 2. 加载属于KieBase "rules2"的规则(路径:rule2/*.drl)
        Resource[] rule2Resources = resolver.getResources("classpath*:rule2/*.drl");
        for (Resource resource : rule2Resources) {
            // 规则文件路径对应KieBase2的package:"rule2"
            String resourcePath = "rule2/" + resource.getFilename();
            kieFileSystem.write(ResourceFactory.newClassPathResource(resourcePath, "UTF-8"));
            log.info("已加载KieBase(rules2)的规则文件: {}", resourcePath);
        }
        return kieFileSystem;
    }

    @Bean
    public KieContainer kieContainer(KieServices kieServices, KieFileSystem kieFileSystem, KieModuleModel kieModuleModel) {

        // 构建KieModule
        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
        // 构建KieRepository
        KieRepository kieRepository = kieServices.getRepository();
        //这里我是自己玩搭建环境,用的默认的版本号,实际这里可能要自定义releaseId(规则版本号,用于区分规则版本是否发生变化用于热部署)
        kieRepository.addKieModule(kieRepository::getDefaultReleaseId);
        // 执行构建
        kieBuilder.buildAll();
        // 检查构建错误
        if (kieBuilder.getResults().hasMessages(Message.Level.ERROR)) {
            log.error("规则构建失败: {}", kieBuilder.getResults().getMessages());
            throw new RuntimeException("规则构建失败,请检查规则文件");
        }
        // 创建KieContainer
        return kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
    }
}

四、drl文件(两个内容差不多,但是包名称和位置不一样)
src/main/resources/rule1/rule1.drl

package rule1;
import com.online.admin.entity.Person;

rule "Age Check Rule"
    when
        $p : Person(age >= 18)
    then
        System.out.println("[rules1规则触发] " + $p.getName() + " 是成年人,年龄: " + $p.getAge());
end

rule "Check using eval"
    when
        $p : Person()
        eval( $p.getAge() >= 18 )
    then
        System.out.println("rules1条件满足");
end

src/main/resources/rule2/rule2.drl

package rule2;
import com.online.admin.entity.Person;

rule "Age Check Rule"
    when
        $p : Person(age < 18)
    then
        System.out.println("[rules2规则触发] " + $p.getName() + " 不是成年人,年龄: " + $p.getAge());
end

rule "Check using eval"
    when
        $p : Person()
        eval( $p.getAge() >= 18 )
    then
        System.out.println("rules2条件满足");
end

五、测试类

package com.online.admin;

import com.online.admin.config.DroolsConfig;
import com.online.admin.entity.Person;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.kie.api.KieBase;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Collection;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DroolsConfig.class)
public class AppTest {

    @Autowired
    private KieContainer kieContainer;

    @Test
    public void testRule1() {
        KieSession kieSession = null;
        try {
            Collection<String> kieBaseNames = kieContainer.getKieBaseNames();
            log.info("kieBaseNames : {}", kieBaseNames);
            kieSession = kieContainer.newKieSession("ksession-rules1");
            KieBase kieBase = kieSession.getKieBase();
            log.info("kieBase : {}", kieBase.getKiePackages());
//            Rule rule=kieBase.getRule("rule1","Age Check Rule");
//            log.info("rule : {}", rule.getName());
            Person adult = new Person("李四", 25);
            kieSession.insert(adult);
            int firedRules = kieSession.fireAllRules();
            log.info("触发了 {} 条规则", firedRules);
        } catch (RuntimeException e) {
            log.error("规则执行失败", e);
            Assert.fail(e.getMessage());
            throw e;
        } finally {
            if (kieSession != null) {
                try {
                    kieSession.dispose();
                } catch (RuntimeException e) {
                    log.error("kiesession关闭失败,{}", e.getMessage());
                }
            }
        }
    }

    @Test
    public void testRule2() {
        KieSession kieSession = null;
        try {
            kieSession = kieContainer.newKieSession("ksession-rules2");
            Person adult = new Person("李四", 12);
            kieSession.insert(adult);
            int firedRules = kieSession.fireAllRules();
            log.info("触发了 {} 条规则", firedRules);
        } catch (RuntimeException e) {
            log.error("规则执行失败", e);
            Assert.fail(e.getMessage());
            throw e;
        } finally {
            if (kieSession != null) {
                try {
                    kieSession.dispose();
                } catch (RuntimeException e) {
                    log.error("kiesession关闭失败,{}", e.getMessage());
                }
            }
        }
    }
}