当前位置: 首页 > news >正文

SpringBoot 插件化开发模式

一、前言

1.1 使用插件的好处

1.1.1 模块解耦

实现服务模块之间解耦的方式有很多,但是插件来说,其解耦的程度似乎更高,而且更灵活,可定制化、个性化更好。

举例来说,代码中可以使用设计模式来选择使用哪种方式发送短信给下单完成的客户,问题是各个短信服务商并不一定能保证在任何情况下都能发送成功,怎么办呢?这时候设计模式也没法帮你解决这个问题,如果使用定制化插件的方式,结合外部配置参数,假设系统中某种短信发送不出去了,这时候就可以利用插件动态植入,切换为不同的厂商发短信了。

1.1.2 提升扩展性和开放性

以spring来说,之所以具备如此广泛的生态,与其自身内置的各种可扩展的插件机制是分不开的,试想为什么使用了spring框架之后可以很方便的对接其他中间件,那就是spring框架提供了很多基于插件化的扩展点。

插件化机制让系统的扩展性得以提升,从而可以丰富系统的周边应用生态。

1.1.3 方便第三方接入

有了插件之后,第三方应用或系统如果要对接自身的系统,直接基于系统预留的插件接口完成一套适合自己业务的实现即可,而且对自身系统的侵入性很小,甚至可以实现基于配置参数的热加载,方便灵活,开箱即用。

1.2 插件化常用实现思路

以java为例,这里结合实际经验,整理一些常用的插件化实现思路:

  • spi机制;

  • 约定配置和目录,利用反射配合实现;

  • springboot中的Factories机制;

  • java agent(探针)技术;

  • spring内置扩展点;

  • 第三方插件包,例如:spring-plugin-core;

  • spring aop技术;

二、Java常用插件实现方案

2.1 serviceloader方式

serviceloader是java提供的spi模式的实现。按照接口开发实现类,而后配置,java通过ServiceLoader来实现统一接口不同实现的依次调用。而java中最经典的serviceloader的使用就是Java的spi机制。

2.1.1 java spi

SPI全称 Service Provider Interface ,是JDK内置的一种服务发现机制,SPI是一种动态替换扩展机制,比如有个接口,你想在运行时动态给他添加实现,你只需按照规范给他添加一个实现类即可。比如大家熟悉的jdbc中的Driver接口,不同的厂商可以提供不同的实现,有mysql的,也有oracle的,而Java的SPI机制就可以为某个接口寻找服务的实现。

下面用一张简图说明下SPI机制的原理

图片

2.1.2 java spi 简单案例

如下工程目录,在某个应用工程中定义一个插件接口,而其他应用工程为了实现这个接口,只需要引入当前工程的jar包依赖进行实现即可,这里为了演示我就将不同的实现直接放在同一个工程下;

图片

定义接口

public interface MessagePlugin {public String sendMsg(Map msgMap);}

定义两个不同的实现

public class AliyunMsg implements MessagePlugin {@Overridepublic String sendMsg(Map msgMap) {System.out.println("aliyun sendMsg");return "aliyun sendMsg";}
}
public class TencentMsg implements MessagePlugin {@Overridepublic String sendMsg(Map msgMap) {System.out.println("tencent sendMsg");return "tencent sendMsg";}
}

在resources目录按照规范要求创建文件目录,并填写实现类的全类名

图片

自定义服务加载类

 public static void main(String[] args) {ServiceLoader<MessagePlugin> serviceLoader = ServiceLoader.load(MessagePlugin.class);Iterator<MessagePlugin> iterator = serviceLoader.iterator();Map map = new HashMap();while (iterator.hasNext()){MessagePlugin messagePlugin = iterator.next();messagePlugin.sendMsg(map);}}

运行上面的程序后,可以看到下面的效果,这就是说,使用ServiceLoader的方式可以加载到不同接口的实现,业务中只需要根据自身的需求,结合配置参数的方式就可以灵活的控制具体使用哪一个实现。

图片

2.2 自定义配置约定方式

serviceloader其实是有缺陷的,在使用中必须在META-INF里定义接口名称的文件,在文件中才能写上实现类的类名,如果一个项目里插件化的东西比较多,那很可能会出现越来越多配置文件的情况。所以在结合实际项目使用时,可以考虑下面这种实现思路:

  • A应用定义接口;

  • B,C,D等其他应用定义服务实现;

  • B,C,D应用实现后达成SDK的jar;

  • A应用引用SDK或者将SDK放到某个可以读取到的目录下;

  • A应用读取并解析SDK中的实现类;

