Drools 作为一个优秀的开源规则引擎,它的功能无疑是非常强大的。与普通的规则硬编码相比,Drools 有着非常多的优点,比如:规则的动态更新、规则配置的可视化等。
使用或了解过 Drools 的小伙伴们应该知道它里面定义了很多的概念,其中有一个比较重要的就是 KieModule,而 Drools 的规则动态更新也就是基于对 KieModule 地动态加载。由于一般在现实的使用场景中,规则内容都会被打包成一个 Jar 文件,然后由 KieScanner 根据 Jar 的 GAV(groupId/artifactId/version) 对其进行扫描加载,从而实现规则的动态更新。那么 Drools 是怎样根据 GAV 来控制规则的版本管理的呢?
在 Drools 里面,规则包是强依赖于 Maven 的,规则管理的抽象模型也与其基本一致,与 Maven 的 jar 仓库类似,Drools 也有着自己的仓库对 KieModule 进行管理,当然,Drools 的仓库只是程序代码中一个概念。
Drools 的仓库非常简单,首先,我们来看一下仓库相关的类图:
类的层次结构非常简单,一个接口,一个实现类,就完成了整个对 KieModule 的管理,当然只限于 KieModule,不包含其里面的 KieBase、KieSession 之类的。
而对于 KieModule 的管理,所有的操作也只有三种:添加(addKieModule)、删除(removeKieModule)、获取(getKieModule)。而这三类操作却全部委派给 KieRepositoryImpl.KieModuleRepo 这个内部类来完成了,这个类将是我们学习的重点。
KieModuleRepo 属性:用于定义 KieModule 缓存区的大小。
// GA 缓存的最大 Size 相关属性
public static final String CACHE_GA_MAX_PROPERTY = "kie.repository.project.cache.size";
static final int MAX_SIZE_GA_CACHE // made changeable for test purposes
= Integer.parseInt(System.getProperty(CACHE_GA_MAX_PROPERTY, "100"));
// Version 缓存的最大 Size 相关属性
public static final String CACHE_VERSIONS_MAX_PROPERTY = "kie.repository.project.versions.cache.size";
static final int MAX_SIZE_GA_VERSIONS_CACHE // made changeable for test purposes
= Integer.parseInt(System.getProperty(CACHE_VERSIONS_MAX_PROPERTY, "10"));
KieModule 的缓存分为两类:
// KieModules 缓存区,大小为 GA Size
final Map> kieModules
= new LinkedHashMap>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry> eldest) {
return (size() > MAX_SIZE_GA_CACHE);
}
};
// oldKieModules 缓存区,大小为 GA Size * Version Size
final LinkedHashMap oldKieModules = new LinkedHashMap() {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > (MAX_SIZE_GA_CACHE * MAX_SIZE_GA_VERSIONS_CACHE);
}
};
上述的两个缓存区都使用 LinkedHashMap 实现了 LRU(近期最少使用)算法,用来控制缓存区的大小,在缓存区无多余容量时,将最少使用的 KieModule 移出缓存区(新技能 Get)。
整个 KieModule 的添加流程可以分成三步:
第一步:ReleaseId 的拆解,即分为 GA 与 Version。
第二步:KieModule 二级缓存 NavigableMap 的获取(创建)。
第三步:备份旧的 KieModule,并添加新的 KieModule。
synchronized void store(KieModule kieModule) {
ReleaseId releaseId = kieModule.getReleaseId();
// 拼接 groupId 及 artifactId 用来标识仓库里面一组同 GA 的 KieModule
String ga = releaseId.getGroupId() + ":" + releaseId.getArtifactId();
// 根据 KieModule 的版本号通过某种算法计算出一个可比较的版本
ComparableVersion comparableVersion = new ComparableVersion(releaseId.getVersion());
// 获取相同 GA 的 KieModule 集合
NavigableMap artifactMap = kieModules.get(ga);
if( artifactMap == null ) {
artifactMap = createNewArtifactMap();
kieModules.put(ga, artifactMap);
}
// 正式添加 KieModule 之前,备份旧的 KieModule
KieModule oldReleaseIdKieModule = oldKieModules.get(releaseId);
// variable used in order to test race condition
if (oldReleaseIdKieModule == null) {
KieModule oldKieModule = artifactMap.get(comparableVersion);
if (oldKieModule != null) {
oldKieModules.put( releaseId, oldKieModule );
}
}
// 正式添加 KieModule
artifactMap.put( comparableVersion, kieModule );
}
在这三步操作中,第一步里面有一个 ComparableVersion 的计算,这里暂时不做赘述,详情请参考 ComparableVersion 的实现。重点看一下第二步中的 NavigableMap 的初始化,也是版本管理中的重中之重。
private NavigableMap createNewArtifactMap() {
// 使用实现 TreeMap 匿名内部类,作用:保证 KieModule 缓存中元素按照 ComparableVersion 进行排序
NavigableMap newArtifactMap = new TreeMap() {
// 此处定义一个 Map 指向 TreeMap 本身的实例,是因为需要在索引队列中,对真实的 KieModule 缓存进行了删除操作
// 而匿名内部类无法访问到外部类的实例,所以增加一个 final 的引用指向外部类的一个实例,即真实的缓存容器
private final Map artifactMap = this;
// 使用 LinkedHashMap 实现一个 LRU 队列,存储了真实缓存 artifactMap 里面的 Key(相当于索引),下面称索引队列
// 主要作用:限制 KieModule 缓存容器的无限增长
LinkedHashMap backingLRUMap = new LinkedHashMap(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry( Map.Entry eldest ) {
// 当集合中元素数量超过缓存允许的最大限制时,移除最老(根据访问顺序或插入顺序判断)的元素
boolean remove = (size() > MAX_SIZE_GA_VERSIONS_CACHE);
// 根据 backingLRUMap 存储的索引删除真实缓存 artifactMap 里面的 KieModule
if( remove ) {
artifactMap.remove(eldest.getKey());
}
return remove;
}
};
@Override
public KieModule put( ComparableVersion key, KieModule value ) {
// 向索引队列里面插入一个最新的索引
backingLRUMap.put(key, PRESENT);
// 向真实缓存 artifactMap 中插入最新的 KieModule,此处不可以使用 artifactMap 直接调用,会造成死循环
return super.put(key, value);
}
};
return newArtifactMap;
}
可以看到注释比代码还要多,由此可见这段代码的逻辑较之前面的代码复杂了很多。此段代码我们从变量入手来剖析它:
程序虽然短小,但设计十分精巧,令人叹服。