Plugin - 插件开发05_Solon中的插件实现机制

文章目录

  • Pre
  • 概述
  • 插件
  • 插件扩展机制(Spi)
    • 插件扩展机制概述
      • 插件扩展机制的优势
    • 插件扩展机制实现步骤
      • 第一步:定制插件实现类
        • 示例代码:插件实现类
      • 第二步:通过插件配置文件声明插件
        • 示例插件配置文件:`META-INF/solon/solon.data.properties`
      • 第三步:扫描并发现插件
    • 插件的排除
        • 示例:排除插件
    • 插件包的命名规范
    • 示例:Solon Data 插件
      • 插件实现类:`XPluginImp.java`
      • 插件配置文件:`META-INF/solon/solon.data.properties`
      • 插件应用示例
      • 插件引入
    • 小结
  • 插件体外扩展机制(E-Spi)
    • E-Spi 插件体外扩展机制概述
    • E-Spi 机制特点
      • 1. 共享资源
      • 2. 灵活的打包方式
      • 3. 更新机制
      • 4. 内核支持
    • E-Spi 插件扩展操作说明
      • 配置扩展目录
        • 示例配置:手动指定扩展目录
        • 示例配置:自动创建扩展目录
      • 插件包和配置文件加载
        • 1. 数据源配置文件
        • 2. 插件包
      • 插件包的打包注意事项
        • 1. 打成 FatJar
        • 2. 与主应用共享依赖
      • 示例演示:E-Spi 扩展机制使用示例
        • 目录结构
        • 配置文件示例:`_db.properties`
        • 插件包示例:`demo_user.jar`
        • 插件包示例:`demo_order.jar`
        • 主应用代码示例
    • 小结
  • 插件热插拔管理机制(H-Spi)
    • H-Spi 插件热插拔管理机制概述
      • 1. 特点说明
        • 1.1 完全隔离
        • 1.2 动态热插拔
        • 1.3 资源独立管理
        • 1.4 事件总线交互
      • 2. 关于 ClassLoader 隔离
        • 2.1 父子级 ClassLoader
        • 2.2 同级 ClassLoader
        • 2.3 插件之间的独立性
      • 3. 插件开发注意事项
        • 3.1 插件资源的管理
        • 3.2 资源清理
        • 3.3 模板渲染的 ClassLoader 问题
      • 4. 插件管理
    • 小结
  • Code

在这里插入图片描述


Pre

插件 - 通过SPI方式实现插件管理

插件 - 一份配置,离插件机制只有一步之遥

插件 - 插件机制触手可及

Plugin - 插件开发01_SPI的基本使用

Plugin - 插件开发02_使用反射机制和自定义配置实现插件化开发

Plugin - 插件开发03_Spring Boot动态插件化与热加载

Plugin - 插件开发04_Spring Boot中的SPI机制与Spring Factories实现


概述

https://solon.noear.org/article/441

本系列在内核知识的基础上做进一步延申。主要涉及:

  1. 插件
  2. 插件扩展体系(Spi)
  3. 插件体外扩展体系(E-Spi)
  4. 插件热插拔管理机制(H-Spi)

这些知识,为构建大的项目架构会有重要帮助。

本系列演示可参考:

https://gitee.com/noear/solon-examples/tree/main/2.Solon_Advanced


插件

Solon Plugin 是框架的核心接口,简称“插件”。其本质是一个参与应用“生命周期”的接口。它可以代表一个模块参与应用的生命周期过程

public interface Plugin {//启动void start(AppContext context) throws Throwable;//预停止default void prestop() throws Throwable{}//停止default void stop() throws Throwable{}
}

插件扩展机制(Spi)

Solon框架实现的插件扩展机制,基于“插件”+“配置声明”的方式,提供了一种轻量、解耦、灵活的扩展方式,类似于Spring的Factories和Java的SPI(Service Provider Interface)。本文将详细介绍Solon插件扩展机制的实现及使用。

插件扩展机制概述

Solon的插件扩展机制简化了模块化的开发和插件的管理,允许开发者将可复用的功能封装成插件,并通过配置文件进行管理和加载。插件的核心作用是在应用启动时提供初始化和生命周期管理的功能,使得模块化的能力可以灵活地集成到系统中。

