简述

scanner有两个实现 KieRepositoryScannerImpl KieFileSystemScannerImpl 二者区别为 一个是从文件夹检查更新, 一个是从maven仓库检查更新

下面以 KieFileSystemScannerImpl 为例进行演示从文件夹获取更新 然后分析过程 最后自定义一个 KieScanner

准备kjar工程

其实就是个maven项目 存放着规则文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?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>fluffy.mo</groupId>
    <artifactId>kjarUpdate</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>11</java.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
</project>

src\main\resources\META-INF\kmodule.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <kbase name="kb1" packages="vdrl" default="true">
        <ksession name="ks-testUpdate" type="stateful" default="true"/>
    </kbase>
</kmodule>

src\main\resources\vdrl\testUpdate.drl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package vdrl;

global String tag
rule "aa1"
when
  total : Number()
      from accumulate( d: Double(), sum( d ) )
then
  System.out.println("v1--"+ tag + "--" + total );
end

打包成kjar

先打包一次 得到文件 kjarUpdate-0.0.1-SNAPSHOT.jar

1
mvn clean package

修改 pom.xml中的版本号为 0.0.2-SNAPSHOT 修改 testUpdate.drl 中 【v1】为【v2】 再打包 得到文件 kjarUpdate-0.0.2-SNAPSHOT.jar

1
mvn clean package

同上 得到 kjarUpdate-0.0.3-SNAPSHOT.jar

1
mvn clean package

kjar 全部放在 drools-demo-update 文件夹中

1
2
3
4
5
6
kjarUpdate-0.0.1-SNAPSHOT.jar
kjarUpdate-0.0.2-SNAPSHOT.jar
kjarUpdate-0.0.3-SNAPSHOT.jar
pom.xml
scanFoler/
src/

测试流程

先加载 0.0.1 版本的jar 执行规则, 然后升级到 0.0.2 版本的jar 执行规则,看是否升级成功

调用待测规则

jdk 11 kotlin 1.3.50 drools 7.18.0.Final

DroolsRunMyScanner.kt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package fluffy.test_update_rule

import org.kie.api.KieServices
import org.kie.api.builder.ReleaseIdComparator.ComparableVersion
import org.kie.api.runtime.KieSession
import java.util.concurrent.TimeUnit

val pjRoot = "E:\\myprogram\\my_workspace\\code_rep12\\some-task\\drools-demo-update";
// 初版规则 kjar
val kjar = "$pjRoot/kjarUpdate-0.0.1-SNAPSHOT.jar"
// 检查更新的文件夹
val updateFolder = "$pjRoot/scanFoler";
fun main() {
  mainV1();
}

fun mainV1() {
    val ks = KieServices.Factory.get()
    val kModule = ks.repository.addKieModule(ks.resources.newFileSystemResource(kjar))
    // 创建container
    val kContainer = ks.newKieContainer(kModule.getReleaseId())
    // 指定文件夹,每两秒检查一次更新
    val scanner = ks.newKieScanner(kContainer, updateFolder)
    scanner.start(TimeUnit.SECONDS.toMillis(2))

    val kss = kContainer.newKieSession("ks-testUpdate")
    kss.setGlobal("tag", "A");
    runRule(kss)
    println("此处打打断点--在文件夹【scanFoler】放入新的kjar--kjarUpdate-0.0.2-SNAPSHOT.jar-然后继续执行")
    TimeUnit.SECONDS.sleep(2)

    runRule(kss)
    println("完成--可看到之后调用的规则-更新为v2了-对之前创建的session也有效")
}

fun runRule(kss: KieSession) {
    kss.insert(1.01)
    kss.fireAllRules()
    kss.insert(1.02)
    kss.fireAllRules()
    kss.insert(1.03)
    kss.fireAllRules()
}

方法 mainV1()的逻辑为 根据v1版本的jar创建一个容器 新建一个session 运行到断点处, 手动将 kjar–kjarUpdate-0.0.2-SNAPSHOT.jar 放入 scanFoler 文件夹 然后再次触发规则 即可看到 规则已经更新

运行结果