在上文中案例基础上,我们做如下调整;

2.2.1 添加配置文件

在配置文件中,将具体的实现类配置进去

server :port : 8081
impl:name : com.congge.plugins.spi.MessagePluginclazz :- com.congge.plugins.impl.TencentMsg- com.congge.plugins.impl.AliyunMsg
2.2.2 自定义配置文件加载类

通过这个类,将上述配置文件中的实现类封装到类对象中,方便后续使用;

@ConfigurationProperties("impl")
@ToString
public class ClassImpl {@Getter@SetterString name;@Getter@SetterString[] clazz;
}
2.2.3 自定义测试接口

使用上述的封装对象通过类加载的方式动态的在程序中引入

@RestController
public class SendMsgController {@AutowiredClassImpl classImpl;//localhost:8081/sendMsg@GetMapping("/sendMsg")public String sendMsg() throws Exception{for (int i=0;i<classImpl.getClazz().length;i++) {Class pluginClass= Class.forName(classImpl.getClazz()[i]);MessagePlugin messagePlugin = (MessagePlugin) pluginClass.newInstance();messagePlugin.sendMsg(new HashMap());}return "success";}}
2.2.4 启动类
@EnableConfigurationProperties({ClassImpl.class})
@SpringBootApplication
public class PluginApp {public static void main(String[] args) {SpringApplication.run(PluginApp.class,args);}}

启动工程代码后,调用接口:localhost:8081/sendMsg,在控制台中可以看到下面的输出信息,即通过这种方式也可以实现类似serviceloader的方式,不过在实际使用时,可以结合配置参数进行灵活的控制;

图片

2.3 自定义配置读取依赖jar的方式

更进一步,在很多场景下,可能我们并不想直接在工程中引入接口实现的依赖包,这时候可以考虑通过读取指定目录下的依赖jar的方式,利用反射的方式进行动态加载,这也是生产中一种比较常用的实践经验。

具体实践来说,主要为下面的步骤:

  • 应用A定义服务接口;

  • 应用B,C,D等实现接口(或者在应用内部实现相同的接口);

  • 应用B,C,D打成jar,放到应用A约定的读取目录下;