插件的类型非常多样,例如像@Tran@Cache等注解功能,通常希望在多个项目中复用,因此它们的实现会被封装为插件。

插件扩展机制的优势

  • 解耦:插件的引入和管理通过配置声明,使得核心业务与插件逻辑解耦。
  • 灵活:插件可以根据需求进行启用、配置和排除。
  • 扩展性强:用户可以方便地通过插件实现功能扩展,并可以根据优先级排序加载插件。

插件扩展机制实现步骤

第一步:定制插件实现类

插件实现类负责处理插件的生命周期,通常包括启动、停止、预停止等方法。在Solon中,插件实现类需要实现Plugin接口,并重写startprestopstop等方法。

示例代码:插件实现类
public class XPluginImpl implements Plugin {@Overridepublic void start(AppContext context) {// 插件启动时,进行初始化工作}@Overridepublic void prestop() throws Throwable {// 插件预停止时的操作// 注意:预停止后,插件将等待几秒后才会停止,适用于需要安全停止的插件}@Overridepublic void stop() {// 插件停止时的清理工作}
}

第二步:通过插件配置文件声明插件

插件需要通过配置文件来声明自己,配置文件通常放置在META-INF/solon/目录下,并且文件名需保证全局唯一,通常使用包名作为文件名,以避免冲突。

示例插件配置文件:META-INF/solon/solon.data.properties
# 插件实现类配置
solon.plugin=org.noear.solon.data.integration.XPluginImp  
# 插件优先级配置,值越大优先级越高,默认为0
solon.plugin.priority=1

第三步:扫描并发现插件

在应用启动时,Solon框架会扫描META-INF/solon/目录下的所有配置文件,加载并按照配置文件中的优先级排序插件。

插件的排除

有时,在项目中可能引入了多个插件包,但我们希望排除某些插件。Solon提供了solon.plugin.exclude配置项来排除不需要的插件。

示例:排除插件
solon.plugin.exclude:- "org.noear.solon.data.integration.XPluginImp"

插件包的命名规范

Solon框架对插件包有一定的命名规范,以便于区分不同类型的插件:

插件命名规则说明
solon-*表示内部架构插件
*-solon-plugin表示外部适配插件
*-solon-cloud-plugin表示云接口外部适配插件

示例:Solon Data 插件

Solon Data插件提供了事务管理的能力,使用@Tran注解可以自动管理事务。下面我们以Solon Data插件为例,演示如何实现一个插件。

插件实现类:XPluginImp.java

public class XPluginImp implements Plugin {@Overridepublic void start(AppContext context) {// 如果启用事务管理,则添加事务拦截器if (Solon.app().enableTransaction()) {context.beanInterceptorAdd(Tran.class, new TranInterceptor(), 120);}}
}

插件配置文件:META-INF/solon/solon.data.properties

# 配置插件实现类
solon.plugin=org.noear.solon.data.integration.XPluginImp
# 配置插件优先级
solon.plugin.priority=3

插件应用示例

在应用中,我们可以通过@Tran注解来使用插件提供的事务管理能力。

@Component
public class AppService {@InjectAppMapper appMapper;@Tranpublic void addApp(App app) {// 事务管理:如果方法执行失败,将回滚事务appMapper.add(app);}
}

插件引入

pom.xml中引入solon-data插件依赖:

<dependency><groupId>org.noear</groupId><artifactId>solon-data</artifactId><version>最新版本</version>
</dependency>

小结

Solon框架的插件扩展机制为我们提供了一种非常灵活、解耦且可扩展的方式来实现应用的功能扩展。通过插件机制,我们可以将通用的功能封装成插件,并且在应用中按需引入。插件的生命周期由Solon自动管理,而通过配置文件,我们可以灵活地管理插件的优先级、排除插件等。


插件体外扩展机制(E-Spi)

很多时候我们需要解决如何在 fatjar 模式部署时实现插件化扩展的问题。Solon框架提供了一个名为 E-Spi 的插件体外扩展机制,旨在解决将业务模块、配置文件等放在应用外部的问题。通过 E-Spi,我们能够将插件和配置与主程序解耦,从而使得后期的插件更新、配置修改等更加灵活。

E-Spi 插件体外扩展机制概述

E-Spi 机制的设计主要是为了应对以下场景:

