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 {
    @Override
    public void start(AppContext context) {
        // 插件启动时,进行初始化工作
    }
    
    @Override
    public void prestop() throws Throwable {
        // 插件预停止时的操作
        // 注意:预停止后,插件将等待几秒后才会停止,适用于需要安全停止的插件
    }
    
    @Override
    public 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 {
    @Override
    public 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 {
    @Inject
    AppMapper appMapper;

    @Tran
    public 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 {
    @Inject
    AppMapper appMapper;

    @Tran
    public 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;

    @Override
    public void start(AppContext context) {
        this.context = context;

        // 添加配置文件
        context.cfg().loadAdd("demo1011.plugin1.yml");

        // 扫描插件的 Bean
        context.beanScan(Plugin1Impl.class);

        // 注册静态文件仓库
        staticRepository = new ClassPathStaticRepository(context.getClassLoader(), "plugin1_static");
        StaticMappings.add("/html/", staticRepository);
    }

    @Override
    public 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 {
    // 要考虑模板所在的 classloader
    static final FreemarkerRender viewRender = new FreemarkerRender(BaseController.class.getClassLoader());

    @Override
    public 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) {
            //ParameterizedType
            return getTypeClass(((ParameterizedType) type).getRawType());
        } else {
            //TypeVariable
            return 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();
    }
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

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

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

分布式cap

P&#xff08;分区安全&#xff09;都能保证&#xff0c;就是在C&#xff08;强一致&#xff09;和A&#xff08;性能&#xff09;之间做取舍。 &#xff08;即立马做主从同步&#xff0c;还是先返回写入结果等会再做主从同步。类似的还有&#xff0c;缓存和db之间的同步。&am…

【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…

借助 CC-Link IE FB 转 Profinet 网关实现西门子 PLC 接入三菱 PLC 系统的解决策略

我们公司自动化生产线上&#xff0c;原有的控制系统采用三菱 PLC 通过 CC-Link IEFB 网络进行通信和控制。后来随着企业生产规模的扩大和对自动化系统集成度要求的提高&#xff0c;需要将部分设备与新引入的西门子 PLC 控制系统相连接&#xff0c;而西门子 PLC 使用 ProfiNet 协…

即时通信系统项目总览

聊天室服务端项目总体介绍 本项目是一个全栈的即时通信系统, 前端使用QT实现聊天客户端, 后端采⽤微服务框架设计, 由网关子服务统一接收客户端的请求, 再分发到不同的子服务上处理并将结果返回给网关, 网关再将响应转发给客户端 拆分的微服务包含&#xff1a; 网关服务器&…

Redis的五种数据类型(Set、Zset)

目录 1. Set 集合1.1 Set介绍1.2 常见命令1.2.1 SADD命令1.2.2 SMEMBERS命令1.2.3 SISMEMBER命令1.2.4 SCARD命令1.2.5 SPOP命令1.2.6 SMOVE命令1.2.7 SREM命令 1.3 集合间操作1.3.1 SINTER命令1.3.2 SINTERSTORE命令1.3.3 SUNION命令1.3.4 SUNIONSTORE命令1.3.5 SDIFF命令1.3.…

负载均衡OJ项目中遇到的问题

1、续行符问题 关于换行符 &#xff0c;代码在使用了换行符后无法编译文件&#xff0c;也没有爆出任何错误&#xff0c;更没有按照我们的代码打印出如下类似内容 &#xff1a;[ERROR][compiler.hpp][66][1732635247]编译失败,没有形成可执行程序 随机排查才发现。 代码中的 \ …

力扣面试150 环形子数组的最大和 循环数组 逆向思维

Problem: 918. 环形子数组的最大和 &#x1f468;‍&#x1f3eb; 参考题解 ⏰ 时间复杂度&#xff1a; O ( n ) O(n) O(n) &#x1f30e; 空间复杂度&#xff1a; O ( 1 ) O(1) O(1) class Solution {public int maxSubarraySumCircular(int[] nums) {int maxSum Integer.M…

Java 享元模式:打造高扩展游戏角色模型,优化 MMO 游戏开发

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

【精选】AI Coding 新范式:Windsurf、Cursor、Coze齐上阵

2AGI.NET | 探索 AI 无限潜力&#xff0c;2AGI 为您带来最前沿资讯。 随着人工智能技术的飞速发展&#xff0c;AI Coding领域迎来了前所未有的变革。Codeium的Windsurf、Cursor的agent模式更新、Copilot的新版本以及Coze的AI应用能力&#xff0c;都在推动着编程领域的创新。本期…

我的世界网易版安装Continuum光影

先去官网下载这个光影压缩包&#xff0c;下载完不要解压&#xff08;网慢的开加速器&#xff09; Downloads - Continuum Graphics 进去后选最新的版本号下载就行&#xff1a; 下载好后&#xff0c;先不管他&#xff0c;通过网易游戏启动器打开我的世界 觉得自己电脑性能将将…

使用uniapp开发小程序场景:在百度地图上调用接口返回的设备相关信息并展示

首先在百度地图开发者平台注册微信小程序开发密钥下载百度地图SDK-bmap-wx.min.js,下载地址在项目入口index.html页面进行引入页面中进行调用&#xff0c;代码示例如下<map id"map" longitude"108.95" latitude"34.34" scale"3" :m…

Push an existing folder和Push an existing Git repository的区别

Push an existing folder 和 Push an existing Git repository 是在使用 Git 服务&#xff08;如 GitHub、GitLab、Bitbucket 等&#xff09;时两个常见的操作选项。它们的区别主要体现在项目的初始化和版本控制状态上&#xff1a; 1. Push an existing folder 适用场景&#…

机器学习详解(2):线性回归之理论学习

文章目录 1 监督学习2 线性回归2.1 简单/多元线性回归2.2 最佳拟合线2.3 成本函数和梯度下降2.4 线性回归的假设2.5 线性回归的评估指标函数 3 总结 机器学习是人工智能的一个分支&#xff0c;主要致力于开发能够从数据中学习并进行预测的算法和统计模型。线性回归是机器学习的…

ASP.NET Core8.0学习笔记(二十五)——EF Core Include导航数据加载之预加载与过滤

一、导航属性数据加载 1.在EF Core中可以使用导航属性来加载相关实体。 2.加载实体的三种方式&#xff1a; (1)预先加载&#xff1a;直接在查询主体时就把对应的依赖实体查出来&#xff08;作为初始查询的一部分&#xff09; (2)显式加载&#xff1a;使用代码指示稍后显式的从…

QT实战--带行号的支持高亮的编辑器实现(1)

本文主要介绍了基于QPlainTextEdit实现的&#xff0c;带有行号的,支持高亮的编辑器实现&#xff0c;话不多说&#xff0c;先上效果图&#xff1a; 1.行号头文件&#xff1a;linenumberarea.h #ifndef LINENUMBERAREA_H #define LINENUMBERAREA_H#include <QWidget> #inc…

基于Matlab的卷积神经网络(CNN)苹果分级检测系统

本研究提出了一种基于卷积神经网络&#xff08;CNN&#xff09;的自动化苹果分级系统&#xff0c;该系统根据苹果的视觉特征进行分类。系统采用了预训练的深度学习模型&#xff0c;使用包含不同等级苹果的图像数据集进行训练。研究方法包括图像预处理、特征提取和苹果等级分类。…

华为、华三交换机纯Web下如何创关键VLANIF、操作STP参数

华为交换机WEB操作 使用的是真机S5735&#xff0c;目前主流的版本都适用&#xff08;V1R5~V2R1的就不在列了&#xff0c;版本太老了&#xff0c;界面完全不一样&#xff0c;这里调试线接的console口&#xff0c;电脑的网络接在ETH口&#xff09; 「模拟器、工具合集」复制整段内…

ACM:均分纸牌

主要思路 整体思路概述&#xff1a; 本题旨在解决给定N堆纸牌&#xff08;纸牌总数是N的倍数&#xff09;&#xff0c;通过按照特定移牌规则移动纸牌&#xff0c;找出用最少移动次数使每堆纸牌数量相等的方法。程序采用了一种逐步调整的思路&#xff0c;先计算出每堆纸牌应有的…

定时任务——xxl-job源码解析

摘要 本文深入解析了xxl-job的源码&#xff0c;xxl-job是一个分布式任务调度平台&#xff0c;其核心设计思想是将调度行为抽象成“调度中心”&#xff0c;而任务逻辑则由“执行器”处理&#xff0c;实现调度与任务的解耦。文章详细介绍了调度器和执行器的初始化流程、任务执行…