  • 应用A加载约定目录下的jar,通过反射加载目标方法;

在上述的基础上,按照上面的实现思路来实现一下;

2.3.1 创建约定目录

在当前工程下创建一个lib目录,并将依赖的jar放进去

图片

2.3.2 新增读取jar的工具类

添加一个工具类,用于读取指定目录下的jar,并通过反射的方式,结合配置文件中的约定配置进行反射方法的执行;

@Component
public class ServiceLoaderUtils {@AutowiredClassImpl classImpl;public static void loadJarsFromAppFolder() throws Exception {String path = "E:\\code-self\\bitzpp\\lib";File f = new File(path);if (f.isDirectory()) {for (File subf : f.listFiles()) {if (subf.isFile()) {loadJarFile(subf);}}} else {loadJarFile(f);}}public static void loadJarFile(File path) throws Exception {URL url = path.toURI().toURL();// 可以获取到AppClassLoader,可以提到前面,不用每次都获取一次URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();// 加载//Method method = URLClassLoader.class.getDeclaredMethod("sendMsg", Map.class);Method method = URLClassLoader.class.getMethod("sendMsg", Map.class);method.setAccessible(true);method.invoke(classLoader, url);}public  void main(String[] args) throws Exception{System.out.println(invokeMethod("hello"));;}public String doExecuteMethod() throws Exception{String path = "E:\\code-self\\bitzpp\\lib";File f1 = new File(path);Object result = null;if (f1.isDirectory()) {for (File subf : f1.listFiles()) {//获取文件名称String name = subf.getName();String fullPath = path + "\\" + name;//执行反射相关的方法//ServiceLoaderUtils serviceLoaderUtils = new ServiceLoaderUtils();//result = serviceLoaderUtils.loadMethod(fullPath);File f = new File(fullPath);URL urlB = f.toURI().toURL();URLClassLoader classLoaderA = new URLClassLoader(new URL[]{urlB}, Thread.currentThread().getContextClassLoader());String[] clazz = classImpl.getClazz();for(String claName : clazz){if(name.equals("biz-pt-1.0-SNAPSHOT.jar")){if(!claName.equals("com.congge.spi.BitptImpl")){continue;}Class<?> loadClass = classLoaderA.loadClass(claName);if(Objects.isNull(loadClass)){continue;}//获取实例Object obj = loadClass.newInstance();Map map = new HashMap();//获取方法Method method=loadClass.getDeclaredMethod("sendMsg",Map.class);result = method.invoke(obj,map);if(Objects.nonNull(result)){break;}}else if(name.equals("miz-pt-1.0-SNAPSHOT.jar")){if(!claName.equals("com.congge.spi.MizptImpl")){continue;}Class<?> loadClass = classLoaderA.loadClass(claName);if(Objects.isNull(loadClass)){continue;}//获取实例Object obj = loadClass.newInstance();Map map = new HashMap();//获取方法Method method=loadClass.getDeclaredMethod("sendMsg",Map.class);result = method.invoke(obj,map);if(Objects.nonNull(result)){break;}}}if(Objects.nonNull(result)){break;}}}return result.toString();}public Object loadMethod(String fullPath) throws Exception{File f = new File(fullPath);URL urlB = f.toURI().toURL();URLClassLoader classLoaderA = new URLClassLoader(new URL[]{urlB}, Thread.currentThread().getContextClassLoader());Object result = null;String[] clazz = classImpl.getClazz();for(String claName : clazz){Class<?> loadClass = classLoaderA.loadClass(claName);if(Objects.isNull(loadClass)){continue;}//获取实例Object obj = loadClass.newInstance();Map map = new HashMap();//获取方法Method method=loadClass.getDeclaredMethod("sendMsg",Map.class);result = method.invoke(obj,map);if(Objects.nonNull(result)){break;}}return result;}public static String invokeMethod(String text) throws Exception{String path = "E:\\code-self\\bitzpp\\lib\\miz-pt-1.0-SNAPSHOT.jar";File f = new File(path);URL urlB = f.toURI().toURL();URLClassLoader classLoaderA = new URLClassLoader(new URL[]{urlB}, Thread.currentThread().getContextClassLoader());Class<?> product = classLoaderA.loadClass("com.congge.spi.MizptImpl");//获取实例Object obj = product.newInstance();Map map = new HashMap();//获取方法Method method=product.getDeclaredMethod("sendMsg",Map.class);//执行方法Object result1 = method.invoke(obj,map);// TODO According to the requirements , write the implementation code.return result1.toString();}public static String getApplicationFolder() {String path = ServiceLoaderUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath();return new File(path).getParent();}}
2.3.3 添加测试接口

添加如下测试接口

@GetMapping("/sendMsgV2")
public String index() throws Exception {String result = serviceLoaderUtils.doExecuteMethod();return result;
}

以上全部完成之后,启动工程,测试一下该接口,仍然可以得到预期结果;

图片

在上述的实现中还是比较粗糙的,实际运用时,还需要做较多的优化改进以满足实际的业务需要,比如接口传入类型参数用于控制具体使用哪个依赖包的方法进行执行等;我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。

三、SpringBoot中的插件化实现

在大家使用较多的springboot框架中,其实框架自身提供了非常多的扩展点,其中最适合做插件扩展的莫过于spring.factories的实现;

3.1 Spring Boot中的SPI机制

在Spring中也有一种类似与Java SPI的加载机制。它在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化,这种自定义的SPI机制是Spring Boot Starter实现的基础。

3.2 Spring Factories实现原理

spring-core包里定义了SpringFactoriesLoader类,这个类实现了检索META-INF/spring.factories文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:

  • loadFactories 根据接口类获取其实现类的实例,这个方法返回的是对象列表;

  • loadFactoryNames 根据接口获取其接口类的名称,这个方法返回的是类名的列表;

上面的两个方法的关键都是从指定的ClassLoader中获取spring.factories文件,并解析得到类名列表,具体代码如下:

public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {String factoryClassName = factoryClass.getName();try {Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));List<String> result = new ArrayList<String>();while (urls.hasMoreElements()) {URL url = urls.nextElement();Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));String factoryClassNames = properties.getProperty(factoryClassName);result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));}return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);}
}

从代码中我们可以知道,在这个方法中会遍历整个ClassLoader中所有jar包下的spring.factories文件,就是说我们可以在自己的jar中配置spring.factories文件,不会影响到其它地方的配置,也不会被别人的配置覆盖。

spring.factories的是通过Properties解析得到的,所以我们在写文件中的内容都是安装下面这种方式配置的:

