求求你别再手动部署jar包了,太low了!动态上传热部署真的太爽了!
近期开发系统过程中遇到的一个需求系统给定一个接口用户可以自定义开发该接口的实现并将实现打成jar包上传到系统中。系统完成热部署并切换该接口的实现。定义简单的接口这里以一个简单的计算器功能为例接口定义比较简单直接上代码。public interface Calculator { int calculate(int a, int b); int add(int a, int b); }该接口的一个简单的实现考虑到用户实现接口的两种方式使用spring上下文管理的方式或者不依赖spring管理的方式这里称它们为注解方式和反射方式。calculate方法对应注解方式add方法对应反射方式。计算器接口实现类的代码如下Service publicclass CalculatorImpl implements Calculator { Autowired CalculatorCore calculatorCore; /** * 注解方式 */ Override public int calculate(int a, int b) { int c calculatorCore.add(a, b); return c; } /** * 反射方式 */ Override public int add(int a, int b) { returnnew CalculatorCore().add(a, b); } }这里注入CalculatorCore的目的是为了验证在注解模式下系统可以完整的构造出bean的依赖体系并注册到当前spring容器中。CalculatorCore的代码如下Service public class CalculatorCore { public int add(int a, int b) { return ab; } }反射方式热部署用户把jar包上传到系统的指定目录下这里定义上传jar文件路径为jarAddressjar的Url路径为jarPath。private static String jarAddress E:/zzq/IDEA_WS/CalculatorTest/lib/Calculator.jar; private static String jarPath file:/ jarAddress;并且可以要求用户填写jar包中接口实现类的完整类名。接下来系统要把上传的jar包加载到当前线程的类加载器中然后通过完整类名加载得到该实现的Class对象。然后反射调用即可完整代码/** * 热加载Calculator接口的实现 反射方式 */ public static void hotDeployWithReflect() throws Exception { URLClassLoader urlClassLoader new URLClassLoader(new URL[]{new URL(jarPath)}, Thread.currentThread().getContextClassLoader()); Class clazz urlClassLoader.loadClass(com.nci.cetc15.calculator.impl.CalculatorImpl); Calculator calculator (Calculator) clazz.newInstance(); int result calculator.add(1, 2); System.out.println(result); }注解方式热部署如果用户上传的jar包含了spring的上下文那么就需要扫描jar包里的所有需要注入spring容器的bean注册到当前系统的spring容器中。其实这就是一个类的热加载动态注册的过程。直接上代码/** * 加入jar包后 动态注册bean到spring容器包括bean的依赖 */ public static void hotDeployWithSpring() throws Exception { SetString classNameSet DeployUtils.readJarFile(jarAddress); URLClassLoader urlClassLoader new URLClassLoader(new URL[]{new URL(jarPath)}, Thread.currentThread().getContextClassLoader()); for (String className : classNameSet) { Class clazz urlClassLoader.loadClass(className); if (DeployUtils.isSpringBeanClass(clazz)) { BeanDefinitionBuilder beanDefinitionBuilder BeanDefinitionBuilder.genericBeanDefinition(clazz); defaultListableBeanFactory.registerBeanDefinition(DeployUtils.transformName(className), beanDefinitionBuilder.getBeanDefinition()); } } }在这个过程中将jar加载到当前线程类加载器的过程和之前反射方式是一样的。然后扫描jar包下所有的类文件获取到完整类名并使用当前线程类加载器加载出该类名对应的class对象。判断该class对象是否带有spring的注解如果包含则将该对象注册到系统的spring容器中。DeployUtils包含读取jar包所有类文件的方法、判断class对象是否包含sping注解的方法、获取注册对象对象名的方法。代码如下/** * 读取jar包中所有类文件 */ public static SetString readJarFile(String jarAddress) throws IOException { SetString classNameSet new HashSet(); JarFile jarFile new JarFile(jarAddress); EnumerationJarEntry entries jarFile.entries();//遍历整个jar文件 while (entries.hasMoreElements()) { JarEntry jarEntry entries.nextElement(); String name jarEntry.getName(); if (name.endsWith(.class)) { String className name.replace(.class, ).replaceAll(/, .); classNameSet.add(className); } } return classNameSet; }/** * 方法描述 判断class对象是否带有spring的注解 */ public static boolean isSpringBeanClass(Class? cla) { if (cla null) { returnfalse; } //是否是接口 if (cla.isInterface()) { returnfalse; } //是否是抽象类 if (Modifier.isAbstract(cla.getModifiers())) { returnfalse; } if (cla.getAnnotation(Component.class) ! null) { returntrue; } if (cla.getAnnotation(Repository.class) ! null) { returntrue; } if (cla.getAnnotation(Service.class) ! null) { returntrue; } returnfalse; }/** * 类名首字母小写 作为spring容器beanMap的key */ public static String transformName(String className) { String tmpstr className.substring(className.lastIndexOf(.) 1); return tmpstr.substring(0, 1).toLowerCase() tmpstr.substring(1); }删除jar时需要同时删除spring容器中注册的bean在jar包切换或删除时需要将之前注册到spring容器的bean删除。spring容器的bean的删除操作和注册操作是相逆的过程这里要注意使用同一个spring上下文。代码如下/** * 删除jar包时 需要在spring容器删除注入 */ public static void delete() throws Exception { SetString classNameSet DeployUtils.readJarFile(jarAddress); URLClassLoader urlClassLoader new URLClassLoader(new URL[]{new URL(jarPath)}, Thread.currentThread().getContextClassLoader()); for (String className : classNameSet) { Class clazz urlClassLoader.loadClass(className); if (DeployUtils.isSpringBeanClass(clazz)) { defaultListableBeanFactory.removeBeanDefinition(DeployUtils.transformName(className)); } } }测试测试类手动模拟用户上传jar的功能。测试函数写了个死循环一开始没有找到jar会抛出异常捕获该异常并睡眠10秒。这时候可以把jar手动放到指定的目录下。代码如下ApplicationContext applicationContext new ClassPathXmlApplicationContext(applicationContext.xml); DefaultListableBeanFactory defaultListableBeanFactory (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory(); while (true) { try { hotDeployWithReflect(); // hotDeployWithSpring(); // delete(); } catch (Exception e) { e.printStackTrace(); Thread.sleep(1000 * 10); } }

相关新闻

SpringBoot在一次http请求中耗费了多少内存?

SpringBoot在一次http请求中耗费了多少内存?

2026/7/10 3:58:10 阅读更多 →
【QML 入门到实战】从零开始搭建第一个 QML 界面(附完整代码)

【QML 入门到实战】从零开始搭建第一个 QML 界面(附完整代码)

2026/7/10 14:00:31 阅读更多 →
CVE-2026-24617:Easy Modal 插件中的存储型XSS漏洞技术分析

CVE-2026-24617:Easy Modal 插件中的存储型XSS漏洞技术分析

2026/7/7 16:21:59 阅读更多 →

最新新闻

GEO企业服务技术评估与选择指南

GEO企业服务技术评估与选择指南

行业现状与需求背景随着生成式AI搜索(豆包、文心一言、DeepSeek、Kimi等)在企业级采购与本地服务信息获取中的渗透率持续攀升,传统搜索引擎优化(SEO)已无法完全覆盖新一代用户的触达路径。据QuestMobile 2024年调研显示…

2026/7/11 7:27:10 阅读更多 →
OpenAI硬件战略解析:AI开发者的技术转型与实战应对

OpenAI硬件战略解析:AI开发者的技术转型与实战应对

最近科技圈有个很有意思的现象:当大家都在关注AI大模型的技术竞赛时,OpenAI却在硬件领域悄悄布局。这不禁让人思考:一家以软件算法见长的公司,为什么要涉足硬件?这背后到底隐藏着什么样的战略意图?更关键的…

2026/7/11 7:27:10 阅读更多 →
JavaScript 类型转换实战:3 种 `valueOf` 与 `toString` 优先级测试

JavaScript 类型转换实战:3 种 `valueOf` 与 `toString` 优先级测试

JavaScript 类型转换实战:3 种valueOf与toString优先级测试1. 对象到原始值转换的核心机制当 JavaScript 需要将对象转换为原始值时,会触发内部的ToPrimitive抽象操作。这个操作会根据上下文提供的 "hint"(提示)来决定转…

2026/7/11 7:25:09 阅读更多 →
为什么你的提示词总被降权?揭秘Midjourney 6.1新算法对关键词位置、标点、语言的隐性惩罚机制

为什么你的提示词总被降权?揭秘Midjourney 6.1新算法对关键词位置、标点、语言的隐性惩罚机制

更多请点击: https://kaifayun.com 第一章:Midjourney提示词工程的范式迁移 传统图像生成提示词设计强调“描述性堆砌”——罗列对象、风格、光照等静态属性。而当前Midjourney v6 的理解机制已深度耦合语义解析与上下文推理能力,促使提示词…

2026/7/11 7:23:09 阅读更多 →
C++/Qt 工业协议中的数据类型的选择原则和典型用法。

C++/Qt 工业协议中的数据类型的选择原则和典型用法。

一、类型对比总结类型符号大小本质在工业协议中的角色char由实现定义(通常有符号)1 字节C 内建字符类型不推荐用于字节操作,(范围 –128 ~ 127)存 ≥128 的值会溢出/符号扩展unsigned char无符号1 字节C 内建无符号字节…

2026/7/11 7:21:08 阅读更多 →
STM32F303RC与CMT-8540S-SMT音频开发实战指南

STM32F303RC与CMT-8540S-SMT音频开发实战指南

1. 为什么选择STM32F303RC与CMT-8540S-SMT组合在嵌入式音频开发领域,硬件选型往往决定了项目的上限。STM32F303RC作为STMicroelectronics推出的Cortex-M4内核微控制器,其72MHz主频和硬件FPU单元为实时音频处理提供了坚实基础。而CMT-8540S-SMT这款表面贴…

2026/7/11 7:19:08 阅读更多 →

日新闻

5分钟完全掌握:ComfyUI ControlNet预处理器终极使用指南

5分钟完全掌握:ComfyUI ControlNet预处理器终极使用指南

5分钟完全掌握:ComfyUI ControlNet预处理器终极使用指南 【免费下载链接】comfyui_controlnet_aux ComfyUIs ControlNet Auxiliary Preprocessors 项目地址: https://gitcode.com/gh_mirrors/co/comfyui_controlnet_aux 想要让AI图像生成真正听从你的指挥吗&…

2026/7/11 0:02:12 阅读更多 →
PIC18F45K42驱动EPT-14A4005P压电蜂鸣器方案详解

PIC18F45K42驱动EPT-14A4005P压电蜂鸣器方案详解

1. 项目背景与核心需求 在工业控制、安防系统和智能家居等领域,可靠的声音警报系统是不可或缺的基础组件。传统蜂鸣器存在音量不足、音质模糊等问题,而基于压电陶瓷技术的EPT-14A4005P蜂鸣器配合PIC18F45K42微控制器,能够构建一套适应性强、音…

2026/7/11 0:04:12 阅读更多 →
大模型推理中的算子融合优化:LayerNorm + Attention 的 CUDA Kernel 手写与验证

大模型推理中的算子融合优化:LayerNorm + Attention 的 CUDA Kernel 手写与验证

大模型推理中的算子融合优化:LayerNorm Attention 的 CUDA Kernel 手写与验证 一、GPU 利用率 30%:分开的算子吃掉所有带宽 推理服务的 GPU 利用率监测显示一个反直觉的现象:计算核心(SM)利用率不到 30%,但…

2026/7/11 0:04:12 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/10 19:03:29 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/9 13:46:46 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/9 21:41:05 阅读更多 →

月新闻