  • 将某些业务模块(如用户模块、订单模块等)打包成插件包放在外部。
  • 将配置信息(如数据源配置、缓存配置等)单独放在外部,便于后期修改。

E-Spi 使得插件和配置文件可以在应用外部部署,且支持后续的灵活修改和热更新。这种机制具有高效的插件管理功能,并且能够与现有的 fatjar 部署模式无缝集成。

E-Spi 机制特点

1. 共享资源

所有插件包共享同一个 ClassLoaderAppContext 和配置。也就是说,所有的插件都能访问相同的资源,保证插件之间的协作和互操作。

2. 灵活的打包方式

  • 体外插件包:插件可以作为独立的包(如 .jar 文件)放到体外,供主应用加载。
  • 内嵌插件包:插件也可以与主程序一起打包成一个 fatjar,支持两种方式的灵活切换。

3. 更新机制

  • 更新体外插件包或配置文件时,需要重启主服务才能生效。
  • 不同于直接打包在一起的插件,体外插件需要借助指定的扩展目录来加载。

4. 内核支持

E-Spi 是由 Solon 内核直接提供支持的,不需要额外的依赖,极大简化了扩展机制的使用。

E-Spi 插件扩展操作说明

配置扩展目录

为了让 Solon 知道从哪里加载外部插件和配置文件,我们需要在应用的配置文件中指定扩展目录。该目录可以手动创建,也可以通过配置让 Solon 自动创建。

示例配置:手动指定扩展目录
solon.extend: "demo_ext"

此时,demo_ext 目录需要手动创建,并且 Solon 会从这个目录加载插件和配置文件。

示例配置:自动创建扩展目录
solon.extend: "!demo_ext"

当使用 ! 前缀时,Solon 会在启动时自动创建该扩展目录。

插件包和配置文件加载

插件体外扩展机制支持将不同类型的文件放置在扩展目录中,并进行加载。

1. 数据源配置文件

假设我们有一个数据库配置文件 _db.properties,我们可以将它放置在扩展目录中,后续可以方便地进行修改而无需重新打包应用。

demo.jar
demo_ext/_db.properties
2. 插件包

除了配置文件外,我们还可以将业务模块做成插件包(如 .jar 文件),并将其放到扩展目录中。Solon会自动扫描并加载这些插件。

demo.jar
demo_ext/_db.properties
demo_ext/demo_user.jar
demo_ext/demo_order.jar

插件包的打包注意事项

插件包的管理是 E-Spi 机制中的关键部分。插件包可以选择以下两种打包方式:

1. 打成 FatJar

将插件包打成 fatjar(使用 maven-assembly-plugin 插件)是一种常见的方式。Fatjar 是包含所有依赖的单一 JAR 文件,这种方式方便部署,但文件较大。

2. 与主应用共享依赖

更推荐的方式是将插件包的公共依赖放入主应用中,而在插件包的 pom.xml 中将这些依赖标记为可选。这样可以避免重复依赖,提高应用的整体性能。

示例演示:E-Spi 扩展机制使用示例

目录结构

假设我们有如下的目录结构:

demo.jar
demo_ext/├── _db.properties├── demo_user.jar└── demo_order.jar

其中:

  • demo.jar 是主应用的 JAR 包。
  • demo_ext/ 目录包含了外部扩展的插件包和配置文件。
配置文件示例:_db.properties
# 数据源配置文件
db.url=jdbc:mysql://localhost:3306/demo
db.username=root
db.password=secret
插件包示例:demo_user.jar

demo_user.jar 可能包含了用户模块的相关功能,实现了业务逻辑或数据处理。

插件包示例:demo_order.jar

demo_order.jar 可能包含了订单模块的相关功能,同样是一个独立的插件包。

主应用代码示例

在主应用中,我们通过 @Inject 注解加载外部插件,并在应用中使用它们的功能。

@Component
public class AppService {@InjectAppMapper appMapper;@Tranpublic void addApp(App app) {appMapper.add(app);}
}

小结

Solon的插件体外扩展机制(E-Spi)为我们提供了一种非常灵活的方式来管理插件和配置文件。通过将插件和配置文件体外化,我们可以实现以下目标:

