“万恶”之源的KieServices,获取代码就一行,表面代码越少里面东西就越多,本以为就是个简单的工厂方法,没想到里面弯弯绕绕这么多东西

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();
}

这是将传入的字符串数组进行流化,然后按照下面顺序进行操作:

  1. 映射:对每一个字符串进行操作,调用ServiceUtil的instance,通过全路径名实例化类,代码很简单,如下所示:

    代码段8
    private static Optional<?> instance(String className) {try {return Optional.of(Class.forName(className).getConstructor().newInstance());} catch (Exception var2) {return Optional.empty();}
    }

  2. 过滤:判断得到的实例化的类是否为空,如果为空则会被过滤掉

  3. 映射:将第二步没有过滤掉的实例从Optional中get出来

  4. 寻找:找到第一个符合条件的类,将其返回

  5. 异常:如果没有符合条件的类,就抛出异常

按照源码来走,这一步会获得到类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);}}}
}

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/90575.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LangChain手记 Models,Prompts and Parsers

整理并翻译自DeepLearning.AILangChain的官方课程&#xff1a;Models,Prompts and Parsers 模型&#xff0c;提示词和解析器&#xff08;Models, Prompts and Parsers&#xff09; 模型&#xff1a;大语言模型提示词&#xff1a;构建传递给模型的输入的方式解析器&#xff1a;…

大语言模型:LLM的概念是个啥?

一、说明 大语言模型&#xff08;维基&#xff1a;LLM- large language model&#xff09;是以大尺寸为特征的语言模型。它们的规模是由人工智能加速器实现的&#xff0c;人工智能加速器能够处理大量文本数据&#xff0c;这些数据大部分是从互联网上抓取的。 [1]所构建的人工神…

Qt应用开发(基础篇)——工具箱 QToolBox

一、前言 QToolBox类继承于QFrame&#xff0c;QFrame继承于QWidget&#xff0c;是Qt常用的基础工具部件。 框架类QFrame介绍 QToolBox工具箱类提供了一列选项卡窗口&#xff0c;当前项显示在当前选项卡下面&#xff0c;适用于分类浏览、内容展示、操作指引这一类的使用场景。 二…

基于熵权法对Topsis模型的修正

由于层次分析法的最大缺点为&#xff1a;主观性太强&#xff0c;影响判断&#xff0c;对结果有很大影响&#xff0c;所以提出了熵权法修正。 变异程度方差/标准差。 如何度量信息量的大小&#xff1a; 把不可能的事情变成可能&#xff0c;这里面就有很多信息量。 概率越大&…

KCC@广州开源读书会广州开源建设讨论会

亲爱的开源读书会朋友们&#xff0c; 在下个周末我们将举办一场令人激动的线下读书会&#xff0c;探讨两本引人入胜的新书《只是为了好玩》和《开源之迷》。作为一个致力于推广开源精神和技术创新的社区&#xff0c;这次我们还邀请了圈内大咖前来参与&#xff0c;会给大家提供一…

瑞数信息《2023 API安全趋势报告》重磅发布: API攻击持续走高,Bots武器更聪明

如今API作为连接服务和传输数据的重要通道&#xff0c;已成为数字时代的新型基础设施&#xff0c;但随之而来的安全问题也日益凸显。为了让各个行业更好地应对API安全威胁挑战&#xff0c;瑞数信息作为国内首批具备“云原生API安全能力”认证的专业厂商&#xff0c;近年来持续输…

观察者模式实战

场景 假设创建订单后需要发短信、发邮件等其它的操作&#xff0c;放在业务逻辑会使代码非常臃肿&#xff0c;可以使用观察者模式优化代码 代码实现 自定义一个事件 发送邮件 发送短信 最后再创建订单的业务逻辑进行监听&#xff0c;创建订单 假设后面还需要做其它的…

【12】Git工具 协同工作平台使用教程 Gitee使用指南 腾讯工蜂使用指南【Gitee】【腾讯工蜂】【Git】

tips&#xff1a;少量的git安装和使用教程&#xff0c;更多讲快速使用上手Gitee和工蜂平台 一、准备工作 1、下载git Git - Downloads (git-scm.com) 找到对应操作系统&#xff0c;对应版本&#xff0c;对应的位数 下载后根据需求自己安装&#xff0c;然后用git --version验…

自动化更新导致的各种问题解决办法

由于最近自动化频频更新导致出现各种问题&#xff0c;因此在创建驱动对象代码时改成这种方式 我最近就遇到了由于更新而导致的代码报错&#xff0c;报错信息如下&#xff1a; 复制内容如下&#xff1a; Exception in thread “main” org.openqa.selenium.remote.http.Connecti…

