Dubbo SPI源码
文章目录
- Dubbo SPI
- 使用方式
- AOP功能
- 源码剖析
- @SPI注解
- 1.获取加载器
- 2.获取拓展实例对象
- 3.创建拓展类的实例对象
Dubbo SPI
Dubbo 的 SPI(Service Provider Interface)机制是一种强大的扩展机制,它允许开发者在运行时动态地替换或增加框架的功能。Dubbo 的 SPI 机制与 Java 原生的 SPI 机制有所不同,它提供了更多的灵活性和功能。
SPI机制的核心组件包括:
-
服务接口:接口定义了服务提供者需要实现的方法,应用程序将使用这个接口与具体的服务实现进行交互。
-
服务实现:这是实现了服务接口的具体类,第三方可以为服务接口提供多个实现。
-
服务提供者配置文件:这是一个位于META-INF/dubbo目录下的文件,文件名与服务接口的全限定名相同,该文件包含了服务实现类的全限定名,每行一个接口的具体实现类,在运行时就可以加载这些实现类。
-
ServiceLoader:用于加载服务实现,应用程序可以使用ServiceLoader来获取服务接口的所有具体实现类。
SPI的工作流程如下:
- 定义服务接口。
- 实现服务接口,创建具体的服务实现类。
- 在META-INF/dubbo目录下创建服务提供者配置文件,列出所有服务实现类的全限定名。
- 使用ServiceLoader加载服务具体实现类,并根据需要使用它们。
总结就是说SPI机制使得应用程序可以在运行时动态地选择和加载服务实现,从而提高了应用程序的可扩展性和灵活性。
使用方式
使用方式和 java的SPI类似,首先通过@SPI注解定义服务接口
@SPI
public interface Greeting {public void sayHello();
}
再定义服务实现类实现接口并重写接口中的方法
public class ChineseGreeting implements Greeting {@Overridepublic void sayHello() {System.out.println("你好,世界!");}
}
public class EnglishGreeting implements Greeting {@Overridepublic void sayHello() {System.out.println("Hello World!");}
}
在META-INF/dubbo目录下创建一个名为com.xydp.dubbo.Greeting的文件,用于存储自定义键名(这里与java的 SPI 不同,需要自定义key的名称,键名随意,主要是为了实现后面按需加载)与具体实现类的全限定名。文件内容如下:
文件内容
english=com.xydp.dubbo.EnglishGreeting
chinese=com.xydp.dubbo.ChineseGreeting
编写测试类
public class SpiDemo {public static void main(String[] args) {ExtensionLoader<Greeting> extensionLoader = ExtensionLoader.getExtensionLoader(Greeting.class);Greeting englishGreeting = extensionLoader.getExtension("english");englishGreeting.sayHello();System.out.println(englishGreeting.getClass());}
}
输出结果
Dubbo的 SPI 通过自定义键名的方式按需加载,可以在O(1)的时间复杂度获取具体的服务实现类,而java SPI 需要通过迭代器的方式全局遍历获取某个具体实现类,达到线性时间复杂度O(n)。
Dubbo SPI与Java SPI区别
-
设计理念:Java SPI 主要关注于服务实现的加载,而 Dubbo SPI 更注重于框架的可扩展性和灵活性。
-
功能丰富度:Dubbo SPI 提供了更多的功能,如自适应扩展、激活扩展和依赖注入等,而 Java SPI 功能相对有限。
-
加载方式:Java SPI 采用全局加载的方式,加载特定的实现类时间复杂度达到O(n),性能差,而 Dubbo SPI 采用按需加载的方式,时间复杂度只需O(1),提高了性能。
-
配置方式:Dubbo SPI 支持通过注解和 URL 参数进行动态配置,使得框架更加灵活;Java SPI 主要通过配置文件进行静态配置。
总之,Java SPI 和 Dubbo SPI 都是用于实现服务发现和实现加载的机制,但 Dubbo SPI 在设计理念、功能和用法上更加灵活和强大,Dubbo SPI 更适合用于构建复杂的分布式系统,而 Java SPI 更适合用于简单的服务加载场景。
AOP功能
在 Dubbo 中,实现 AOP 功能的方式是通过自定义 Wrapper 类实现的,Dubbo 要求实现 AOP 功能的类以 Wrapper 结尾,这是一种约定,以便于识别这些类是用于包装服务实现的,Wrapper 类是 Dubbo 框架的一部分,用于在运行时动态地为服务实现类添加额外的功能,原理和Spring AOP类似,将通知织入所要执行的目标方法前后。
public class GreetingWrapper1 implements Greeting{private Greeting greeting;public GreetingWrapper1(Greeting greeting){this.greeting = greeting;}@Overridepublic void sayHello() {System.out.println("before do someting");this.greeting.sayHello();System.out.println("after do something");}
}
同时需要在配置文件中添加包装类的全限定名
测试类保持不变
public class SpiDemo {public static void main(String[] args) {ExtensionLoader<Greeting> extensionLoader = ExtensionLoader.getExtensionLoader(Greeting.class);Greeting englishGreeting = extensionLoader.getExtension("english");englishGreeting.sayHello();System.out.println(englishGreeting.getClass());}
}
输出结果
从结果可以看出最终执行的是GreetingWrapper1中greeting的sayHello()方法,GreetingWrapper1在这里充当EnglishGreeting的代理对象。
源码剖析
@SPI注解
在 Dubbo 的 SPI 机制中,@SPI 注解用于标记一个接口为可扩展的扩展点,@SPI 注解有两个可选参数:value 和 scope。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {String value() default "";ExtensionScope scope() default ExtensionScope.APPLICATION;
}
value 参数用于指定扩展点的默认实现类,当没有其他扩展实现类被明确指定时,Dubbo 会使用 value 参数指定的key,表示从配置文件中查找对应的实现类。
scope 参数指定扩展实现类的作用域,有以下四种作用域
-
Constants.FRAMEWORK(框架作用域):在Dubbo框架内,实现类只会生成唯一实例,并在整个应用程序内共享。
-
Constants.APPLICATION(应用程序作用域):在应用程序上下文中,实现类仅会被实例化一次,并在整个应用程序中共享,是默认的作用域。
-
Constants.MODULE(模块作用域):在模块上下文中,该实现类将仅创建一个实例,并在该模块内共享。
-
Constants.SELF(自定义作用域):用户可定义实现类的作用范围,涵盖任意范围。
1.获取加载器
当执行获取拓展加载器这行代码时
ExtensionLoader<Greeting> extensionLoader = ExtensionLoader.getExtensionLoader(Greeting.class);
源码如下
public <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {//检查拓展目录是否被删除this.checkDestroyed();//校验类型是否为nullif (type == null) {throw new IllegalArgumentException("Extension type == null");} else if (!type.isInterface()) {throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");} else if (!withExtensionAnnotation(type)) {throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");} else {//如果类型不为null,尝试从缓存中获取加载器loader以及作用域ExtensionLoader<T> loader = (ExtensionLoader)this.extensionLoadersMap.get(type);ExtensionScope scope = (ExtensionScope)this.extensionScopeMap.get(type);//如果缓存中不存在scope,就从SPI注解获取scope,再放入缓存if (scope == null) {SPI annotation = (SPI)type.getAnnotation(SPI.class);scope = annotation.scope();this.extensionScopeMap.put(type, scope);}//如果加载器为null且作用域是SELF,就直接创建loaderif (loader == null && scope == ExtensionScope.SELF) {loader = this.createExtensionLoader0(type);}//若lader为空,作用域不是SELF且父类加载器不为空,那么尝试从父类加载器去获取loaderif (loader == null && this.parent != null) {loader = this.parent.getExtensionLoader(type);}//若无法从父类加载器获取loader,那么自己实例化一个loader并放入缓存if (loader == null) {loader = this.createExtensionLoader(type);}//返回加载器return loader;}}
private <T> ExtensionLoader<T> createExtensionLoader(Class<T> type) {ExtensionLoader<T> loader = null;//根据SPI注解的属性判断作用域是否等于默认作用域if (this.isScopeMatched(type)) {loader = this.createExtensionLoader0(type);}return loader;}
private <T> ExtensionLoader<T> createExtensionLoader0(Class<T> type) {//检查拓展目录是否被删除this.checkDestroyed();//根据类型创建加载器并放入缓存this.extensionLoadersMap.putIfAbsent(type, new ExtensionLoader(type, this, this.scopeModel));//从缓存获取loaderExtensionLoader<T> loader = (ExtensionLoader)this.extensionLoadersMap.get(type);//返回loaderreturn loader;}
2.获取拓展实例对象
执行这行代码获取拓展类的实例对象
Greeting englishGreeting = extensionLoader.getExtension("english");
源码如下
public T getExtension(String name) {//获取拓展实例对象T extension = this.getExtension(name, true);if (extension == null) {throw new IllegalArgumentException("Not find extension: " + name);} else {//获取到了返回return extension;}}
public T getExtension(String name, boolean wrap) {//检查拓展目录是否被删除this.checkDestroyed();//若参数为空,抛异常if (StringUtils.isEmpty(name)) {throw new IllegalArgumentException("Extension name == null");}//若拓展类名称为true,表示使用SPI注解中声明的默认拓展实现类 else if ("true".equals(name)) {return this.getDefaultExtension();} else {String cacheKey = name;if (!wrap) {cacheKey = name + "_origin";}//尝试从缓存中获取拓展类实例对象,如果获取不到cacheKey对应的holder//则会创建一个空的holder再返回Holder<Object> holder = this.getOrCreateHolder(cacheKey);Object instance = holder.get();//若获取不到,则采用双重检验的单例模式创建实例对象,代码第一次执行是获取不到对象的,//此时instance为null//第一层判断是为了提高执行速度,防止实例对象不为空时还去竞争锁if (instance == null) {synchronized(holder) {instance = holder.get();//第二层判断是为了避免创建重复的实例对象if (instance == null) {//获取拓展类实例对象instance = this.createExtension(name, wrap);//将实例对象放入缓存holder.set(instance);}}}//返回实例对象return instance;}}
private Holder<Object> getOrCreateHolder(String name) {//根据推展类名称获取缓存对象holderHolder<Object> holder = (Holder)this.cachedInstances.get(name);//获取不到,则创建一个空的holder并放入缓存if (holder == null) {this.cachedInstances.putIfAbsent(name, new Holder());holder = (Holder)this.cachedInstances.get(name);}//返回holderreturn holder;}
Holder类是一个缓存对象,用于缓存自定义键名对应的拓展类实例对象
public class Holder<T> {
//volatil的作用是禁止指令重排序private volatile T value;public Holder() {}public void set(T value) {this.value = value;}public T get() {return this.value;}
}
可以看到缓存Holder类用volatile修饰变量,这样做是为了禁止指令重排序,避免返回一个未初始化完成的实例对象,创建一个对象分为3步
- 为对象分配内存空间
- 初始化对象
- 将对象指向1所分配的内存空间
若holder没有用volatile修饰,2和3的指令发生顺序颠倒,此时指令的执行顺序为1->3->2,当执行完3时,代码执行到第二层 if 判断,发现instance不为null,此时直接返回instance,返回的是还未初始化的对象(对象的属性未赋值)。
3.创建拓展类的实例对象
第一次执行时缓存是获取不到实例对象的,所以需要创建,之后就能从缓存中直接获取。
private T createExtension(String name, boolean wrap) {//解析配置文件,先获取所有的拓展类,再根据类名获取对应的classClass<?> clazz = (Class)this.getExtensionClasses().get(name);if (clazz != null && !this.unacceptableExceptions.contains(name)) {try {//根据类对象从缓存获取类实例对象T instance = this.extensionInstances.get(clazz);//获取不到,则通过反射的方式创建实例对象并放入缓存if (instance == null) {this.extensionInstances.putIfAbsent(clazz, this.createExtensionInstance(clazz));instance = this.extensionInstances.get(clazz);//前置处理instance = this.postProcessBeforeInitialization(instance, name);//依赖注入this.injectExtension(instance);//后置处理instance = this.postProcessAfterInitialization(instance, name);}//包装类的处理,用于实现AOP功能if (wrap) {List<Class<?>> wrapperClassesList = new ArrayList();if (this.cachedWrapperClasses != null) {wrapperClassesList.addAll(this.cachedWrapperClasses);wrapperClassesList.sort(WrapperComparator.COMPARATOR);Collections.reverse(wrapperClassesList);}if (CollectionUtils.isNotEmpty(wrapperClassesList)) {Iterator var6 = wrapperClassesList.iterator();while(var6.hasNext()) {Class<?> wrapperClass = (Class)var6.next();Wrapper wrapper = (Wrapper)wrapperClass.getAnnotation(Wrapper.class);boolean match = wrapper == null || (ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) && !ArrayUtils.contains(wrapper.mismatches(), name);if (match) {instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance));instance = this.postProcessAfterInitialization(instance, name);}}}}//生命周期管理this.initExtension(instance);return instance;} catch (Throwable var10) {throw new IllegalStateException("Extension instance (name: " + name + ", class: " + this.type + ") couldn't be instantiated: " + var10.getMessage(), var10);}} else {throw this.findException(name);}}
(1)解析配置文件,获取拓展类
private Map<String, Class<?>> getExtensionClasses() {//从缓存中获取拓展类Map<String, Class<?>> classes = (Map)this.cachedClasses.get();//缓存获取不到,通过双重检查锁的单例模式创建拓展类if (classes == null) {synchronized(this.cachedClasses) {classes = (Map)this.cachedClasses.get();if (classes == null) {try {//解析配置文件,获取类信息并放入缓存classes = this.loadExtensionClasses();} catch (InterruptedException var5) {logger.error("0-15", "", "", "Exception occurred when loading extension class (interface: " + this.type + ")", var5);throw new IllegalStateException("Exception occurred when loading extension class (interface: " + this.type + ")", var5);}this.cachedClasses.set(classes);}}}return classes;}private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {//检查拓展目录是否被销毁this.checkDestroyed();//判断SPI注解中的默认参数是否合法this.cacheDefaultExtensionName();Map<String, Class<?>> extensionClasses = new HashMap();LoadingStrategy[] var2 = strategies;int var3 = var2.length;//解析三个配置文件的信息,并将类信息放入缓存//三个配置文件 META-INF/dubbo/,META-INF/dubbo/internal/,META-INF/services/for(int var4 = 0; var4 < var3; ++var4) {LoadingStrategy strategy = var2[var4];this.loadDirectory(extensionClasses, strategy, this.type.getName());if (this.type == ExtensionInjector.class) {this.loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());}}return extensionClasses;}
(2)实例化对象
T instance = this.extensionInstances.get(clazz);if (instance == null) {this.extensionInstances.putIfAbsent(clazz, this.createExtensionInstance(clazz));instance = this.extensionInstances.get(clazz);}//通过反射的方式创建实例对象private Object createExtensionInstance(Class<?> type) throws ReflectiveOperationException {return this.instantiationStrategy.instantiate(type);}
(3)前置处理
前置处理和Spring的前置处理类似,可以在实例对象初始化之前执行一些自定义的初始化逻辑,例如检查实例对象是否满足某些条件,或者为实例对象添加一些额外的功能。
instance = this.postProcessBeforeInitialization(instance, name);private T postProcessBeforeInitialization(T instance, String name) throws Exception {ExtensionPostProcessor processor;if (this.extensionPostProcessors != null) {for(Iterator var3 = this.extensionPostProcessors.iterator(); var3.hasNext(); instance = processor.postProcessBeforeInitialization(instance, name)) {processor = (ExtensionPostProcessor)var3.next();}}return instance;}
(4)依赖注入
Dubbo的依赖注入只支持Setter方法级别的注入。
this.injectExtension(instance);private T injectExtension(T instance) {if (this.injector == null) {return instance;} else {try {Method[] var2 = instance.getClass().getMethods();int var3 = var2.length;for(int var4 = 0; var4 < var3; ++var4) {Method method = var2[var4];//方法是setter方法,方法不包含DisableInject注解且instance不是基本数据类型if (this.isSetter(method) && !method.isAnnotationPresent(DisableInject.class) && method.getDeclaringClass() != ScopeModelAware.class && (!(instance instanceof ScopeModelAware) && !(instance instanceof ExtensionAccessorAware) || !ignoredInjectMethodsDesc.contains(ReflectUtils.getDesc(method)))) {//获取方法的参数类型Class<?> pt = method.getParameterTypes()[0];if (!ReflectUtils.isPrimitives(pt)) {try {//获取参数值String property = this.getSetterProperty(method);//根据参数类型和参数值获取依赖对象Object object = this.injector.getInstance(pt, property);if (object != null) {//将依赖对象object注入instancemethod.invoke(instance, object);}} catch (Exception var9) {logger.error("0-15", "", "", "Failed to inject via method " + method.getName() + " of interface " + this.type.getName() + ": " + var9.getMessage(), var9);}}}}} catch (Exception var10) {logger.error("0-15", "", "", var10.getMessage(), var10);}return instance;}}
(5)后置处理
后置处理的思想与Spring的后置处理类似。
instance = this.postProcessAfterInitialization(instance, name);private T postProcessAfterInitialization(T instance, String name) throws Exception {if (instance instanceof ExtensionAccessorAware) {((ExtensionAccessorAware)instance).setExtensionAccessor(this.extensionDirector);}ExtensionPostProcessor processor;if (this.extensionPostProcessors != null) {for(Iterator var3 = this.extensionPostProcessors.iterator(); var3.hasNext(); instance = processor.postProcessAfterInitialization(instance, name)) {processor = (ExtensionPostProcessor)var3.next();}}return instance;}
(6)包装类Wrapper处理
Dubbo的AOP功能就是通过Wrapper实现的。
if (wrap) {List<Class<?>> wrapperClassesList = new ArrayList();//判断wrapper缓存包装类集合是否为空// 不为空则加入list集合中,然后排序之后再翻转if (this.cachedWrapperClasses != null) {wrapperClassesList.addAll(this.cachedWrapperClasses);wrapperClassesList.sort(WrapperComparator.COMPARATOR);Collections.reverse(wrapperClassesList);}//判断wrapper包装类集合不为空if (CollectionUtils.isNotEmpty(wrapperClassesList)) {Iterator var6 = wrapperClassesList.iterator();//遍历集合中的每一个包装类while(var6.hasNext()) {Class<?> wrapperClass = (Class)var6.next();//获取包装类的注解Wrapper wrapper = (Wrapper)wrapperClass.getAnnotation(Wrapper.class);//判断是否符合包装条件boolean match = wrapper == null || (ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) && !ArrayUtils.contains(wrapper.mismatches(), name);//符合包装条件,将当前实例对象添加到包装类中,并做一些后置处理if (match) {//将instance作为参数传给wrapper的构造方法,通过反射的方式创建wrapper实例,//再往wrapper实例注入依赖,然后将wrapper赋值给instance,再对instance做后置处理instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance));instance = this.postProcessAfterInitialization(instance, name);}}}}
(7)生命周期管理
this.initExtension(instance);private void initExtension(T instance) {if (instance instanceof Lifecycle) {Lifecycle lifecycle = (Lifecycle)instance;lifecycle.initialize();}}
相关文章:

Dubbo SPI源码
文章目录 Dubbo SPI使用方式AOP功能源码剖析SPI注解1.获取加载器2.获取拓展实例对象3.创建拓展类的实例对象 Dubbo SPI Dubbo 的 SPI(Service Provider Interface)机制是一种强大的扩展机制,它允许开发者在运行时动态地替换或增加框架的功能。…...

《C++代码高度优化之双刃剑:避免过度优化引发的“暗雷”》
在 C编程的世界里,追求高效性能的代码是每个开发者的目标之一。高度优化的 C代码可以带来显著的性能提升,让程序在运行速度、内存占用等方面表现出色。然而,正如一把双刃剑,过度优化可能会引入难以察觉的错误,给程序带…...

javascript网页设计案例
设计一个具有良好用户体验的 JavaScript 网页涉及多个方面,如用户界面(UI)、用户体验(UX)、交互设计等。以下是一些示例案例,展示了如何使用 JavaScript 创建功能丰富且吸引人的网页设计。 1. 响应式导航菜…...

初阶数据结构【TOP】- 11.普通二叉树的介绍 - 1. (细致,保姆~~!)
文章目录 前言一、普通二叉树的链式结构二、 造树三、普通二叉树的遍历四、遍历完整代码五、总结 前言 本篇文章笔者将会对普通二叉树部分进行细致的讲解 , 本篇主要包括以下内容: 二叉树链式结构的介绍 ,二叉树的遍历. 笔者会一步一步分析带学者领略递归的美好~~ 一、普通二叉…...

【pyenv】pyenv安装版本超时的解决方案
目录 1、现象 2、分析现象 3、手动下载所需版本 4、存放到指定路径 5、重新安装 6、pip失败(做个记录,未找到原因) 7、方法二修改环境变量方法 7.1 设置环境变量 7.2 更新 7.3 安装即可 8、方法三修改XML文件 前言:研…...

【新片场-注册安全分析报告-无验证方式导致安全隐患】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 1. 暴力破解密码,造成用户信息泄露 2. 短信盗刷的安全问题,影响业务及导致用户投诉 3. 带来经济损失,尤其是后付费客户,风险巨大,造…...

新160个crackme - 057-bbbs-crackme04
运行分析 因软件版本老旧,需使用windows XP虚拟机运行有个SystemID,值为12345678需破解User ID和Password PE分析 yC壳,32位 OD手动脱壳 使用windows XP虚拟机,将程序拖入OD按一下F8,ESP变红,根据ESP定律设…...

车机中 Android Audio 音频常见问题分析方法实践小结
文章目录 前言1. 无声2. 断音3. 杂音4. 延迟播放5. 焦点问题6. 无声问题(连上 BT )其他完善中…… 前言 本文主要总结了一下车机开发中遇到的 Audio 有关的问题,同时参考网上的一案例,由于Audio 模块出现音频问题的场景很多,对每一个出现的问…...

湘大 OJ 代码仓库
有时候不需要上传一些题解,想要上传一些纯代码就行,傻傻把代码上传到文章里面,感觉效率不是很高,还是建立一个代码仓库比较方便 需要会使用魔法可能才能访问,github代码仓库地址...

Ruoyi Cloud K8s 部署
本文视频版本:https://www.bilibili.com/video/BV1xF4Se3Esv 参考 https://blog.csdn.net/Equent/article/details/137779505 https://blog.csdn.net/weixin_48711696/article/details/138117392 https://zhuanlan.zhihu.com/p/470647732 https://gitee.com/y_project/Ruo…...

OpenGL Texture C++ Camera Filter滤镜
基于OpenGL Texture纹理的强大功能,在片段着色器(Shader)中编写GLSL代码,对YUV的数据进行数据转换从而实现视频编辑软件中的相机滤镜功能。 接上一篇OpenGL Texture C 预览Camera视频的功能实现,本篇来实现Camera滤镜效…...

基于Sobel算法的边缘检测设计与实现
1、边缘检测 针对的时灰度图像,顾名思义,检测图像的边缘,是针对图像像素点的一种计算,目的时标识数字图像中灰度变化明显的点,图像的边缘检测,在保留了图像的重要结构信息的同时,剔除了可以认为…...

java:练习
编写一个 Java 程序,计算并输出从 1 到用户指定的数字 n 中,所有“幸运数字”。幸运数字的定义如下:条件 1:数字的所有位数(如个位、十位)加起来的和是 7 的倍数。条件 2:数字本身是一个质数&am…...

大数据中一些常用的集群启停命令
文章目录 一、HDFS二、MapReduce && YARN三、Hive 一、HDFS 格式化namenode # 确保以hadoop用户执行 su - hadoop # 格式化namenode hadoop namenode -format启动 # 一键启动hdfs集群 start-dfs.sh # 一键关闭hdfs集群 stop-dfs.sh# 如果遇到命令未找到的错误&#…...

Golang、Python、C语言、Java的圆桌会议
一天,Golang、C语言、Java 和 Python 四位老朋友坐在编程领域的“圆桌会议”上,讨论如何一起完成一个任务:实现一个简单的高并发服务器,用于处理成千上万的请求。大家各抒己见,而 Golang 则是这次会议的主角。 1. Pyth…...

C语言编译原理
目录 一、C语言的编译过程 二、预处理 三、编译阶段 3.1 词法分析(Lexical Analysis) 3.2 语法分析(Syntax Analysis) 语法分析的主要步骤: 语法分析的关键技术: 构建AST: 符号表的维护…...

【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
🌟🌟作者主页:ephemerals__ 🌟🌟所属专栏:C 目录 前言 一、取地址运算符重载 1. const修饰成员函数 2. 取地址运算符重载 二、深究构造函数 三、类型转换 四、static修饰成员 1. static修饰成员变…...

Apache POI 学习
Apache POI 学习 1. 引言2. 环境搭建MavenGradle 3. 基础概念4. 基本操作4.1 创建 Excel 文件4.2 读取 Excel 文件 5. 进阶操作5.1 设置单元格样式5.2 数据验证5.3 图表创建5.4 合并单元格5.5 居中对齐5.6 设置边框和字体颜色 6. 性能优化7. 总结 1. 引言 Apache POI 是一个用…...

福建科立讯通信 指挥调度管理平台 SQL注入漏洞
北峰通信-福建科立讯通信 指挥调度管理平台 SQL注入漏洞 厂商域名和信息收集 域名: 工具sqlmap python sqlmap.py -u "http://ip:端口/api/client/down_file.php?uuid1" --batch 数据包 GET /api/client/down_file.php?uuid1%27%20AND%20(SELECT%20…...

4.qml单例模式
这里写目录标题 js文件单例模式qml文件单例模式 js文件单例模式 直接添加一个js文件到qml中 修改内容 TestA.qml import QtQuick 2.0 import QtQuick.Controls 2.12 import "./MyWork.js" as MWItem {Row{TextField {onEditingFinished: {MW.setA(text)}}Button…...

CACTI 0.8.7 迁移并升级到 1.2.7记录
升级前后环境 升级前: CactiEZ 中文版 V10 升级后: Ubuntu 2204 Cacti 1.2.7 升级原因:风险漏洞太多,升不尽,补不完. 升级流程 Created with Raphal 2.3.0 开始 DST:安装Ububtu/Mariadb/apache/php SRC:备份 DB/RRA 数据导入 结束 Cacti 依赖包 注意:UBUNTU下有些包,它非另外…...

OrionX vGPU 研发测试场景下最佳实践之Jupyter模式
在上周的文章中,我们讲述了OrionX vGPU研发测试场景下最佳实践之SSH模式,今天,让我们走进 Jupyter模式下的最佳实践。 • Jupyter模式:Jupyter是最近几年算法人员使用比较多的一种工具,很多企业已经将其改造集成开发工…...

国风编曲:了解国风 民族调式 五声音阶 作/编曲思路 变化音 六声、七声调式
中国风 以流行为基础加入中国特色乐器、调式、和声融为一体的风格 如:青花瓷、菊花台、绝代风华、江南等等等等 省流:中国风=流行民族乐 两者结合,民族元素越多越中国风 流行民族/摇滚民族/电子民族 注意:中国风≠…...

HTTP 响应状态码详解
HTTP状态码详解:HTTP状态码,是用以表示WEB服务器 HTTP响应状态的3位数字代码 小技巧: CtrlF 快速查找 Http状态码状态码含义100客户端应当继续发送请求。这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当…...

在服务器上开Juypter Lab教程(远程访问)
在服务器上开Juypter Lab教程(远程访问) 文章目录 在服务器上开Juypter Lab教程(远程访问)一、安装anaconda1、安装anaconda2、提权限3、运行4、同意协议5、安装6、是否要自动初始化 conda7、结束8、检查 二、Anaconda安装Pytorch…...

【硬件模块】SHT20温湿度传感器
SHT20是一个用IIC通信的温湿度传感器。我们知道这个就可以了。 它支持的电压范围是2.1~3.6V,推荐是3V,所以如果我们的MCU是5V的,那么就得转个电压才能用了。 IIC常见的速率有100k,400k,而SHT20是支持400k的(…...

Redhat 8,9系(复刻系列) 一键部署Oracle23ai rpm
Oracle23ai前言 Oracle Database 23ai Free 让您可以充分体验 Oracle Database 的能力,世界各地的企业都依赖它来处理关键任务工作负载。 Oracle Database Free 的资源限制为 2 个 CPU(前台进程)、2 GB 的 RAM 和 12 GB 的磁盘用户数据。该软件包不仅易于使用,还可轻松下载…...

SIPp uac.xml 之我见
https://sipp.sourceforge.net/doc/uac.xml.html 这个 uac.xml 有没有问题呢? 有! 问题之一是: <recv response"200" rtd"true" rrs"true"> 要加 rrs, 仔细看注释就能看到 问题之二是࿱…...

引领智能家居新风尚,WTN6040F门铃解决方案——让家的呼唤更动听
在追求高效与便捷的智能家居时代,每一个细节都承载着我们对美好生活的向往。WTN6040F,作为一款专为现代家庭设计的低成本、高性能门铃解决方案,正以其独特的魅力,悄然改变着我们的居家生活体验。 芯片功能特点: 1.2.4…...

Android 蓝牙服务启动
蓝牙是Android设备中非常常见的一个feature,设备厂家可以用BT来做RC、连接音箱、设备本身做Sink等常见功能。如果一些设备不需要BT功能,Android也可以通过配置来disable此模块,方便厂家为自己的设备做客制化。APP操作设备的蓝牙功能ÿ…...