  • 模块化:不同的业务模块可以作为独立的插件进行管理和部署。
  • 灵活性:配置文件可以单独存放在外部,便于修改和热更新。
  • 简化依赖管理:公共依赖可以由主应用来统一管理,避免重复。

E-Spi 机制不仅能支持传统的 fatjar 模式,也能够满足更高效的插件管理需求,是构建高可扩展性微服务系统的理想选择。


插件热插拔管理机制(H-Spi)

插件化和模块化管理已经成为了开发的重要方向。Solon框架提供了一个名为 H-Spi 的插件热插拔管理机制,它专注于提供隔离性热插拔性管理性。与 E-Spi 机制不同,H-Spi 更强调在生产环境下的灵活性,尤其是在支持插件热更新和模块隔离方面的优势。

H-Spi 插件热插拔管理机制概述

H-Spi 是一种为生产环境设计的扩展方案。它的核心目标是通过热插拔机制管理插件,并确保插件间的隔离性。通过 H-Spi,应用能够在运行时动态加载、卸载插件,而无需重启主服务。每个插件在运行时都拥有独立的资源和上下文,避免了插件间的相互干扰。

1. 特点说明

1.1 完全隔离

H-Spi 机制下,每个插件包都会拥有独立的 ClassLoaderAppContext 和配置。插件之间的资源和环境完全隔离,可以避免不同插件之间的冲突或干扰。

  • ClassLoader 隔离:插件包独享自己的 ClassLoader,确保插件间不会互相访问或修改其他插件的资源。
  • AppContext 隔离:每个插件可以访问自己的上下文,但也可以通过 Solon.app()、Solon.cfg() 和 Solon.context() 等方式手动获取主程序或全局资源。
1.2 动态热插拔

H-Spi 机制支持热插拔,即可以在不重启主服务的情况下进行插件的动态加载和卸载。这大大提高了应用的灵活性,特别是在生产环境下进行插件更新时无需影响整个系统的稳定性。

1.3 资源独立管理

在插件开发过程中,开发者需要保证插件资源的独立性。如果插件启动时向系统添加了某些资源或对象,则在插件停止时必须移除这些资源,以支持热更新。

1.4 事件总线交互

为了保证插件之间的松耦合,插件之间的交互尽量通过事件总线(EventBus)进行。事件数据通常采用弱类型(如 Map 或 JSON 字符串)形式传递,以减少插件之间的依赖关系。推荐与 DamiBus 等工具结合使用,进一步解耦。

2. 关于 ClassLoader 隔离

H-Spi 的 ClassLoader 隔离 是其关键特性之一。在这种隔离下,插件只能访问自己的资源和类,无法直接访问其他插件的资源。这意味着,插件之间的交互需要更加小心,并避免直接通过类进行互相访问。

2.1 父子级 ClassLoader
  • 父级 ClassLoader:通常用于存放公共资源,子级插件可以访问父级 ClassLoader 中的资源。
  • 子级 ClassLoader:插件会使用自己的 ClassLoader,它无法直接访问同级插件的类或资源。
2.2 同级 ClassLoader
  • 同级插件之间无法直接交互。如果需要交互,建议通过事件总线进行,避免显式的类交互。
  • 交互的数据:可以使用父级 ClassLoader 的实体类,或者使用弱类型的数据,如 JSON 字符串等。
2.3 插件之间的独立性

建议尽量减少插件之间的交互,使每个插件都能独立运行。如果确实需要交互,最好通过事件总线(EventBus)来进行,且使用弱类型数据格式(如 Map 或 JSON)。

3. 插件开发注意事项

在 H-Spi 模式下,插件开发需要特别注意资源的添加与移除。特别是在插件停止时,必须清理所有为插件注册的资源,确保插件的热插拔能力得以保持。

3.1 插件资源的管理

例如,当插件启动时,可能会向系统添加配置文件、扫描 Bean 或注册静态文件等操作。这些资源在插件停止时需要被移除。

public class Plugin1Impl implements Plugin {private AppContext context;private StaticRepository staticRepository;@Overridepublic void start(AppContext context) {this.context = context;// 添加配置文件context.cfg().loadAdd("demo1011.plugin1.yml");// 扫描插件的 Beancontext.beanScan(Plugin1Impl.class);// 注册静态文件仓库staticRepository = new ClassPathStaticRepository(context.getClassLoader(), "plugin1_static");StaticMappings.add("/html/", staticRepository);}@Overridepublic void stop() throws Throwable {// 移除 HTTP 路由Solon.app().router().remove("/user");// 移除定时任务(如果有)JobManager.remove("job1");// 移除事件订阅context.beanForeach(bw -> {if (bw.raw() instanceof EventListener) {EventBus.unsubscribe(bw.raw());}});// 移除静态文件仓库StaticMappings.remove(staticRepository);}
}
3.2 资源清理

在插件停止时,务必执行清理工作,确保不会留下不必要的资源。这是 H-Spi 热插拔机制的关键。

3.3 模板渲染的 ClassLoader 问题

当涉及到模板渲染时,可能会遇到 ClassLoader 的问题。例如,如果使用 Freemarker 渲染模板时,必须确保模板加载的 ClassLoader 是正确的:

public class BaseController implements Render {// 要考虑模板所在的 classloaderstatic final FreemarkerRender viewRender = new FreemarkerRender(BaseController.class.getClassLoader());@Overridepublic void render(Object data, Context ctx) throws Throwable {if (data instanceof Throwable) {throw (Throwable) data;}if (data instanceof ModelAndView) {viewRender.render(data, ctx);} else {ctx.render(data);}}
}

4. 插件管理

在 H-Spi 中,插件不只是通过独立 ClassLoader 进行管理,还可以通过 solon-hotplug 插件来实现插件的仓库化和平台化。这样,插件可以通过中央仓库进行管理,并在平台上动态更新、卸载,进一步提升应用的灵活性和可扩展性。

通过插件管理工具,平台可以更好地控制插件的生命周期,支持对插件的版本管理、更新和回滚。

小结

H-Spi 插件热插拔管理机制是为生产环境设计的高效扩展方案,具有以下几个核心特点:

