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

文章目录

  • Pre
  • 方案概览
  • 使用插件的好处
  • 流程
  • Code
    • Plugin 定义
    • Plugin 实现
    • Plugin 使用方
      • 动态加载插件
      • 类加载器
      • 注册与卸载插件
      • 配置文件
      • 启动类
      • 测试验证
  • 小结

在这里插入图片描述


Pre

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

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

插件 - 插件机制触手可及

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

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

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

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


方案概览

常用的插件化实现思路:

  1. spi机制
  2. spring内置扩展点
  3. spring aop技术
  4. springboot中的Factories机制
  5. 第三方插件包:spring-plugin
  6. java agent 技术

在这里插入图片描述


使用插件的好处

  • 模块解耦:插件机制能帮助系统模块间解耦,使得服务间的交互变得更加灵活。
  • 提升扩展性和开放性:通过插件机制,系统能够轻松扩展,支持新的功能或服务,无需大规模修改核心代码。
  • 方便第三方接入:第三方可以按照预定义的插件接口进行集成,不会对主系统造成过多侵入。

流程

定义接口
定义接口
定义接口
打包SDK
打包SDK
打包SDK
读取配置
指定实现类
Bean实例化
调用接口方法
返回结果
应用 A: 定义接口
应用 B: 实现接口并打包成SDK
应用 C: 实现接口并打包成SDK
应用 D: 实现接口并打包成SDK
应用 E: 引用SDK并动态加载实现类
配置文件: 配置项
启动时注册Bean/运行时注册Bean
实例化实现类
调用 接口方法

Code

在这里插入图片描述

要实现一个插件化系统,以便动态地引入外部插件。这些插件可能包括功能增强、第三方集成或业务逻辑的拓展。
定义插件接口。

实现插件机制的关键步骤

  • 插件实现类。
  • 通过Spring的 ImportBeanDefinitionRegistrar 动态注册插件。
    使用自定义类加载器加载外部JAR包中的类。
    在Spring应用中动态加载、更新和删除插件。

Plugin 定义

在这里插入图片描述

首先,我们需要定义一个插件接口,该接口为插件提供统一的方法。插件实现类将根据此接口进行开发。

package com.plugin;public interface IPlugin {String customPluginMethod(String name);
}

接口 IPlugin 包含一个方法 customPluginMethod,插件类可以通过实现该接口来定义具体的插件行为。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>SpringBootPluginTest</artifactId><groupId>com.plugin</groupId><version>0.0.1-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>plugin-api</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties></project>

Plugin 实现

在这里插入图片描述
插件实现类实现了 IPlugin 接口,提供了插件的具体功能。