【C++】多态的概念和简单介绍、虚函数、虚函数重写、多态构成的条件、重载、重写、重定义

文章目录 多态1.多态的概念和介绍2.虚函数2.1final2.2override 3.虚函数的重写3.1协变3.2析构函数的重写 4.多态构成的条件5.重载、重写、重定义...... 多态 1.多态的概念和介绍 C中的多态是一种面向对象编程的特性&#xff0c;它允许不同的对象对同一个消息做出不同的响应。 …

Hazel 引擎学习笔记

目录 Hazel 引擎学习笔记学习方法思考引擎结构创建工程程序入口点日志系统Premake\MD没有 cpp 文件的项目会出错include 到某个库就要包含这个库的路径&#xff0c;注意头文件展开 事件系统 获取和利用派生类信息预编译头文件抽象窗口类和 GLFWgit submodule addpremake 脚本禁…

【JVM】对String::intern()方法深入详解(JDK7及以上)

文章目录 1、什么是intern&#xff1f;2、经典例题解释例1例2例3 1、什么是intern&#xff1f; String::intern()是一个本地方法&#xff0c;它的作用是如果字符串常量池中已经包含一个等于此String对象的字符串&#xff0c;则返回代表池中这个字符串的String对象的引用&#…

7-15 然后是几点

有时候人们用四位数字表示一个时间&#xff0c;比如 1106 表示 11 点零 6 分。现在&#xff0c;你的程序要根据起始时间和流逝的时间计算出终止时间。 读入两个数字&#xff0c;第一个数字以这样的四位数字表示当前时间&#xff0c;第二个数字表示分钟数&#xff0c;计算当前时…

【Vue-Router】嵌套路由

footer.vue <template><div><router-view></router-view><hr><h1>我是父路由</h1><div><router-link to"/user">Login</router-link><router-link to"/user/reg" style"margin-left…

代码随想录算法训练营(二叉树总结篇)

一.二叉树的种类 1.满二叉树&#xff1a;就是说每一个非叶子节点的节点都有两个子节点。 2.完全二叉树&#xff1a;此二叉树只有最后一层可能没填满&#xff0c;并且存在的叶子节点都集中在左侧&#xff01;&#xff01;&#xff01; &#xff08;满二叉树也是完全二叉树&…

【Flutter】【基础】CustomPaint 绘画功能(一)

功能&#xff1a;CustomPaint 相当于在一个画布上面画画&#xff0c;可以自己绘制不同的颜色形状等 在各种widget 或者是插件不能满足到需求的时候&#xff0c;可以自己定义一些形状 使用实例和代码&#xff1a; CustomPaint&#xff1a; 能使你绘制的东西显示在你的ui 上面&a…

安装Tomac服务器——安装步骤以及易出现问题的解决方法

文章目录 前言 一、下载Tomcat及解压 1、选择下载版本&#xff08;本文选择tomcat 8版本为例&#xff09; 2、解压安装包 二、配置环境 1、在电脑搜索栏里面搜索环境变量即可 2、点击高级系统设置->环境变量->新建系统变量 1) 新建系统变量&#xff0c;变量名为…

nginx一般轮询、加权轮询、ip_hash等负载均衡模式配置介绍

一.负载均衡含义简介 二.nginx负载均衡配置方式 准备三台设备&#xff1a; 2.190均衡服务器&#xff0c;2.191web服务器1&#xff0c;2.160web服务器2&#xff0c;三台设备均安装nginx&#xff0c;两台web服务器均有网页内容 1.一般轮询负载均衡 &#xff08;1&#xff09…

Autoware感知02—欧氏聚类(lidar_euclidean_cluster_detect)源码解析

文章目录 引言一、点云回调函数&#xff1a;二、预处理&#xff08;1&#xff09;裁剪距离雷达过于近的点云&#xff0c;消除车身的影响&#xff08;2&#xff09;点云降采样&#xff08;体素滤波&#xff0c;默认也是不需要的&#xff09;&#xff08;3&#xff09;裁剪雷达高…

React Native 图片组件基础知识

在 React Native 中使用图片其实跟 HTML 中使用图片一样简单&#xff0c;在 React Native 中我们使用Image组件来呈现图片的内容&#xff0c;其中主要的属性有&#xff1a;source。这个属性主要是设置图片的内容&#xff0c;它可以是网络图像地址、静态资源、临时本地图像以及本…