com.xxx.interface=com.xxx.classname

如果一个接口希望配置多个实现类,可以使用’,’进行分割

3.3 Spring Factories案例实现

接下来看一个具体的案例实现来体验下Spring Factories的使用;

3.3.1 定义一个服务接口

自定义一个接口,里面添加一个方法;

public interface SmsPlugin {public void sendMessage(String message);}
3.3.2 定义2个服务实现

实现类1

public class BizSmsImpl implements SmsPlugin {@Overridepublic void sendMessage(String message) {System.out.println("this is BizSmsImpl sendMessage..." + message);}
}

实现类2

public class SystemSmsImpl implements SmsPlugin {@Overridepublic void sendMessage(String message) {System.out.println("this is SystemSmsImpl sendMessage..." + message);}
}
3.3.3 添加spring.factories文件

在resources目录下,创建一个名叫:META-INF的目录,然后在该目录下定义一个spring.factories的配置文件,内容如下,其实就是配置了服务接口,以及两个实现类的全类名的路径;

com.congge.plugin.spi.SmsPlugin=\
com.congge.plugin.impl.SystemSmsImpl,\
com.congge.plugin.impl.BizSmsImpl
3.3.4 添加自定义接口

添加一个自定义的接口,有没有发现,这里和java 的spi有点类似,只不过是这里换成了SpringFactoriesLoader去加载服务;

@GetMapping("/sendMsgV3")
public String sendMsgV3(String msg) throws Exception{List<SmsPlugin> smsServices= SpringFactoriesLoader.loadFactories(SmsPlugin.class, null);for(SmsPlugin smsService : smsServices){smsService.sendMessage(msg);}return "success";
}

启动工程之后,调用一下该接口进行测试,localhost:8087/sendMsgV3?msg=hello,通过控制台,可以看到,这种方式能够正确获取到系统中可用的服务实现;

图片

利用spring的这种机制,可以很好的对系统中的某些业务逻辑通过插件化接口的方式进行扩展实现;

四、插件化机制案例实战

结合上面掌握的理论知识,下面基于Java SPI机制进行一个接近真实使用场景的完整的操作步骤;

4.1 案例背景

  • 3个微服务模块,在A模块中有个插件化的接口;

  • 在A模块中的某个接口,需要调用插件化的服务实现进行短信发送;

  • 可以通过配置文件配置参数指定具体的哪一种方式发送短信;

  • 如果没有加载到任何插件,将走A模块在默认的发短信实现;

4.1.1 模块结构

1、biz-pp,插件化接口工程;

2、bitpt,aliyun短信发送实现;

3、miz-pt,tencent短信发送实现;

4.1.2 整体实现思路

本案例完整的实现思路参考如下:

  • biz-pp定义服务接口,并提供出去jar被其他实现工程依赖;

  • bitpt与miz-pt依赖biz-pp的jar并实现SPI中的方法;

  • bitpt与miz-pt按照API规范实现完成后,打成jar包,或者安装到仓库中;

  • biz-pp在pom中依赖bitpt与miz-pt的jar,或者通过启动加载的方式即可得到具体某个实现;

4.2 biz-pp 关键代码实现过程

4.2.1 添加服务接口
public interface MessagePlugin {public String sendMsg(Map msgMap);}
4.2.2 打成jar包并安装到仓库

这一步比较简单就不展开了

4.2.3 自定义服务加载工具类

这个类,可以理解为在真实的业务编码中,可以根据业务定义的规则,具体加载哪个插件的实现类进行发送短信的操作;