package com.plugin.impl;import com.plugin.IPlugin;public class MyPluginImpl implements IPlugin {@Overridepublic String customPluginMethod(String name) {return "MyPluginImpl-customPluginMethod executed - " + name;}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.plugin</groupId><artifactId>SpringBootPluginTest</artifactId><version>0.0.1-SNAPSHOT</version></parent><groupId>com.plugin</groupId><artifactId>plugin-impl</artifactId><name>plugin-impl</name><description>插件实现类</description><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId></dependency><dependency><groupId>com.plugin</groupId><artifactId>plugin-api</artifactId><version>0.0.1-SNAPSHOT</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>8</source><target>8</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-assembly-plugin</artifactId><version>3.1.0</version><configuration><descriptorRefs><descriptorRef>jar-with-dependencies</descriptorRef></descriptorRefs><finalName>${project.artifactId}-${project.version}-jar-with-dependencies</finalName><appendAssemblyId>false</appendAssemblyId><attach>false</attach><archive><manifest><addDefaultImplementationEntries>true</addDefaultImplementationEntries></manifest></archive></configuration><executions><execution><id>make-assembly</id><phase>package</phase><goals><goal>single</goal></goals></execution></executions></plugin></plugins></build>
</project>

我们将接口实现打包为jar包, 放到业务使用方配置的目录下。


Plugin 使用方

动态加载插件

在Spring Boot中,我们可以通过自定义 ImportBeanDefinitionRegistrar 来实现插件的动态加载。在插件模块启动时,Spring Boot会自动加载并注册插件类

package com.plugin.config;import com.plugin.utils.ClassLoaderHelper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;/*** 启动时注册bean*/
@Slf4j
public class PluginImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {/*** jar的存放路径*/private String targetUrl;/*** 插件类全路径*/private String pluginClass;/*** 注册Bean定义到Spring容器中** @param importingClassMetadata 导入类的元数据,通常用于获取注解信息等* @param registry Bean定义的注册表,用于注册Bean定义*/@SneakyThrows@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 获取自定义类加载器,用于加载插件类ClassLoader classLoader = ClassLoaderHelper.getClassLoader(targetUrl);// 加载插件类Class<?> clazz = classLoader.loadClass(pluginClass);// 创建Bean定义构建器BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);// 获取Bean定义BeanDefinition beanDefinition = builder.getBeanDefinition();// 在注册表中注册Bean定义registry.registerBeanDefinition(clazz.getName(), beanDefinition);// 日志记录注册成功信息log.info("plugin register bean [{}],Class [{}] success.", clazz.getName(), clazz);}/*** 设置环境属性** @param environment 环境对象,用于获取环境属性*/@Overridepublic void setEnvironment(Environment environment) {// 从环境对象中获取目标URL属性this.targetUrl = environment.getProperty("targetUrl");// 从环境对象中获取插件类属性this.pluginClass = environment.getProperty("pluginClass");}
}

在 registerBeanDefinitions 方法中,使用自定义的类加载器 ClassLoaderHelper 动态加载插件类,并将其注册为Spring容器中的Bean。


类加载器

package com.plugin.utils;import lombok.extern.slf4j.Slf4j;import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;/*** 类加载器工具类*/
@Slf4j
public class ClassLoaderHelper {/*** 根据给定的URL获取一个ClassLoader实例* 此方法旨在动态加载指定位置的类资源,通过反射手段确保URLClassLoader的addURL方法可访问** @param url 类资源的URL地址,指示ClassLoader要加载的类的位置* @return URLClassLoader的实例,用于加载指定URL路径下的类文件如果无法创建或访问ClassLoader,则返回null*/public static ClassLoader getClassLoader(String url) {try {// 获取URLClassLoader的addURL方法,该方法允许向URLClassLoader添加新的URLMethod method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);// 如果方法不可访问,则设置其为可访问,因为addURL方法是受保护的,需要这样做才能调用if (!method.isAccessible()) {method.setAccessible(true);}// 创建一个新的URLClassLoader实例,初始URL为空数组,使用ClassLoaderUtil类的ClassLoader作为父ClassLoaderURLClassLoader classLoader = new URLClassLoader(new URL[]{}, ClassLoaderHelper.class.getClassLoader());// 在创建 URLClassLoader 时,指定当前系统的 ClassLoader 为父类加载器  ClassLoader.getSystemClassLoader() 这步比较关键,用于打通主程序与插件之间的 ClassLoader ,解决把插件注册进 IOC 时的各种 ClassNotFoundException 问题// URLClassLoader classLoader = new URLClassLoader(new URL[]{}, ClassLoader.getSystemClassLoader());// 调用addURL方法,将指定的URL添加到classLoader中,以便它可以加载该URL路径下的类method.invoke(classLoader, new URL(url));// 返回配置好的ClassLoader实例return classLoader;} catch (Exception e) {// 记录错误信息和异常堆栈,当无法通过反射访问或调用addURL方法时,会进入此块log.error("getClassLoader-error", e);// 返回null,表示未能成功创建和配置ClassLoader实例return null;}}}

为了加载外部JAR包中的插件类,需要一个自定义的类加载器。ClassLoaderHelper 类负责通过反射机制调用 addURL 方法,动态加载指定URL路径下的JAR包。


注册与卸载插件

package com.plugin.utils;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;/*** Spring 工具类**/
@Slf4j
@Component
public class SpringHelper implements ApplicationContextAware {private DefaultListableBeanFactory defaultListableBeanFactory;private ApplicationContext applicationContext;/*** 设置ApplicationContext环境** 当该类被Spring管理时,Spring会调用此方法将ApplicationContext注入* 通过重写此方法,我们可以自定义处理ApplicationContext的方式** @param applicationContext Spring的上下文对象,包含所有的Bean定义和配置信息* @throws BeansException 如果在处理Bean时发生错误*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {// 将传入的ApplicationContext赋值给类的成员变量,以便后续使用this.applicationContext = applicationContext;// 将applicationContext转换为ConfigurableApplicationContext,以便获取BeanFactoryConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;// 获取bean工厂并转换为DefaultListableBeanFactory,以便进行更深层次的自定义配置this.defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();}/*** 注册bean到spring容器中** @param beanName 名称* @param clazz    class*/public void registerBean(String beanName, Class<?> clazz) {// 通过BeanDefinitionBuilder创建bean定义BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);// 注册beandefaultListableBeanFactory.registerBeanDefinition(beanName, beanDefinitionBuilder.getRawBeanDefinition());log.info("register bean [{}],Class [{}] success.", beanName, clazz);}/*** 根据bean名称移除bean定义* 此方法检查指定的bean名称是否在BeanFactory中定义如果定义存在,则将其移除** @param beanName 要移除的bean的名称*/public void removeBean(String beanName) {// 检查BeanFactory中是否定义了指定名称的beanif(defaultListableBeanFactory.containsBeanDefinition(beanName)) {// 如果bean定义存在,则从BeanFactory中移除该定义defaultListableBeanFactory.removeBeanDefinition(beanName);}// 记录移除bean操作的日志信息log.info("remove bean [{}] success.", beanName);}/*** 根据bean的名称获取对应的bean实例* 此方法用于从Spring应用上下文中获取指定名称的bean,便于在需要的地方直接获取bean实例,避免了硬编码** @param name bean的名称,用于唯一标识一个bean* @return Object 返回指定名称的bean实例,类型为Object,可以根据需要转换为具体的类型*/public Object getBean(String name) {return applicationContext.getBean(name);}
}

插件一旦加载并注册到Spring IoC容器后,我们可以通过API来操作插件,比如执行插件中的方法,或者动态更新和卸载插件。

package com.plugin.controller;import com.plugin.IPlugin;
import com.plugin.utils.ClassLoaderHelper;
import com.plugin.utils.SpringHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@Slf4j
@RestController
public class PluginTestController {@Autowired(required = false)private IPlugin IPlugin;@Resourceprivate SpringHelper springHelper;/*** jar的地址*/@Value("${targetUrl}")private String targetUrl;/*** 插件类全路径*/@Value("${pluginClass}")private String pluginClass;@GetMapping("/test")public String test() {return IPlugin.customPluginMethod("test plugin");}/*** 运行时注册bean* 此方法用于在应用程序运行时动态加载并注册一个bean,* 然后根据这个bean执行相应操作或返回其信息*/@GetMapping("/reload")public Object reload() throws ClassNotFoundException {// 使用自定义类加载器获取指定URL的类加载器ClassLoader classLoader = ClassLoaderHelper.getClassLoader(targetUrl);// 通过类加载器加载指定名称的类Class<?> clazz = classLoader.loadClass(pluginClass);// 在Spring上下文中注册这个类作为一个beanspringHelper.registerBean(clazz.getName(), clazz);// 从Spring上下文中获取新注册的beanObject bean = springHelper.getBean(clazz.getName());// 检查bean是否实现了PluginInterface接口if (bean instanceof IPlugin) {// 如果实现了,将此接口赋值给当前的pluginInterface,并调用其sayHello方法IPlugin plugin = (IPlugin) bean;this.IPlugin = plugin;return plugin.customPluginMethod("test reload");} else {// 如果没有实现,获取并记录该bean实现的第一个接口的名称,并返回bean的字符串表示log.info(bean.getClass().getInterfaces()[0].getName());return bean.toString();}}/*** 移除bean* 该方法用于从Spring应用上下文中移除指定的bean* 它首先通过ClassLoader加载指定的类,然后使用springUtil工具类移除对应的bean* 最后,它尝试获取并打印被移除的bean的信息,如果bean仍然存在的话** @return 返回被移除bean的类名* @throws ClassNotFoundException 如果指定的类不存在,则抛出此异常*/@GetMapping("/remove")public Object remove() throws ClassNotFoundException {// 获取目标URL对应的ClassLoaderClassLoader classLoader = ClassLoaderHelper.getClassLoader(targetUrl);// 通过ClassLoader加载插件类Class<?> clazz = classLoader.loadClass(pluginClass);// 使用springUtil工具类移除加载的插件类beanspringHelper.removeBean(clazz.getName());// 清空pluginInterface引用,表示插件接口已被移除this.IPlugin = null;// 尝试获取已被移除的插件类bean// Object bean = springHelper.getBean(clazz.getName());// 如果bean不为空,打印bean的信息//if (bean != null) {//    log.info(bean.toString());// }// 返回被移除bean的类名return clazz.getName() + " removed";}
}

配置文件

在 application.properties 中配置插件路径和插件类的全路径

spring.main.allow-bean-definition-overriding=truetargetUrl=file:/D:/plugin-extends/plugin-impl-0.0.1-SNAPSHOT-jar-with-dependencies.jar
pluginClass=com.plugin.impl.MyPluginImpl

启动类

我们在Spring Boot启动类中使用 @Import 注解加载插件配置

package com.plugin;import com.plugin.config.PluginImportBeanDefinitionRegistrar;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;@SpringBootApplication
@Import(PluginImportBeanDefinitionRegistrar.class)
public class SpringBootPluginTestApplication {public static void main(String[] args) {SpringApplication.run(SpringBootPluginTestApplication.class, args);}
}

测试验证

在这里插入图片描述

启动时动态加载jar:http://127.0.0.1:8080/test
运行时动态加载jar:http://127.0.0.1:8080/reload
运行时动态卸载jar: http://127.0.0.1:8080/remove


小结

通过自定义类加载器、ImportBeanDefinitionRegistrar 和动态Bean管理,我们能够在Spring Boot应用中实现灵活的插件机制。这样的插件化架构不仅能够提升系统的可扩展性,还能有效地支持插件的动态加载和卸载,为系统提供更好的功能扩展能力。

在这里插入图片描述

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

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

相关文章

ECharts柱状图-阶梯瀑布图,附视频讲解与代码下载

引言&#xff1a; 在数据可视化的世界里&#xff0c;ECharts凭借其丰富的图表类型和强大的配置能力&#xff0c;成为了众多开发者的首选。今天&#xff0c;我将带大家一起实现一个柱状图图表&#xff0c;通过该图表我们可以直观地展示和分析数据。此外&#xff0c;我还将提供…

【Hash Function and HashMap】

散列函数&#xff08;Hash Function&#xff09;是一种将任意大小的数据映射到固定大小值的函数。在 HashMap 中&#xff0c;它扮演着核心角色。让我详细解释&#xff1a; 散列函数基本原理 输入&#xff1a;任意类型的键&#xff08;key&#xff09;输出&#xff1a;固定大小…

【jvm】为什么要有GC

目录 1. 自动内存管理2. 提升程序稳定性3. 优化性能4. 跨平台能力5. 分代回收策略 1. 自动内存管理 1.JVM中的GC机制负责自动管理内存&#xff0c;这意味着开发人员不需要手动分配和释放内存。2.这一特性大大简化了Java程序的内存管理&#xff0c;降低了内存泄漏和内存溢出等问…

Python泛型编程:TypeVar和Generic详解 - 写给初学者的指南

Python泛型编程&#xff1a;TypeVar和Generic详解 - 写给初学者的指南 前言1. 为什么需要泛型&#xff1f;2. TypeVar&#xff1a;定义泛型类型变量3. Generic&#xff1a;创建泛型类4. 多个泛型类型变量5. 使用场景小结结语 前言 大家好&#xff01;今天我们来聊一聊Python中…

COUNT(*)、COUNT(1)、COUNT(某一列)的区别是什么?哪个性能更好

一些特殊情况&#xff1a; 有索引时&#xff1a;如果查询使用了索引&#xff0c;且查询的列在索引中&#xff0c;COUNT(某一列) 可能在某些情况下会比较快&#xff0c;因为数据库只需要扫描索引&#xff0c;而不需要扫描整个表。有 NULL 值时&#xff1a;COUNT(某一列) 可能会…

C/C++流星雨

系列文章 序号直达链接1C/C爱心代码2C/C跳动的爱心3C/C李峋同款跳动的爱心代码4C/C满屏飘字表白代码5C/C大雪纷飞代码6C/C烟花代码7C/C黑客帝国同款字母雨8C/C樱花树代码9C/C奥特曼代码10C/C精美圣诞树11C/C俄罗斯方块12C/C贪吃蛇13C/C孤单又灿烂的神-鬼怪14C/C闪烁的爱心15C/C…

【机器学习】——K均值聚类:揭开数据背后的隐藏结构

目录 引言&#xff1a;什么是聚类分析&#xff1f;K均值聚类的基本原理 2.1 聚类的概念2.2 K均值聚类简介 K均值算法的工作原理 3.1 初始化与选定K值3.2 计算距离与分配簇3.3 更新质心3.4 迭代与收敛 K均值聚类的优缺点 4.1 优点4.2 缺点与局限性 K均值聚类的常见应用 5.1 市场…

【WRF-Urban】SLUCM新增空间分布城市冠层参数及人为热排放AHF代码详解(下)

目录 详细解释更改文件内容4 运行模块(run):README.namelist5 输出模块(share):share/module_check_a_mundo.Fshare/output_wrf.F参考SLUCM新增空间分布城市冠层参数及人为热排放AHF代码详解的前两部分内容可参见-【WRF-Urban】SLUCM新增空间分布城市冠层参数及人为热排放A…

go 集成nacos注册中心、配置中心

使用限制 Go>v1.15 Nacos>2.x 安装 使用go get安装SDK&#xff1a; go get -u github.com/nacos-group/nacos-sdk-go/v2 快速使用 初始化客户端配置ClientConfig constant.ClientConfig{TimeoutMs uint64 // 请求Nacos服务端的超时时间&#xff0c;默…

ModelScope-Agent(1): 基于开源大语言模型的可定制Agent系统

目录 简介快速入门 简介 github地址 快速入门 看前两篇&#xff0c;调用千问API和天气API # 选用RolePlay 配置agent from modelscope_agent.agents.role_play import RolePlay # NOQArole_template 你扮演一个天气预报助手&#xff0c;你需要查询相应地区的天气&#x…

终端中运行 conda install 命令后一直显示“Solving environment: \ ”

初步接触深度学习&#xff0c;在配置环境方面出了点问题&#xff0c;运行 conda install 命令时&#xff0c;卡在 "Solving environment: \ "。 网上搜索发现&#xff0c; 一般可能的原因就是以下几种 环境解析耗时&#xff1a; Conda 在安装包时需要解析当前环境&…

Jenkins相关的Api接口调用详解

Jenkins API是Jenkins持续集成和持续部署(CI/CD)平台提供的一组接口,允许外部程序通过HTTP请求与Jenkins进行交互。以下是对Jenkins API使用的简介: 一、Jenkins API的主要功能 作业管理:通过API,可以创建、配置、删除以及查询作业(Job)。构建触发:可以远程触发新的构…

【模型对比】ChatGPT vs Kimi vs 文心一言那个更好用?数据详细解析,找出最适合你的AI辅助工具!

在这个人工智能迅猛发展的时代&#xff0c;AI聊天助手已经深入我们的工作与生活。你是否曾在选择使用ChatGPT、Kimi或是百度的文心一言时感到一头雾水&#xff1f;每款AI都有其独特的魅力与优势&#xff0c;那么&#xff0c;究竟哪一款AI聊天助手最适合你呢&#xff1f;本文将带…

react 和 react-dom 是什么关系

React和React DOM是两个与React生态系统密切相关的npm包&#xff0c;它们在构建用户界面时扮演不同的角色&#xff0c;但相互之间存在紧密的依赖关系。以下是React和React DOM关系的详细解释&#xff1a; React的作用 React是一个用于构建用户界面的JavaScript库。它提供了构建…

微信小程序uni-app+vue3实现局部上下拉刷新和scroll-view动态高度计算

微信小程序uni-appvue3实现局部上下拉刷新和scroll-view动态高度计算 前言 在uni-appvue3项目开发中,经常需要实现列表的局部上下拉刷新功能。由于网上相关教程较少且比较零散,本文将详细介绍如何使用scroll-view组件实现这一功能,包括动态高度计算、下拉刷新、上拉加载等完整…

Netty面试内容整理-常见问题排查与调试

在使用 Netty 进行开发时,排查和调试常见问题是确保系统稳定运行的关键部分。以下是一些 Netty 中常见的问题排查和调试的方法,以及对应的解决思路: 内存泄漏问题 问题描述:Netty 内存泄漏可能发生在 ByteBuf 没有被正确释放的情况下,导致内存逐渐被耗尽。 排查方法:Reso…

SQL——DQL分组聚合

分组聚合&#xff1a; 格式&#xff1a; select 聚合函数1(聚合的列),聚合函数2(聚合的列) from 表名 group by 标识列; ###若想方便分辨聚合后数据可在聚合函数前加上标识列&#xff08;以标识列进行分组&#xff09; 常见的聚合函数: sum(列名):求和函数 avg(列名)…

maven打包时出现找不到符号的错误如何解决

在maven打包的时候有时会出现找不到符号的情况&#xff0c;具体原因是由于引用的BaseEntity是framework模块下的实体类&#xff0c;所以需要将framework重新clean再install&#xff0c;成功后再将我们的模块打包就成功了

NumPy和Pandas之间直接相互转换

NumPy数组转换为Pandas DataFrame 要将NumPy数组转换为Pandas DataFrame&#xff0c;可以使用Pandas的DataFrame类的构造函数。这个过程很简单&#xff0c;只需要将NumPy数组作为参数传递给DataFrame构造函数即可。此外&#xff0c;还可以指定列名&#xff0c;以便在DataFrame…

openGauss开源数据库实战二十一

文章目录 任务二十一 使用JDBC访问openGauss数据库任务目标实施步骤一、准备工作 二、下载并安装JavaSE81 下载JavaSE8安装Java8SE并配置环境变量 三、下载并安装eclipse四、下载并安装openGauss的JDBC驱动包五、使用IDEA编写JDBC测试程序1 使用IDEA的SSH连接虚拟机2 创建项目并…