1
2
3
4
5
6
7
8
v1--A--1.01
v1--A--2.0300000000000002
v1--A--3.0600000000000005
此处打打断点--在文件夹【scanFoler】放入新的kjar--kjarUpdate-0.0.2-SNAPSHOT.jar-然后继续执行
v2--A--4.07
v2--A--5.09
v2--A--6.12
完成--可看到之后调用的规则-更新为v2了-对之前创建的session也有效

分析 KieFileSystemScannerImpl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import java.io.File;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import org.drools.compiler.kie.builder.impl.AbstractKieScanner;
import org.drools.compiler.kie.builder.impl.InternalKieModule;
import org.drools.compiler.kie.builder.impl.KieContainerImpl;
import org.drools.core.impl.InternalKieContainer;
import org.kie.api.builder.KieScanner;
import org.kie.api.builder.ReleaseIdComparator.ComparableVersion;
import org.kie.api.runtime.KieContainer;

public class KieFileSystemScannerImpl extends AbstractKieScanner<InternalKieModule> implements KieScanner {

    private final File repositoryFolder;
    private final String kjarFileHead;
    private final VersionComparator versionComparator;

    public KieFileSystemScannerImpl(final KieContainer kieContainer, final String repositoryFolder) {
        this.kieContainer = ( InternalKieContainer ) kieContainer;
        // 查询当前的ArtifactId, 以确定文件夹下的新kjar以什么文件名开头
        this.kjarFileHead = kieContainer.getReleaseId().getArtifactId() + "-";
        // 用来从文件夹下找到最新的kjar
        this.versionComparator = new VersionComparator( kjarFileHead.length() );
        this.repositoryFolder = new File( repositoryFolder );
    }

    @Override
    protected InternalKieModule internalScan() {
        final File newKJar = findNewFile();
        return newKJar == null ? null : InternalKieModule.createKieModule(kieContainer.getReleaseId(), newKJar);
    }

    @Override
    protected void internalUpdate(final InternalKieModule kmodule ) {
        ((KieContainerImpl) kieContainer).updateToKieModule( kmodule );
    }

    private File findNewFile() { // 如果有新的规则kjar,则返回新kjar
        File[] files = repositoryFolder.listFiles((dir, name) -> name.startsWith(kjarFileHead) && name.endsWith(".jar" ));
        if (files == null || files.length == 0) {
            return null;
        }

        File candidateNew = getCandidateNew( files ); // 可能有多个 排序后取最新的
        // 比较 文件夹中的kjar版本 和 当前容器中使用的版本 , 返回新版本 或空
        int versionComparison = compareVersion(getVersionFromFile(candidateNew, kjarFileHead.length()), kieContainer.getReleaseId().getVersion());
        return versionComparison > 0 || ( versionComparison == 0 && kieContainer.getReleaseId().isSnapshot() ) ? candidateNew : null;
    }

    private File getCandidateNew( File[] files ) {
        if (files.length == 1) {
            return files[0];
        }
        final List<File> jarFiles = Arrays.asList(files);
        jarFiles.sort(versionComparator.reversed());
        return jarFiles.get(0);
    }

    private static class VersionComparator implements Comparator<File> {
        private final int headLength;

        private VersionComparator(final int headLength) {
            this.headLength = headLength;
        }

        @Override
        public int compare(final File f1, final File f2 ) {
            return compareVersion( getVersionFromFile(f1, headLength), getVersionFromFile(f2, headLength) );
        }
    }

    private static int compareVersion(String v1, String v2) {
        return new ComparableVersion( v1 ).compareTo( new ComparableVersion( v2 ) );
    }

    private static String getVersionFromFile(File f, int headLength) {
        String name = f.getName();
        return name.substring( headLength, name.length()-4 );
    }
}

测试 drools中比较版本的工具 org.kie.api.builder.ReleaseIdComparator.ComparableVersion

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fun testCompareVersion() {
    val v1 = ComparableVersion("myshop-1.0.0-20191101.165906-1.jar")
    val v2 = ComparableVersion("myshop-1.0.0-20191104.065907-3.jar")
    val v3 = ComparableVersion("myshop-1.2.0-SNAPSHOT.jar")
    val v4 = ComparableVersion("myshop-1.10.0-SNAPSHOT.jar")
    val v5 = ComparableVersion("myshop-1.10.0.Final.jar")

    println(v1.compareTo(v2))
    println(v2.compareTo(v3))
    println(v3.compareTo(v4))
    println(v4.compareTo(v5))
    // 结果显示: v5的版本号最大, 没毛病
    println(v5.compareTo(v1))
}