public class PluginFactory {public void installPlugin(){Map context = new LinkedHashMap();context.put("_userId","");context.put("_version","1.0");context.put("_type","sms");ServiceLoader<MessagePlugin> serviceLoader = ServiceLoader.load(MessagePlugin.class);Iterator<MessagePlugin> iterator = serviceLoader.iterator();while (iterator.hasNext()){MessagePlugin messagePlugin = iterator.next();messagePlugin.sendMsg(context);}}public static MessagePlugin getTargetPlugin(String type){ServiceLoader<MessagePlugin> serviceLoader = ServiceLoader.load(MessagePlugin.class);Iterator<MessagePlugin> iterator = serviceLoader.iterator();List<MessagePlugin> messagePlugins = new ArrayList<>();while (iterator.hasNext()){MessagePlugin messagePlugin = iterator.next();messagePlugins.add(messagePlugin);}MessagePlugin targetPlugin = null;for (MessagePlugin messagePlugin : messagePlugins) {boolean findTarget = false;switch (type) {case "aliyun":if (messagePlugin instanceof BitptImpl){targetPlugin = messagePlugin;findTarget = true;break;}case "tencent":if (messagePlugin instanceof MizptImpl){targetPlugin = messagePlugin;findTarget = true;break;}}if(findTarget) break;}return targetPlugin;}public static void main(String[] args) {new PluginFactory().installPlugin();}}
4.2.4 自定义接口
@RestController
public class SmsController {@Autowiredprivate SmsService smsService;@Autowiredprivate ServiceLoaderUtils serviceLoaderUtils;//localhost:8087/sendMsg?msg=sendMsg@GetMapping("/sendMsg")public String sendMessage(String msg){return smsService.sendMsg(msg);}}
4.2.5 接口实现
@Service
public class SmsService {@Value("${msg.type}")private String msgType;@Autowiredprivate DefaultSmsService defaultSmsService;public String sendMsg(String msg) {MessagePlugin messagePlugin = PluginFactory.getTargetPlugin(msgType);Map paramMap = new HashMap();if(Objects.nonNull(messagePlugin)){return messagePlugin.sendMsg(paramMap);}return defaultSmsService.sendMsg(paramMap);}
}
4.2.6 添加服务依赖

在该模块中,需要引入对具体实现的两个工程的jar依赖(也可以通过启动加载的命令方式)

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--依赖具体的实现--><dependency><groupId>com.congge</groupId><artifactId>biz-pt</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>com.congge</groupId><artifactId>miz-pt</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>

biz-pp的核心代码实现就到此结束了,后面再具体测试的时候再继续;

4.3 bizpt 关键代码实现过程

接下来就是插件化机制中具体的SPI实现过程,两个模块的实现步骤完全一致,挑选其中一个说明,工程目录结构如下:

图片

4.3.1 添加对biz-app的jar的依赖

将上面biz-app工程打出来的jar依赖过来

<dependencies><dependency><groupId>com.congge</groupId><artifactId>biz-app</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>
4.3.2 添加MessagePlugin接口的实现
public class BitptImpl implements MessagePlugin {@Overridepublic String sendMsg(Map msgMap) {Object userId = msgMap.get("userId");Object type = msgMap.get("_type");//TODO 参数校验System.out.println(" ==== userId :" + userId + ",type :" + type);System.out.println("aliyun send message success");return "aliyun send message success";}
}
4.3.3 添加SPI配置文件

按照前文的方式,在resources目录下创建一个文件,注意文件名称为SPI中的接口全名,文件内容为实现类的全类名

com.congge.spi.BitptImpl
4.3.4 将jar安装到仓库中

完成实现类的编码后,通过maven命令将jar安装到仓库中,然后再在上一步的biz-app中引入即可;

4.4 效果演示

启动biz-app服务,调用接口:localhost:8087/sendMsg?msg=sendMsg,可以看到如下效果

图片

为什么会出现这个效果呢?因为我们在实现类配置了具体使用哪一种方式进行短信的发送,而加载插件的时候正好能够找到对应的服务实现,这样的话就给当前的业务提供了一个较好的扩展点。

图片

五、写在文末

从当前的趋势来看,插件化机制的思想已经遍布各种编程语言,框架,中间件,开源工具等领域,因此掌握插件化的实现机制对于当下做程序实现,或架构设计方面都有着很重要的意义,值得深入研究,本篇到此结束

相关文章:

SpringBoot 插件化开发模式

一、前言 1.1 使用插件的好处 1.1.1 模块解耦 实现服务模块之间解耦的方式有很多&#xff0c;但是插件来说&#xff0c;其解耦的程度似乎更高&#xff0c;而且更灵活&#xff0c;可定制化、个性化更好。 举例来说&#xff0c;代码中可以使用设计模式来选择使用哪种方式发送…...

基于树莓派的安保巡逻机器人--项目介绍

目录 一、项目简介 二、项目背景 三、作品研发技术方案 作品主要内容&#xff1a; 方案的科学性 设计的合理性 四、作品创新性及特点 五、作品自我评价 本篇为项目“基于树莓派的安保巡逻机器人”介绍博客 演示视频链接&#xff1a; 基于树莓派的安保巡逻机器人_音游…...

Python学习笔记8-函数1

自定义函数 def 函数名(形参):函数体return 空/变量/对象/表达式 形参&#xff1a;函数定义时声明的参数实参&#xff1a;函数调用时传入的参数。函数只需要定义一次&#xff0c;就可以被多次使用当函数被调用时&#xff0c;才执行函数体&#xff0c;定义时不执行 文档注释 …...

如何使用ST7789展现图片?[ESP--4]

本节我们继续ESP和ST 7789的话题&#xff0c;这节课我们来学学如何展示图片,话不多说&#xff0c;先上效果 好&#xff0c;教程开始~前情提要&#xff0c;要看懂这篇&#xff0c;建议搭配楼主的前两期文章 使用ESP32驱动LCD-ST7789屏幕[ESP–2] 加速你的LCD-ST7789屏幕&#xf…...

【QNX+Android虚拟化方案】129 - USB眼图参数配置

【QNX+Android虚拟化方案】129 - USB眼图参数配置 1. 软件侧dts如何配置眼图参数 及 其对应关系2. 硬件 QNX 侧调试眼图命令2.1 High Speed USB2.0 Host2.2 Super Speed USB3.0 Host3. 硬件 Android 侧调试眼图命令基于原生纯净代码,自学总结 纯技术分享,不会也不敢涉项目、不…...

【机器学习】探索机器学习决策树算法的奥秘

决策树 前言基本概念常见的决策树算法ID3算法C4.5算法CART算法 决策树的优缺点应用场景决策树的可视化总结 前言 在当今这个数据驱动的时代&#xff0c;机器学习作为数据分析与预测的利器&#xff0c;正以前所未有的速度改变着我们的生活和工作方式。在众多机器学习算法中&…...

K8S版本和istio版本的对照关系

版本对照关系 下载地址1 下载地址2...

嵌入式硬件实战提升篇(三)商用量产电源设计方案 三路电源输入设计 电源管理 多输入供电自动管理 DCDC降压

引言&#xff1a;本文你能实际的了解到实战量产产品中电源架构设计的要求和过程&#xff0c;并且从实际实践出发搞懂电源架构系统&#xff0c;你也可以模仿此架构抄板到你自己的项目&#xff0c;并结合硬件篇之前的项目以及理论形成正真的三路电源输入设计与开发板电源架构块供…...

【机器学习】机器学习的基本分类-监督学习-逻辑回归-Sigmoid 函数

Sigmoid 函数是一种常用的激活函数&#xff0c;尤其在神经网络和逻辑回归中扮演重要角色。它将输入的实数映射到区间 (0, 1)&#xff0c;形状类似于字母 "S"。 1. 定义与公式 Sigmoid 函数的公式为&#xff1a; 特点 输出范围&#xff1a;(0, 1)&#xff0c;适合用…...

EasyDarwin搭建直播推流服务

学习链接 easydarwin官网 - 这里看介绍 easydarwin软件下载地址 - 百度网盘 easydarwin视频 B站 文章目录 学习链接使用下载EasyDarwin压缩包&#xff0c;并解压到目录启动EasyDarwin点播直播easyplayer.jsapidocffmpeg推流rtsp & ffplay拉流 使用 下载EasyDarwin压缩包…...

无人机数据处理系统:原理与核心系统

一、数据处理系统的运行原理 数据获取&#xff1a;无人机在飞行过程中&#xff0c;通过搭载的传感器&#xff08;如相机、激光雷达等&#xff09;采集到各种类型的数据&#xff0c;例如图像、点云等。这些数据是后续处理和分析的基础。 数据传输&#xff1a;采集到的数据会通…...

DLL中的inline static成员变量:Windows开发中的常见陷阱

在Windows平台进行C开发时&#xff0c;DLL&#xff08;动态链接库&#xff09;是一个非常重要的概念。它让我们能够实现代码的模块化和动态加载&#xff0c;提高了程序的灵活性和维护性。然而&#xff0c;当我们在DLL中使用C17引入的inline static成员变量时&#xff0c;可能会…...

pandas 读写excel

在Python中&#xff0c;使用Pandas库读写Excel文件是一个常见的操作。Pandas提供了read_excel和to_excel方法来分别实现读取和写入Excel文件的功能。以下是一些基本的示例&#xff1a; ### 读取Excel文件 python import pandas as pd # 读取Excel文件 df pd.read_excel(pat…...

记录Threadlocal使用

编写ThreadLocal工具类 package com.jjking.jplan.context;public class BaseContext<T> {public static final ThreadLocal threadLocal new ThreadLocal();//存储用户public static void set(Object t) {threadLocal.set(t);}//获取用户public static <T> T ge…...

2024 ccpc 辽宁省赛 E(构造 思维?)L(二分+一点点数论知识?)

E 题意&#xff1a; 可以注意到&#xff1a; 我的两种方格都四个方格的大小。 所以 如果存在一种摆放方式 那么 4|nm。 再考虑一种特殊的情况 22 &#xff0c;此时虽然我的积是4 但是无法摆放的。 1>对于 4 | n,或者 4 | m.我直接摆放第二种方格就可以了。 如果我n 是4 的…...

【iOS】设计模式的六大原则

【iOS】设计模式的六大原则 文章目录 【iOS】设计模式的六大原则前言开闭原则——OCP单一职能原则——SRP里氏替换原则——LSP依赖倒置原则——DLP接口隔离原则——ISP迪米特法则——LoD小结 前言 笔者这段时间看了一下有关于设计模式的七大原则&#xff0c;下面代码示例均为OC…...

网络安全:攻防技术-Google Hacking的实现及应用

前言 google hacking其实并算不上什么新东西&#xff0c;在早几年我在一些国外站点上就看见过相关的介绍&#xff0c;但是由于当时并没有重视这种技术&#xff0c;认为最多就只是用来找找未改名的mdb或者别人留下的webshell什么的&#xff0c;并无太大实际用途。但是前段时间仔…...

输入一行字符,分别统计出其中英文字母、空格、数字和其它字符的个数。-多语言

目录 C 语言实现 Python 实现 Java 实现 Js 实现 Ts 实现 题目&#xff1a;输入一行字符&#xff0c;分别统计出其中英文字母、空格、数字和其它字符的个数。 程序分析&#xff1a;利用while语句,条件为输入的字符不为\n。 C 语言实现 #include <stdio.h>int mai…...

2-2-18-9 QNX系统架构之文件系统(三)

阅读前言 本文以QNX系统官方的文档英文原版资料为参考&#xff0c;翻译和逐句校对后&#xff0c;对QNX操作系统的相关概念进行了深度整理&#xff0c;旨在帮助想要了解QNX的读者及开发者可以快速阅读&#xff0c;而不必查看晦涩难懂的英文原文&#xff0c;这些文章将会作为一个…...

各大浏览器(如Chrome、Firefox、Edge、Safari)的对比

浏览器如Chrome、Firefox、Edge等在功能、性能、隐私保护等方面各有特点。以下是对这些浏览器的详细对比&#xff0c;帮助你选择合适的浏览器。 1. Google Chrome 市场份额&#xff1a;Chrome是目前市场上最流行的浏览器&#xff0c;约占全球浏览器市场的65%以上。 性能&#…...

nginx搭建直播推流服务

文章目录 学习链接步骤使用nginx搭建直播推流服务安装依赖库下载nginx-http-flv-module模块下载nginx解压nginx&#xff0c;进入nginx目录设置nginx编译配置编译并安装配置nginx rtmp服务启动nginx 准备另外一台电脑下载OBS下载OBS windows | linux 安装vlc观看直播flv协议hls协…...

单片机-- 松瀚sonix学习过程

硬件&#xff1a;松瀚sn8f5701sg、SN-LINK 3 Adapter模拟器、sn-link转接板 软件&#xff1a; keil-c51&#xff08;v9.60&#xff09;&#xff1a;建立工程&#xff0c;编辑&#xff0c;烧录程序 SN-Link_Driver for Keil C51_V3.00.005&#xff1a;安装sonix设备包和snlin…...

循环神经网络:从基础到应用的深度解析

&#x1f35b;循环神经网络&#xff08;RNN&#xff09;概述 循环神经网络&#xff08;Recurrent Neural Network, RNN&#xff09;是一种能够处理时序数据或序列数据的深度学习模型。不同于传统的前馈神经网络&#xff0c;RNN具有内存单元&#xff0c;能够捕捉序列中前后信息…...

从扩散模型开始的生成模型范式演变--SDE

SDE是在分数生成模型的基础上&#xff0c;将加噪过程扩展时连续、无限状态&#xff0c;使得扩散模型的正向、逆向过程通过SDE表示。在前文讲解DDPM后&#xff0c;本文主要讲解SDE扩散模型原理。本文内容主要来自B站Up主deep_thoughts分享视频Score Diffusion Model分数扩散模型…...

【python使用kazoo连ZooKeeper基础使用】

from kazoo.client import KazooClient, KazooState from kazoo.exceptions import NoNodeError,NodeExistsError,NotEmptyError import json# 创建 KazooClient 实例&#xff0c;连接到 ZooKeeper 服务器 zk KazooClient(hosts127.0.0.1:2181) zk.start()# 定义节点路径 path…...

【设计模式系列】解释器模式(十七)

一、什么是解释器模式 解释器模式&#xff08;Interpreter Pattern&#xff09;是一种行为型设计模式&#xff0c;它的核心思想是分离实现与解释执行。它用于定义语言的文法规则&#xff0c;并解释执行语言中的表达式。这种模式通常是将每个表达式抽象成一个类&#xff0c;并通…...

只出现一次的数字

只出现一次的数字 给你一个 非空 整数数组 nums &#xff0c;除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次。找出那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法来解决此问题&#xff0c;且该算法只使用常量额外空间。 示例 1 &#xff…...

SpringMVC-08-json

8. Json 8.1. 什么是Json JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式&#xff0c;目前使用特别广泛。采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。易于人阅读和编写&#xf…...

技术文档的语言表达

技术文档的语言表达 在这个瞬息万变的技术世界中&#xff0c;了解如何撰写有效的技术文档显得尤为重要。无论是开发团队还是最终用户&#xff0c;清晰、简洁且有条理的文档都是连接各方的桥梁。本文将深入探讨技术文档的语言表达&#xff0c;从其重要性、写作原则到各种类型&a…...

UEFI 事件

UEFI 不再支持中断&#xff08;准确地说&#xff0c;UEFI 不再为开发者提供中断支持&#xff0c;但在UEFI内部还是使用了时钟中断&#xff09;&#xff0c;所有的异步操作都要通过事件&#xff08;Event&#xff09;来完成。 启动服务为开发者提供了用于操作事件、定时器及TPL…...

做家政服务类网站的要求/如何在百度发布信息推广

1. 简介&#xff1a; 项目中用am3359从TF卡的reiser文件系统转到ext4文件。转化原因是reiser文件系统的官网都没有了&#xff0c;还有文章提到reiserfs4的代码风格不符合linux代码风格&#xff0c;因此&#xff0c;linux不再主动将reiserfs的代码融合到linux中&#xff0c;即使…...

wordpress文章指定数量/上海优化外包

当前位置:我的异常网 Java Web开发 插入数据库错误插入数据库错误www.myexceptions.net 网友分享于&#xff1a;2013-08-03 浏览&#xff1a;6次插入数据库异常往mysql里面的表里插数据&#xff0c;表里的字段见下面定义的这一片&#xff0c;下面的代码是往数据库里插得jsp&…...

网站开发与维护是干什么的/小视频网站哪个可以推广

linux下mysql 启动问题刚开始学mysql时都是用redhat自带的。启动是什么 /rc.d/init.d/ start这很简单&#xff0c;但是后来越学越多&#xff0c;系统自带的mysql&#xff0c;有的是版本太低&#xff0c;有的是与自己想要装的web服务需要的低版本的mysql后来自己学着以tar的方式…...

佛山搭建建网站哪家好/seo关键词优化排名软件

欢迎来到梁钟霖个人博客网站。本个人博客网站提供最新的站长新闻,各种互联网资讯。 还提供个人博客模板,最新最全的java教程,java面试题。在此我将尽我最大所能将此个人博客网站做的最好! 谢谢大家,愿大家一起进步! 背景 既然我们已经使用springbootdubbo 那么也不放继续深…...

做静态网站有什么用/seo关键词优化外包公司

第1关:求二维数组中最大值及所在的位置 任务描述 本关任务:编写一个能计算求二维数组中最大值及所在的位置。 相关知识 为了完成本关任务,你需要掌握:1.如何生成一个二维数组,2.如何求最大值及所在位置。 生成二维数组 可以利用random.seed(n)设置随机种子,再利用rand…...

对中国建设银行网站的评价/海南百度推广公司有哪些

3.37&#xff1a;下面的程序是何含义&#xff0c;程序的输出结果是什么&#xff1f; const char ca[ ] {h , e , l , l &#xff0c;o }; const char *cpca; while(*cp)  { cout<<*cp<<endl; cp; } Ans:输出结果为 h \n e\n l\n l\n o\n ........&#xff08…...