Drools用户手册看了得有一段时间了,现在开始看源码了,因为每次使用drools都会看见这么一段代码:
代码段1 起手代码
KieServices ks = KieServices.Factory.get();
那我就从这段代码出发开始研究drools的源码吧,这么一小段代码起初我还真没看起它,结果被啪啪打脸了,里面东西可是多的狠啊,这段代码的目的不难看出来,就是获取KieServices的实例的,从代码来看,Factory就是KieSerices的内部类,然后其有一个get方法,用来获取KieServices的实例,源代码如下所示:
代码段2 KieServices类的内部类Factory
public static class Factory {public Factory() {}public static KieServices get() {return KieServices.Factory.LazyHolder.INSTANCE;}private static class LazyHolder {private static KieServices INSTANCE = (KieServices)ServiceRegistry.getService(KieServices.class);private LazyHolder() {}}
}
只看get方法,这里直接就是一个返回语句,返回的是LazyHolder,这又是一个内部类,也就是说LazyHolder是内部类的内部类,这个内部类的内部类里面有一个静态属性,叫做INSTANCE(实例),这个INSTANCE是如何获取的?是从ServiceRegistry类的getService方法获取的,因为要获取的就是KieServices的实例,所以将其作为参数传了进去。
这个时候就要研究ServiceRegistry类了,从类名来看,这是一个服务注册类,也就是说在获取KieService之前,这个服务已经在Kie中注册了 ,使用的时候就是直接获取,来看看这个ServiceRegistry的getService方法的源代码:
代码段3 ServiceRegistry的getService方法
static <T> T getService(Class<T> cls) {return getInstance().get(cls);
}
非常简单的代码,里面又涉及到了两个方法,一个是getInstance方法,一个是get方法。
ServiceRegistry类的getInstance方法
代码段4 ServiceRegistry的getinstance方法
static ServiceRegistry getInstance() {return ServiceRegistry.ServiceRegistryHolder.serviceRegistry;
}
这个方法涉及到了ServiceRegistry类中的内部类ServiceRegistryHolder的一个属性——serviceRegistry。
代码段5 ServiceRegistry的内部类ServiceRegistryHolder
public static class ServiceRegistryHolder {private static ServiceRegistry serviceRegistry = ServiceRegistry.Impl.getServiceRegistry();public ServiceRegistryHolder() {}
}
这是内部类ServiceRegistryHolder的全部源码,因为不多,就全放出来了,也很简单,serviceRegistry属性就是ServiceRegistry的内部类Impl中的getServiceRegistry方法,可以说从一个内部类调用另一个内部类的方法,我也是一时间没想到这么做的意义是什么,但是我之前在敲代码的时候好像也有这么干过,如果之后想起来,我再回来把想法给补上。
那就先来看看这个getServiceRegistry方法吧:
代码段6 ServiceRegistry的内部类Impl类的ServiceRegistry方法
public static ServiceRegistry() {if (supplier == null) {supplier = (Supplier)ServiceUtil.instanceFromNames(new String[]{"org.drools.dynamic.DynamicServiceRegistrySupplier", "org.drools.statics.StaticServiceRegistrySupplier"});}return (ServiceRegistry)supplier.get();
}
这是将传入的字符串数组进行流化,然后按照下面顺序进行操作:
-
映射:对每一个字符串进行操作,调用ServiceUtil的instance,通过全路径名实例化类,代码很简单,如下所示:
代码段8private static Optional<?> instance(String className) {try {return Optional.of(Class.forName(className).getConstructor().newInstance());} catch (Exception var2) {return Optional.empty();} }
-
过滤:判断得到的实例化的类是否为空,如果为空则会被过滤掉
-
映射:将第二步没有过滤掉的实例从Optional中get出来
-
寻找:找到第一个符合条件的类,将其返回
-
异常:如果没有符合条件的类,就抛出异常
按照源码来走,这一步会获得到类DynamicServiceRegistrySupplier,源码如下:
代码段9 DynamicServiceRegistrySupplier源码
public class DynamicServiceRegistrySupplier implements Supplier<ServiceRegistry> {public DynamicServiceRegistrySupplier() {}public ServiceRegistry get() {return DynamicServiceRegistrySupplier.LazyHolder.INSTANCE;}static class LazyHolder {static final Impl INSTANCE = new Impl();LazyHolder() {}}
}
这一段代码会返回给代码段6,然后会执行get方法,这个方法返回的是ServiceRegistry内部类Impl的实例,这就是getInstance方法最终的结果,就是Impl的实例
内部类Impl的get方法
返回到代码段3中,里面的getInstance方法已经走了一遍,现在开始走这个get方法,其源码如下所示:
代码段10 ServiceRegistry的内部类Impl实现的get方法
public <T> T get(Class<T> cls) {Iterator var2 = this.getAll(cls).iterator();Object service;do {if (!var2.hasNext()) {return null;}service = var2.next();} while(!cls.isInstance(service));return service;
}
这段代码先是调用了一个getAll的方法,看看getAll的源码:
代码段11 ServiceRegistry的内部类Impl的getAll方法
public <T> List<T> getAll(Class<T> cls) {return (List)this.registry.getOrDefault(cls.getCanonicalName(), Collections.emptyList());
}
这个代码看似很简单,但是其实大部分的内容都在这里,这段代码是通过传入类的全路径名获取一个列表,如果没有该全路径名,就会返回一个空列表,那这个registry属性究竟是个什么呢?
代码段12 ServiceRegistry的内部类Impl的registry属性
private Map<String, List<Object>> registry = ServiceDiscoveryImpl.getInstance().getServices();
是一个映射,我们可以看到,之前的操作,可没有对这个映射进行初始化的地方,这个东西又是另外的一个路线了,也就是在前面代码段9,实例化Impl的时候,这里就已经初始化了,为了这一路可以顺利结束,我先跳过这一段,咱们后面再讲,咱们回到代码段10,通过getAll获取了一个服务列表,通过迭代找到KieServices实例,然后返回该实例,这个时候代码段1就完成了调用,获得了一个KieServices。
服务注册与配置
这把我们再来看这个代码段12里面的东西,首先是ServiceDiscoveryImpl类,这个类的描述是这样的:
代码段13
public class ServiceDiscoveryImpl {...}
既不是哪个类的子类,也不是什么类的实现类,他就是一个孤零零的,类。那这就简单多了,我们直奔这个getInstance方法而去:
代码段14 ServiceDiscoveryImpl的getInstance方法和内部类LazyHolder
public static ServiceDiscoveryImpl getInstance() {return ServiceDiscoveryImpl.LazyHolder.INSTANCE;
}private static class LazyHolder {static final ServiceDiscoveryImpl INSTANCE = new ServiceDiscoveryImpl();private LazyHolder() {}
}
这个getInstance方法,和之前的的某些代码段用法一致,获取某个内部类的属性,这个属性就是这个孤零零的类的实例,然后再是代码段12里面的getServices方法:
代码段15 ServiceDiscoveryImpl的getServices方法
public synchronized Map<String, List<Object>> getServices() {if (!this.sealed) {this.getKieConfs().ifPresent((kieConfs) -> {Iterator var2 = kieConfs.resources.iterator();while(var2.hasNext()) {URL kieConfUrl = (URL)var2.next();this.registerConfs(kieConfs.classLoader, kieConfUrl);}});//创建一个不可变的映射this.cachedServices = Collections.unmodifiableMap(this.buildMap());this.sealed = true;}return this.cachedServices;
}
这是一个同步方法,sealed初始默认值是false,所以进入方法后会直接进入到if语句中。先是获取到kie的配置内容,对非空配置进行遍历加载。获取配置是一个大活,操作异常的复杂,咱们就从这个getKieConfs方法开始走起:
代码段16 ServiceDiscoveryImpl的getKieConfs方法
private Optional<ServiceDiscoveryImpl.KieConfs> getKieConfs() {return Stream.of(this.getClass().getClassLoader(), Thread.currentThread().getContextClassLoader(), ClassLoader.getSystemClassLoader()).map(this::loadKieConfs).filter(Objects::nonNull).findFirst();
}
这个操作很简单,先是将三个类加载器传入Stream.of中,使其组成一个类加载器数组,然后将其流化。
对每一个类加载器执行lodaKieConfs操作,对map的返回值进行过滤,过滤掉null值,最后拿到第一个符合条件的配置,返回。这一步操作,只有lodaKieConfs是一个方法操作,这个操作的代码如下:
代码段17 ServiceDiscoveryImpl的loadKieConfs方法
private ServiceDiscoveryImpl.KieConfs loadKieConfs(ClassLoader cl) {if (cl == null) {return null;} else {try {Collection<URL> resources = findKieConfUrls(cl);return resources.isEmpty() ? null : new ServiceDiscoveryImpl.KieConfs(cl, resources);} catch (IOException var3) {return null;}}
}
先是判断传入的类加载器是否为空,如果不为空,会执行findKieConfUrls方法,该方法的代码如下:
代码段18 ServiceDiscoveryImpl的findKieConfUrls方法
private static Collection<URL> findKieConfUrls(ClassLoader cl) throws IOException {//声明一个空的URL地址列表List<URL> kieConfsUrls = new ArrayList();//获取类加载器中所有"Meta-INF/kie"文件夹下的资源的枚举Enumeration metaInfs = cl.getResources("META-INF/kie");//遍历枚举值while(metaInfs.hasMoreElements()) {//资源路径URL metaInf = (URL)metaInfs.nextElement();//如果资源是来自虚拟文件系统,则清空列表,并跳出循环//vfs是"Virtual File System"(虚拟文件系统)协议.if (metaInf.getProtocol().startsWith("vfs")) {((List)kieConfsUrls).clear();break;}//打开资源连接URLConnection con = metaInf.openConnection();//判断当前连接类型是Jar还是其他类型if (con instanceof JarURLConnection) {//收集JAR中的kie配置地址collectKieConfsInJar((List)kieConfsUrls, metaInf, (JarURLConnection)con);} else {//收集文件中的kie配置地址collectKieConfsInFile((List)kieConfsUrls, new File(metaInf.getFile()));}}if (((List)kieConfsUrls).isEmpty()) {//如果经过之前操作,配置URL地址列表为空kieConfsUrls = (List)getKieConfsFromKnownModules(cl).collect(Collectors.toList());} else {//寻找未注册的模块List<String> notRegisteredModules = (List)((List)kieConfsUrls).stream().map(ServiceDiscoveryImpl::getModuleName).filter((module) -> {return Arrays.binarySearch(KIE_MODULES, module) < 0;}).collect(Collectors.toList());//如果未注册模块的列表不为空,则抛出异常if (!notRegisteredModules.isEmpty()) {throw new IllegalStateException("kie.conf file discovered for modules " + notRegisteredModules + " but not listed among the known modules. This will not work under OSGi or JBoss vfs.");}}//获取类加载器中所有"Meta-INF/kie.conf"文件Enumeration kieConfEnum = cl.getResources("META-INF/kie.conf");//直接将该文件的URL添加到URL地址列表中while(kieConfEnum.hasMoreElements()) {((List)kieConfsUrls).add((URL)kieConfEnum.nextElement());}if (log.isDebugEnabled()) {log.debug("Discovered kie.conf files: " + kieConfsUrls);}//返回配置文件URL地址列表return (Collection)kieConfsUrls;
}
因为这是一段很长的代码,所以我把解释都放到了注释里面,这里面涉及到的其他方法的源代码如下:
收集JAR中的kie配置地址
代码段19 ServiceDiscoveryImpl的collectKieConfsInJar方法
private static void collectKieConfsInJar(List<URL> kieConfsUrls, URL metaInf, JarURLConnection con) throws IOException {//获取jar文件JarFile jarFile = con.getJarFile();//获取该jar里面的资源条目Enumeration entries = jarFile.entries();//遍历条目while(entries.hasMoreElements()) {JarEntry entry = (JarEntry)entries.nextElement();//如果条目是以kie.conf为结尾,则将其添加到kie配置地址列表中if (entry.getName().endsWith("kie.conf")) {String metaInfString = metaInf.toString();int confFileFolderLength = metaInfString.endsWith("/") ? "META-INF/kie".length() + 1 : "META-INF/kie".length();kieConfsUrls.add(new URL(metaInfString.substring(0, metaInfString.length() - confFileFolderLength) + entry.getName()));}}}
收集文件中的kie配置地址
代码段20 ServiceDiscoveryImpl的collectKieConfsInFile方法
private static void collectKieConfsInFile(List<URL> kieConfsUrls, File file) throws IOException {if (file.isDirectory()) {//如果文件是一个文件夹,则获取文件夹里面的文件数组File[] var2 = file.listFiles();//获取文件数组的大小int var3 = var2.length;//遍历文件for(int var4 = 0; var4 < var3; ++var4) {File child = var2[var4];//递归调用collectKieConfsInFile(kieConfsUrls, child);}} else if (file.toString().endsWith("kie.conf")) {//如果文件是以kie.conf结尾,则将当前文件的URL直接加入到kie配置地址列表中kieConfsUrls.add(file.toURI().toURL());}
}
从已知的模块中寻找配置文件地址
代码段21 ServiceDiscoveryImpl的getKieConfsFromKnownModules方法
public static Stream<URL> getKieConfsFromKnownModules(ClassLoader cl) {return Stream.of(KIE_MODULES).map((module) -> {return cl.getResource("META-INF/kie/" + module + (module.length() > 0 ? "/" : "") + "kie.conf");}).filter(Objects::nonNull);
}
这里面主要的就是KIE_MODULES,需要将这里面的东西流化之后找到对应的资源地址
代码段22 ServiceDiscoveryImpl的KIE_MODULES常量
private static final String[] KIE_MODULES = new String[]{"", "drools-alphanetwork-compiler", "drools-beliefs", "drools-compiler", "drools-core", "drools-decisiontables", "drools-metric", "drools-model-compiler", "drools-mvel", "drools-persistence-jpa", "drools-ruleunit", "drools-scorecards", "drools-serialization-protobuf", "drools-traits", "drools-workbench-models-guided-dtable","drools-workbench-models-guided-scorecard", "drools-workbench-models-guided-template", "jbpm-bpmn2", "jbpm-case-mgmt-cmmn", "jbpm-flow", "jbpm-flow-builder", "jbpm-human-task-jpa", "kie-ci", "kie-dmn-core", "kie-dmn-jpmml","kie-internal", "kie-pmml","kie-pmml-evaluator-assembler", "kie-pmml-evaluator-core", "kie-server-services-jbpm-cluster"};
注册配置
这个地方需要回到代码段15,在获取到配置文件地址列表之后,对配置进行一个注册
代码段23 ServiceDiscoveryImpl的registerConfs方法
public void registerConfs(ClassLoader classLoader, URL url) {log.debug("Loading kie.conf from {} in classloader {}", url, classLoader);try {BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));try {for(String line = br.readLine(); line != null; line = br.readLine()) {if (line.contains("=") && !line.contains("[")) {String[] entry = line.split("=");//这里需要再解释一下<----------------<----------------<--------看这里this.processKieService(classLoader, entry[0].trim(), entry[1].trim());}}} catch (Throwable var7) {try {br.close();} catch (Throwable var6) {var7.addSuppressed(var6);}throw var7;}br.close();} catch (Exception var8) {throw new RuntimeException("Unable to build kie service url = " + url.toExternalForm(), var8);}
}
这一段代码没什么好解释的,就是读取文件内容,进行处理,处理的内容需要好好解释一下,也就是代码段23中注释的地方
代码段24 ServiceDiscoveryImpl的processKieService方法
private void processKieService(ClassLoader classLoader, String key, String values) {//将值通过字符串“,”号分割为字符串数组String[] var4 = values.split(",");//获取分割后数量int var5 = var4.length;//遍历分割后的数组for(int var6 = 0; var6 < var5; ++var6) {String value = var4[var6];//判断key值是不是以“?”号开头boolean optional = key.startsWith("?");//将开头的“?”号去掉,获取service名String serviceName = optional ? key.substring(1) : key;try {if (value.startsWith("+")) {//如果值是以“+”开头,从childServices映射中获取serviceName对应的列表,//如果childServices没有serviceName关键字,则直接返回一个新的列表//将返回的列表中加入实例((List)this.childServices.computeIfAbsent(serviceName, (k) -> {return new ArrayList();})).add(this.newInstance(classLoader, value.substring(1)));log.debug("Added child Service {}", value);} else {//通过符号“;”将值分割,如果分割的结果数组数量大于2,则抛出异常String[] splitValues = value.split(";");if (splitValues.length > 2) {throw new RuntimeException("Invalid kie.conf entry: " + value);}//如果分割的数组长度是2,则优先级为数组下标为1对应的数,//如果分割的数组长度是1.也就是没有配置优先级,则优先级为0int priority = splitValues.length == 2 ? Integer.parseInt(splitValues[1].trim()) : 0;//将服务添加到一个优先级映射中this.services.put(priority, serviceName, this.newInstance(classLoader, splitValues[0].trim()));log.debug("Added Service {} with priority {}", value, priority);}} catch (RuntimeException var12) {if (!optional) {log.error("Loading failed because {}", var12.getMessage());throw var12;}log.info("Cannot load service: {}", serviceName);}}}
总结
最后我们看一下,一小段短短的代码,里面却包含了如此多的工作,先是要实例化服务注册也就是Impl类,在实例化的时候需要通过服务发现类将所有的配置文件获取,建立服务列表,最后通过传入服务类的类名,获取服务类。流程就是这么个流程,你说简单他也简单,你说难我觉得你说的对,最后我也有个地方没有明白,可能是源码看太多脑子浆糊了,代码段15里面,还有一个buildMap的方法,源码如下,谁能给我解释解释,最后那一段代码在做什么?
代码段X ServiceDiscoveryImpl的buildMap方法
private Map<String, List<Object>> buildMap() {Map<String, List<Object>> servicesMap = new HashMap();//处理KieService方法里面的优先级映射转成迭代器Iterator var2 = this.services.entrySet().iterator();while(true) {Entry serviceEntry;List children;do {if (!var2.hasNext()) {//如果优先级映射里面没有下一个元素if (!this.childServices.isEmpty()) {//如果子服务列表不空,则抛出异常throw new RuntimeException("Child services " + this.childServices.keySet() + " have no parent");}if (log.isTraceEnabled()) {//如果日志是可追踪的,则将优先级列表里面的内容重新迭代一遍,用来打印日志var2 = servicesMap.entrySet().iterator();while(var2.hasNext()) {serviceEntry = (Entry)var2.next();if (((List)serviceEntry.getValue()).size() == 1) {log.trace("Service {} is implemented by {}", serviceEntry.getKey(), ((List)serviceEntry.getValue()).get(0));} else {log.trace("Service {} is implemented (in order of priority) by {}", serviceEntry.getKey(), serviceEntry.getValue());}}}return servicesMap;}//获取服务条目serviceEntry = (Entry)var2.next();log.debug("Service {} is implemented by {}", serviceEntry.getKey(), ((List)serviceEntry.getValue()).get(0));//将服务条目放入到服务映射中servicesMap.put((String)serviceEntry.getKey(), (List)serviceEntry.getValue());//移除childService中的已经添加到服务映射中的条目children = (List)this.childServices.remove(serviceEntry.getKey());} while(children == null);//从这开始下面的这段代码我是没太搞懂是要做什么Iterator var5 = children.iterator();while(var5.hasNext()) {Object child = var5.next();Iterator var7 = ((List)serviceEntry.getValue()).iterator();while(var7.hasNext()) {Object service = var7.next();((Consumer)service).accept(child);}}}
}