KieFileSystemScannerImpl 继承了 AbstractKieScanner 调度的逻辑在抽象类中 检查更新的逻辑 在 internalScan 方法中实现 然后 internalUpdate 中将新规则更新到容器中

自定义KieScanner

自定义后 可以从指定检查更新的来源 比如 从数据库查 从某个url去查询

自定义的scanner中 internalScan、 internalUpdate都是复制的 KieFileSystemScannerImpl 的逻辑 通过在 findNewFile 方法中编写自己的检查更新逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package fluffy.test_update_rule;

import org.drools.compiler.kie.builder.impl.AbstractKieScanner;
import org.drools.compiler.kie.builder.impl.InternalKieModule;
import org.drools.compiler.kie.builder.impl.KieContainerImpl;
import org.drools.core.impl.InternalKieContainer;
import org.kie.api.builder.KieScanner;
import org.kie.api.runtime.KieContainer;

import java.io.File;

public class KieMyScannerImpl extends AbstractKieScanner<InternalKieModule> implements KieScanner {
    private final File repositoryFolder;
    int count = 1;

    public KieMyScannerImpl(final KieContainer kieContainer, final String repositoryFolder) {
        this.kieContainer = (InternalKieContainer) kieContainer;
        // 查询当前的ArtifactId, 以确定文件夹下的新kjar以什么文件名开头
        this.repositoryFolder = new File(repositoryFolder);
    }

    @Override
    protected InternalKieModule internalScan() {
        // 同 KieFileSystemScannerImpl 相同
        final File newKJar = findNewFile();
        return newKJar == null ? null : InternalKieModule.createKieModule(kieContainer.getReleaseId(), newKJar);
    }

    @Override
    protected void internalUpdate(final InternalKieModule kmodule) {
        // 同 KieFileSystemScannerImpl 相同
        ((KieContainerImpl) kieContainer).updateToKieModule(kmodule);
    }

    private File findNewFile() {
        // 查找新文件的逻辑, 此处写死了
        count++;
        String path = repositoryFolder.getAbsolutePath();
        File f = null;
        if (count == 2) {
            f = new File(path + "/kjarUpdate-0.0.2-SNAPSHOT.jar");
        } else if (3 == count) {
            f = new File(path + "/kjarUpdate-0.0.3-SNAPSHOT.jar");
        }
        if (f != null) {
            System.out.println("发现新规则--" + f.getName());
        }
        return f;
    }
}

运行自定义检测类的逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun mainV2() {
    val ks = KieServices.Factory.get()
    val kModule = ks.repository.addKieModule(ks.resources.newFileSystemResource(kjar))
    // 创建container
    val kContainer = ks.newKieContainer(kModule.getReleaseId())

    val ksss = kContainer.newKieSession("ks-testUpdate")
    ksss.setGlobal("tag", "A");
    runRule(ksss)

    val scanner = KieMyScannerImpl(kContainer, pjRoot)
    scanner.start(TimeUnit.SECONDS.toMillis(3))

    println("此处不打断点--scanner写死了 两个新版本")
    TimeUnit.SECONDS.sleep(4)
    runRule(ksss)

    TimeUnit.SECONDS.sleep(4)
    runRule(ksss)

    println("done")
}

运行结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
v1--A--1.01
v1--A--2.0300000000000002
v1--A--3.0600000000000005
此处不打断点--scanner写死了 两个新版本
发现新规则--kjarUpdate-0.0.2-SNAPSHOT.jar
v2--A--4.07
v2--A--5.09
v2--A--6.12
发现新规则--kjarUpdate-0.0.3-SNAPSHOT.jar
v3--A--7.130000000000001
v3--A--8.15
v3--A--9.18
done

归根结底 还是调用的 kieContainer.updateToKieModule( kmodule ); 来更新规则的