  1. 插件隔离性:每个插件包独享 ClassLoader、AppContext 和配置,避免了插件间的冲突。
  2. 热插拔:支持插件的动态加载和卸载,无需重启主服务,提升了系统的灵活性。
  3. 资源清理:插件在停止时必须移除自己注册的所有资源,以支持热更新。
  4. 事件总线交互:插件之间通过事件总线进行松耦合的交互,减少了插件之间的依赖。

H-Spi 提供了一个非常灵活的插件化架构,适用于需要高可扩展性和模块化管理的生产环境。通过 H-Spi,可以实现插件的热插拔和灵活更新,极大地提高了系统的可维护性和扩展能力。


Code


import org.noear.solon.Utils;
import org.noear.solon.core.PluginEntity;import java.net.URL;
import java.util.Collection;
import java.util.Properties;
import java.util.function.Consumer;/*** 插件工具** @author noear* @since 1.7*/
public class PluginUtil {/*** 扫描插件** @param classLoader 类加载器* @param excludeList 排除列表*/public static void scanPlugins(ClassLoader classLoader, Collection<String> excludeList, Consumer<PluginEntity> consumer) {//3.查找插件配置(如果出错,让它抛出异常)Collection<String> nameList = ScanUtil.scan(classLoader, "META-INF/solon", n -> n.endsWith(".properties"));for (String name : nameList) {URL res = ResourceUtil.getResource(classLoader, name);if (res == null) {// native 时,扫描出来的resource可能是不存在的(这种情况就是bug),需要给于用户提示,反馈给社区LogUtil.global().warn("Solon plugin: name=" + name + ", resource is null");} else {Properties props = Utils.loadProperties(res);findPlugins(classLoader, props, excludeList, consumer);}}}/*** 查找插件*/public static void findPlugins(ClassLoader classLoader, Properties props, Collection<String> excludeList,Consumer<PluginEntity> consumer) {String pluginStr = props.getProperty("solon.plugin");if (Utils.isNotEmpty(pluginStr)) {String priorityStr = props.getProperty("solon.plugin.priority");int priority = 0;if (Utils.isNotEmpty(priorityStr)) {priority = Integer.parseInt(priorityStr);}String[] plugins = pluginStr.trim().split(",");for (String clzName : plugins) {if (clzName.length() > 0) {if(excludeList.contains(clzName)) {continue;}PluginEntity ent = new PluginEntity(classLoader, clzName.trim(), props);ent.setPriority(priority);consumer.accept(ent);}}}}
}

import org.noear.solon.core.AppClassLoader;
import org.noear.solon.core.exception.ConstructionException;
import org.noear.solon.core.wrap.ClassWrap;import java.lang.reflect.*;
import java.util.Collection;
import java.util.Properties;/*** 类操作工具** @author noear* @since 2.2*/
public class ClassUtil {/*** 是否存在某个类** <pre><code>* if(ClassUtil.hasClass(()->DemoTestClass.class)){*     ...* }* </code></pre>** @param test 检测函数*/public static boolean hasClass(SupplierEx<Class<?>> test) {try {test.get();return true;} catch (ClassNotFoundException | NoClassDefFoundError e) {return false;} catch (Throwable e) {throw new IllegalStateException(e);}}/*** 根据字符串加载为一个类(如果类不存在返回 null)** @param className 类名称*/public static Class<?> loadClass(String className) {return loadClass(null, className);}/*** 根据字符串加载为一个类(如果类不存在返回 null)** @param classLoader 类加载器* @param className   类名称*/public static Class<?> loadClass(ClassLoader classLoader, String className) {try {if (classLoader == null) {return Class.forName(className);} else {return classLoader.loadClass(className);}} catch (ClassNotFoundException | NoClassDefFoundError e) {return null;}}/*** 尝试根据类名实例化一个对象(如果类不存在返回 null)** @param className 类名称*/public static <T> T tryInstance(String className) {return tryInstance(className, null);}/*** 尝试根据类名实例化一个对象(如果类不存在返回 null)** @param className 类名称* @param prop      属性*/public static <T> T tryInstance(String className, Properties prop) {return tryInstance(AppClassLoader.global(), className, prop);}/*** 尝试根据类名实例化一个对象(如果类不存在返回 null)** @param classLoader 类加载器* @param className   类名称*/public static <T> T tryInstance(ClassLoader classLoader, String className) {return tryInstance(classLoader, className, null);}/*** 尝试根据类名实例化一个对象(如果类不存在返回 null)** @param classLoader 类加载器* @param className   类名称* @param prop        属性*/public static <T> T tryInstance(ClassLoader classLoader, String className, Properties prop) {Class<?> clz = loadClass(classLoader, className);return tryInstance(clz, prop);}public static <T> T tryInstance(Class<?> clz, Properties prop) {if (clz == null) {return null;} else {try {return newInstance(clz, prop);} catch (Exception e) {throw new IllegalStateException(e);}}}/*** 根据类名实例化一个对象** @param clz 类*/public static <T> T newInstance(Class<?> clz) throws ConstructionException {return newInstance(clz, null);}/*** 根据类名实例化一个对象** @param clz  类* @param prop 属性*/public static <T> T newInstance(Class<?> clz, Properties prop) throws ConstructionException {try {if (prop == null) {return (T) clz.getDeclaredConstructor().newInstance();} else {return (T) clz.getConstructor(Properties.class).newInstance(prop);}} catch (Exception e) {throw new ConstructionException(e);}}/*** 根据类名和参数类型实例化一个对象** @param clz   类* @param types 构建参数类型* @param args  参数*/public static Object newInstance(Class<?> clz, Class<?>[] types, Object[] args) {try {Constructor<?> constructor = clz.getDeclaredConstructor(types);return constructor.newInstance(args);} catch (Exception e) {throw new ConstructionException(e);}}/*** 根据构造函数实例化一个对象** @param constructor 构造器* @param args        参数*/public static Object newInstance(Constructor constructor, Object[] args) {try {return constructor.newInstance(args);} catch (Exception e) {throw new ConstructionException(e);}}/private static final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();/*** 分析类加载器*/public static ClassLoader resolveClassLoader(Type type) {ClassLoader loader = AppClassLoader.global();if (type != null) {Class<?> clz = getTypeClass(type);if (clz != Object.class) {ClassLoader cl = clz.getClassLoader();if (cl != systemClassLoader) {loader = cl;}}}return loader;}/*** 获取类*/public static Class<?> getTypeClass(Type type) {if (type instanceof Class) {return (Class<?>) type;} else if (type instanceof ParameterizedType) {//ParameterizedTypereturn getTypeClass(((ParameterizedType) type).getRawType());} else {//TypeVariablereturn Object.class;}}/*** 比较参数类型*/public static boolean equalParamTypes(Class<?>[] params1, Class<?>[] params2) {if (params1.length == params2.length) {for (int i = 0; i < params1.length; i++) {if (params1[i] != params2[i])return false;}return true;}return false;}/*** 查找 method*/public static Collection<Method> findPublicMethods(Class<?> clz) {return ClassWrap.get(clz).findPublicMethods();}
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

JAVA-二叉树的概念和性质

目录 一.树形结构 1.1 概念 1.2 树的概念(重要)​编辑 补充&#xff1a;高度和深度的区别 1.3 树的应用 二. 二叉树&#xff08;重点&#xff09; 2.1 概念 2.2 两种特殊的二叉树 2.3 二叉树的性质 2.4 选择题 一.树形结构 1.1 概念 树是一种 非线性 的数据结构&…

SVM的基本思想

一、SVM的基本思想 SVM的基本思想是在样本的向量空间中寻找一个超平面&#xff0c;使得两类样本被分割在平面的两端。这样的平面理论上有无穷多个&#xff0c;但SVM的目标是找到一个最优的超平面&#xff0c;即两侧距离超平面最近的样本点到超平面的距离被最大化的超平面。这个…

【TCP 网络通信(发送端 + 接收端)实例 —— Python】

TCP 网络通信&#xff08;发送端 接收端&#xff09;实例 —— Python 1. 引言2. 创建 TCP 服务器&#xff08;接收端&#xff09;2.1 代码示例&#xff1a;TCP 服务器2.2 代码解释&#xff1a; 3. 创建 TCP 客户端&#xff08;发送端&#xff09;3.1 代码示例&#xff1a;TCP…

day08 接口测试(3)——postman工具使用

下载 postman 的历史版本&#xff1a;Postman 历史版本下载 - 简书 今天开始学习 postman 这个测试工具啦。 【没有所谓的运气&#x1f36c;&#xff0c;只有绝对的努力✊】 目录 1、postman简介 2、postman的安装 3、给postman安装插件——newman 3.1 环境安装 3.1.1 安…

README写作技巧

做一个项目&#xff0c;首先第一眼看上去要美观&#xff0c;这样才有看下去的动力。做项目亦是如此&#xff0c;如果每一步应付做的话&#xff0c;我想动力也不会太大&#xff0c;最终很大概率会放弃或者进度缓慢。 1.README组成 README是对项目的一个说明&#xff0c;它对观看…

渗透测试---burpsuite(5)web网页端抓包与APP渗透测试

声明&#xff1a;学习素材来自b站up【泷羽Sec】&#xff0c;侵删&#xff0c;若阅读过程中有相关方面的不足&#xff0c;还请指正&#xff0c;本文只做相关技术分享,切莫从事违法等相关行为&#xff0c;本人与泷羽sec团队一律不承担一切后果 视频地址&#xff1a;泷羽---bp&…

【Springboot3+vue3】从零到一搭建Springboot3+vue3前后端分离项目之前端环境搭建

【Springboot3vue3】从零到一搭建Springboot3vue3前后端分离项目之前端环境搭建 2 前端环境搭建2.1 环境准备2.2 创建Vue3项目2.3 项目搭建准备2.4 安装Element Plus2.5 安装axios2.5.1 配置&#xff08;创建实例&#xff0c;配置请求&#xff0c;响应拦截器&#xff09;2.5.2 …

11.27-12.5谷粒商城

目录 新增商品 1.上线会员服务 2. 获取分类关联的品牌 3.获取选定分类下的属性分组和属性 4.新增商品vo 5.保存商品信息 6.Spu检索 7.Sku商品检索 新增商品 1.上线会员服务 将会员服务注册到nacos注册中心&#xff0c;启用服务注册发现EnableDiscoveryClient。 同时新增…

深入解析非桥PCI设备的访问和配置方法

往期内容 本文章相关专栏往期内容&#xff0c;PCI/PCIe子系统专栏&#xff1a; 嵌入式系统的内存访问和总线通信机制解析、PCI/PCIe引入 Uart子系统专栏&#xff1a; 专栏地址&#xff1a;Uart子系统 Linux内核早期打印机制与RS485通信技术 – 末片&#xff0c;有专栏内容观看…

ArrayList常见操作源码逐句剖析

目录 前言 正文 1.需要了解的一些字段属性 1.存储 ArrayList 元素的数组缓冲区。 2.集合的大小 3.默认集合容量大小 2.ArrayList对象创建 1.无参构造 2.有参构造1 3.有参构造2 3.添加元素add(E e)以及扩容机制 ​编辑 后言 前言 源码的剖析有助于理解设计模式&…

现代密码学|Rabin密码体制及其数学基础 | 椭圆曲线密码体制及其运算 | DH密钥交换及中间人攻击

文章目录 参考Rabin密码体制及其数学基础中国剩余定理二次剩余Rabin密码体制实例 椭圆曲线密码体制及其运算原理运算规则加密解密实例 DH密钥交换及中间人攻击中间人攻击 参考 现代密码学&#xff5c;Rabin密码体制及其数学基础 现代密码学&#xff5c;椭圆曲线密码体制及其运…

硬件选型规则

光源选型: 先用型号中带H的&#xff0c;没有的选标准的. 光源和光源控制器的搭配需要确保接口一致。 根据型号表中的最佳工作距离和相机的尺寸。 光源控制器选型&#xff1a; 首先选择海康风格系列光源控制器考虑与光源的接口匹配。功率应该满足接近光源功率。检查是否退市…

sharedPreference包的使用总结

文章目录 1 概念介绍2 实现方法3 示例代码我们在上一章回中介绍了"如何自定义评分条"相关的内容,本章回中将介绍如何实现本地存储.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 Flutter是一套跨平台的UI框架,它不像原生SDK一样提供本地存储功能,因此,我们在…

TCP连接的时候遇到的异常(目标端口没开放)

import asyncioasync def check_port(ip, port, timeout1):"""检查目标 IP 和端口是否开放:param ip: 目标 IP 地址:param port: 目标端口:param timeout: 超时时间&#xff08;秒&#xff09;"""try:reader, writer await asyncio.open_connec…

C总结(C语言知识点,深化重难点)

C语言 1.使用C语言的7个步骤2.ASCII码3.提高程序可读性的机巧4.如何使用多种整形5.打印多种整形6.课移植类型&#xff1a;stdint.h和inttypes.h7.浮点数常量8.浮点值的上溢和下溢9.使用数据类型11.常量和C预处理器12.转换说明的意义12.1转换不匹配13.副作用和序列点14.数组简介…

burpsuite(6)暴力破解与验证码识别绕过

声明!!! 学习视频来自B站UP主泷羽sec&#xff0c;如涉及侵权马上删除文章 视频链接&#xff1a;泷羽sec-bilibili 笔记的只是方便各位师傅学习知识,以下网站只涉及学习内容,其他的都与本人无关,切莫逾越法律红线,否则后果自负 项目地址&#xff1a;https://github.com/f0ng/cap…

抗DDOS设备

0x00 定义: 抗DDOS设备顾名思义&#xff0c;就是防御DDoS攻击的设备&#xff0c;通常包含三个部分&#xff1a;检测中心、清洗中心和管理中心 检测中心主要负责对流量进行检测&#xff0c;发现流量异常后上报管理中心&#xff0c;由管理中心下发引流策略至清洗中心&#xff0…

systemV信号量与消息队列

目录 引言 ipc简介 ipc在kernel的管理机制&#xff08;简介&#xff09; 信号量 理解信号量 原子 结论 mmap 消息队列 接口 引言 在复杂的软件系统中&#xff0c;进程间的协调和通信是确保系统高效、稳定运行的关键。System V是一套历史悠久且功能强大的进程间通信&a…

【CKS最新模拟真题】Falco 的运行时安全性

系列文章目录 【CKS最新模拟真题】获取多个集群的上下文名称并保存到指定文件中 文章目录 系列文章目录参考地址一、TASK二、解题过程1、问题一解题2、问题二解题 参考地址 CKS考试允许打开falco的地址 https://falco.org/docs/reference/rules/supported-fields/ 一、TASK …

Altium Designer学习笔记 32 DRC检查_丝印调整

基于Altium Designer 23学习版&#xff0c;四层板智能小车PCB 更多AD学习笔记&#xff1a;Altium Designer学习笔记 1-5 工程创建_元件库创建Altium Designer学习笔记 6-10 异性元件库创建_原理图绘制Altium Designer学习笔记 11-15 原理图的封装 编译 检查 _PCB封装库的创建Al…