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

【工作小札】自定义classloader实现热加载jar

文章目录

  • 楔子
  • 第一步:添加maven依赖
  • 第二步:创建jar包路径构造类
  • 第三步:定义需要被加载的jar的目录结构
  • 第四步:创建自定义类加载器
    • 1 继承ClassLoader并实现Closeable接口
    • 2 标记该加载器支持并行类加载机制
    • 3 私有化构造方法,避免该类被new出来
    • 4 添加一些属性
    • 5 单例模式获取对象
    • 6 创建静态内部内-自定义jar
    • 7 编写加载扩展jar的核心方法
    • 8 编写main方法
    • 9 启动main方法
    • 10 将测试jar包放入指定目录
  • 完整代码

✨这里是第七人格的博客✨小七,欢迎您的到来~✨

🍅系列专栏:【工作小札】🍅

✈️本篇内容: 自定义classloader实现热加载jar✈️

🍱本篇收录完整代码地址:https://gitee.com/diqirenge/sheep-web-demo🍱

楔子

小七最近收到一个需求,需要加载符合条件的jar到正在运行的系统中。因为对热部署那一套,小七以前有过简单的调研,所以首先想到了Osgi、Sofa-Ark等框架,但是仅仅只是想简单的热加载一个jar,引入这种重量级的框架,实属是杀鸡用牛刀,于是小七思考是不是可以写一个自己的类加载器来实现这一个功能。

第一步:添加maven依赖

<dependencies><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.1-jre</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.7</version></dependency>
</dependencies>

第二步:创建jar包路径构造类

主要逻辑如下:

1、申明默认jar包路径

2、获取路径时,如果有指定路径那么使用指定的路径,如果没有指定路径,那么使用默认的路径

public final class JarPathBuilder {/*** 默认ext插件路径* 可以暴露出去,做到参数控制*/private static final String DEFAULT_EXT_PLUGIN_PATH = "/ext-lib/";/*** 得到jar路径** @param path 路径* @return {@link File}*/public static File getJarPath(final String path) {if (StringUtils.isNotEmpty(path)) {System.out.println("开始加载【" + path + "】路径下的jar包");return new File(path);}System.out.println("开始加载【ext-lib】路径下的jar包");return buildJarPath();}/*** 构建jar路径** @return {@link File}*/private static File buildJarPath() {URL url = JarPathBuilder.class.getResource(DEFAULT_EXT_PLUGIN_PATH);return Optional.ofNullable(url).map(u -> new File(u.getFile())).orElse(new File(DEFAULT_EXT_PLUGIN_PATH));}}

第三步:定义需要被加载的jar的目录结构

我们这里定义,需要加载的jar的结构和maven打包出来的jar一致。

我们编写一个测试jar如下:
在这里插入图片描述

完整代码地址:https://gitee.com/diqirenge/sheep-web-demo/tree/master/sheep-web-demo-custom-classloader-jar

执行package命令获取jar包:sheep-web-demo-custom-classloader-jar-1.0-SNAPSHOT.jar
在这里插入图片描述

第四步:创建自定义类加载器

1 继承ClassLoader并实现Closeable接口

public final class CustomLoader extends ClassLoader implements Closeable{}

2 标记该加载器支持并行类加载机制

static {registerAsParallelCapable();
}

注:
类加载器在类初始化时,通过调用 ClassLoader.registerAsParallelCapable 来标记该加载器支持并行类加载机制。

支持该机制的加载器称之为 可并行 的类加载器。需要注意的是,ClassLoader类是默认可并行加载的,但它的子类仍须通过注册接口调用来支持可并行机制,也就是说,可并行机制不可继承。

在委托结构设计不是很有层次性(如出现闭环委托)的情况下,这些类加载器需要实现并行机制,否则会出现死锁问题。具体可以参考loadClass的函数源码。

3 私有化构造方法,避免该类被new出来

private CustomLoader() {super(CustomLoader.class.getClassLoader());
}

4 添加一些属性

/*** 自定义加载程序*/
private static volatile CustomLoader customLoader;/*** 对象缓存池*/
private final ConcurrentHashMap<String, Object> objectPool = new ConcurrentHashMap<>();/*** 锁*/
private final ReentrantLock lock = new ReentrantLock();/*** jar包*/
private final List<CustomJar> jars = Lists.newArrayList();

5 单例模式获取对象

/*** 双重检索,获得实例** @return {@link CustomLoader}*/
public static CustomLoader getInstance() {if (null == customLoader) {synchronized (CustomLoader.class) {if (null == customLoader) {customLoader = new CustomLoader();}}}return customLoader;
}

6 创建静态内部内-自定义jar

/*** 自定义jar** @author lizongyang* @date 2023/03/03*/
private static class CustomJar {/*** jar文件*/private final JarFile jarFile;/*** 源路径*/private final File sourcePath;CustomJar(final JarFile jarFile, final File sourcePath) {this.jarFile = jarFile;this.sourcePath = sourcePath;}
}

7 编写加载扩展jar的核心方法

/*** 加载扩展jar** @param path 路径* @return {@link List}<{@link Object}>* @throws IOException            io异常* @throws ClassNotFoundException 类没有发现异常* @throws InstantiationException 实例化异常* @throws IllegalAccessException 非法访问异常*/
public List<Object> loadExtendJar(final String path) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {File[] jarFiles = JarPathBuilder.getJarPath(path).listFiles(file -> file.getName().endsWith(".jar"));if (null == jarFiles) {return Collections.emptyList();}List<Object> results = new ArrayList<>();try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {for (File each : Objects.requireNonNull(jarFiles)) {outputStream.reset();JarFile jar = new JarFile(each, true);jars.add(new CustomJar(jar, each));Enumeration<JarEntry> entries = jar.entries();while (entries.hasMoreElements()) {JarEntry jarEntry = entries.nextElement();String entryName = jarEntry.getName();if (entryName.endsWith(".class") && !entryName.contains("$")) {String className = entryName.substring(0, entryName.length() - 6).replaceAll("/", ".");Object instance = getOrCreateInstance(className);if (Objects.nonNull(instance)) {results.add(instance);}}}}}return results;
}
/*** 获取或创建实例** @param className 类名* @return {@link T}* @throws ClassNotFoundException 类没有发现异常* @throws IllegalAccessException 非法访问异常* @throws InstantiationException 实例化异常*/
@SuppressWarnings("unchecked")
private <T> T getOrCreateInstance(final String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {if (objectPool.containsKey(className)) {System.out.println("从缓存中获取的className为【" + className + "】");return (T) objectPool.get(className);}lock.lock();try {System.out.println("开始创建className为【" + className + "】的实例");Object inst = objectPool.get(className);if (Objects.isNull(inst)) {Class<?> clazz = Class.forName(className, true, this);inst = clazz.newInstance();objectPool.put(className, inst);}System.out.println("创建className为【" + className + "】的实例结束");return (T) inst;} finally {lock.unlock();}
}

8 编写main方法

public class CustomLoaderAction {public static void main(String[] args) {System.out.println("=======>主线程启动<=======");ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("loader-pool-%d").build();ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, namedThreadFactory);executor.scheduleAtFixedRate(() -> {Date now = new Date();System.out.println();System.out.println(now + "=======>定时任务开始执行<=======");try {List<Object> objects = CustomLoader.getInstance().loadExtendJar("");Object o = objects.get(0);Method say = o.getClass().getMethod("say", String.class);say.invoke(o, " 第七人格");} catch (Exception e) {e.printStackTrace();}System.out.println(now + "=======>定时任务结束<=======");}, 3, 30, TimeUnit.SECONDS);while (true) {// 保持主线程不断}}
}

9 启动main方法

因为当前指定目录下没有jar包,所以系统报错
在这里插入图片描述

10 将测试jar包放入指定目录

在这里插入图片描述

输出结果:
在这里插入图片描述

说明热加载jar成功

完整代码

待加载的jar

https://gitee.com/diqirenge/sheep-web-demo/tree/master/sheep-web-demo-custom-classloader-jar

自定义加载器

https://gitee.com/diqirenge/sheep-web-demo/tree/master/sheep-web-demo-custom-classloader

相关文章:

【工作小札】自定义classloader实现热加载jar

文章目录楔子第一步&#xff1a;添加maven依赖第二步&#xff1a;创建jar包路径构造类第三步&#xff1a;定义需要被加载的jar的目录结构第四步&#xff1a;创建自定义类加载器1 继承ClassLoader并实现Closeable接口2 标记该加载器支持并行类加载机制3 私有化构造方法&#xff…...

spring—AOP

系列文章目录 Spring中AOP技术的学习 文章目录系列文章目录前言一、AOP核心概念二、AOP入门案例1.AOP入门案例思路分析2.AOP入门案例实现三、AOP工作流程四、AOP切入点表达式五、AOP通知类型六、案例&#xff1a;测量业务层接口万次执行效率1.项目结构2.实现类七、AOP获取通知…...

自己曾经的C++笔记【在c盘爆满的时候找到的回忆】

文章目录**C与C的区别** (二&#xff09;类和对象构造函数和析构函数C特殊成员C友元C类的继承C虚函数和多态C模板C可变参模板CSTL容器篇C迭代器C仿函数C函数适配器CSTL算法C智能指针C类型推断CIO流C正则表达式具有特殊意义的元字符量词元字符校验数字的表达式校验字符的表达式特…...

Nginx 实战-负载均衡

一、负载均衡今天学习一下Nginx的负载均衡。由于传统软件建构的局限性&#xff0c;加上一台服务器处理能里的有限性&#xff0c;在如今高并发、业务复杂的场景下很难达到咱们的要求。但是若将很多台这样的服务器通过某种方式组成一个整体&#xff0c;并且将所有的请求平均的分配…...

本周大新闻|128GB版Quest 2再降价,Mojo Vision完成“新A轮”融资

本周XR大新闻&#xff0c;AR方面&#xff0c;DigiLens推出SRG表面浮雕光栅衍射光波导&#xff1b;索尼成立Sony Research&#xff1b;NuEyes推出牙医场景AR眼镜NuLoupes&#xff1b;苹果EMG手环、AR/VR眼球追踪专利公布。 VR方面&#xff0c;128GB版Quest 2降至349美元&#x…...

【论文阅读】如何给模型加入先验知识

如何给模型加入先验知识 1. 基于pretain模型给模型加入先验 把预训练模型的参数导入模型中&#xff0c;这些预训练模型在另一个任务中已经p retrain好了模型的weight,往往具备了一些基本图片的能力 2. 基于输入给模型加入先验 比如说鸟类的头部是一个重要的区分部分&#x…...

arm系列交叉编译器各版本区别

目录交叉编译器命名规则具体编译器举例crosstool-ng交叉编译工具样本arm交叉编译器举例几个概念ABI与EABIgnueabi与gnueabihf参考交叉编译器命名规则 交叉编译器的命名规则&#xff1a;arch [-vendor] [-os] [-(gnu)eabi] [-language] arch - 体系架构&#xff0c; 如arm&…...

随笔记录工作日志

工作中遇到的问题随笔记录 1、将map集合中的key/value数据按照一定的需求过滤出来&#xff0c;并将过滤出来的map的key值存到list集合中 首先想到的是stream流&#xff0c;但是我对stream流的用法基本不熟&#xff0c;记不住方法&#xff0c;如果坚持用stream流去实现这个需求…...

LinkedHashMap源码分析以及LRU的应用

LinkedHashMap源码分析以及LRU的应用 LinkedHashMap简介 LinkedHashMap我们都知道是在HashMap的基础上&#xff0c;保证了元素添加时的顺序&#xff1b;除此之外&#xff0c;它还支持LRU可以当做缓存中心使用 源码分析目的 分析保持元素有序性是如何实现的 LRU是如何实现的…...

【每日一题Day166】LC1053交换一次的先前排列 | 贪心

交换一次的先前排列【LC1053】 给你一个正整数数组 arr&#xff08;可能存在重复的元素&#xff09;&#xff0c;请你返回可在 一次交换&#xff08;交换两数字 arr[i] 和 arr[j] 的位置&#xff09;后得到的、按字典序排列小于 arr 的最大排列。 如果无法这么操作&#xff0c;…...

Canal增量数据订阅和消费——原理详解

文章目录 简介工作原理MySQL主备复制原理canal 工作原理Canal-HA机制应用场景同步缓存 Redis /全文搜索 ES下发任务数据异构简介 canal 翻译为管道,主要用途是基于 MySQL 数据库的增量日志 Binlog 解析,提供增量数据订阅和消费。 早期阿里巴巴因为杭州和美国双机房部署,存…...

为什么要使用线程池

Java线程的创建非常昂贵&#xff0c;需要JVM和OS&#xff08;操作系统&#xff09;配合完成大量的工作&#xff1a; (1)必须为线程堆栈分配和初始化大量内存块&#xff0c;其中包含至少1MB的栈内存。 (2)需要进行系统调用&#xff0c;以便在OS&#xff08;操作系统&#xff09;…...

在云服务部署前后端以及上传数据库

1.上传数据库(sql文件) 首先建立一个目录&#xff0c;用于存放要部署的sql文件&#xff0c;然后在此目录中进入mysql 进入后建立一个数据库&#xff0c;create database 数据库名 完成后&#xff0c;通过select * from 表名可以查到数据说明导入成功。 2.部署Maven后端 将Ma…...

Onedrive for Business迁移方案 | 分享一

文章目录 前言 一、Onedrive for Business迁移方案应用范围? 1.准备目标平台 2.导出源平台数据 <...

pt01数据类型、语句选择

python01 pycharm常用快捷键 (1) 移动到本行开头&#xff1a;home键 (2) 移动到本行末尾&#xff1a;end键盘 (3) 注释代码&#xff1a;ctrl / (4) 复制行&#xff1a;ctrl d #光标放行上 (5) 删除行&#xff1a;shift delete (6) 选择列&#xff1a;shift alt 鼠标左键…...

ChatGPT 存在很大的隐私问题

当 OpenAI 发布时 2020 年 7 月的 GPT-3&#xff0c;它提供了用于训练大型语言模型的数据的一瞥。 根据一篇技术论文&#xff0c;从网络、帖子、书籍等中收集的数百万页被用于创建生成文本系统。 在此数据中收集的是您在网上分享的一些关于您自己的个人信息,这些数据现在让 O…...

图的迭代深度优先遍历

图的深度优先遍历(或搜索)类似于树的深度优先遍历(DFS)。这里唯一的问题是,与树不同,图可能包含循环,因此一个节点可能会被访问​​两次。为避免多次处理一个节点,请使用布尔访问数组。 例子: 输入: n = 4, e = 6 0 -> 1, 0 -> 2, 1 -> 2, 2 -> 0, …...

华为OD机试-开放日活动-2022Q4 A卷-Py/Java/JS

某部门开展Family Day开放日活动&#xff0c;其中有个从桶里取球的游戏&#xff0c;游戏规则如下:有N个容量一样的小桶等距排开&#xff0c;且每个小桶都默认装了数量不等的小球&#xff0c; 每个小桶装的小球数量记录在数组 bucketBallNums 中,游戏开始时&#xff0c;要求所有…...

两亲性聚合物:Lauric acid PEG Maleimide,Mal-PEG-Lauric acid,月桂酸PEG马来酰亚胺,试剂知识分享

Lauric acid PEG Maleimide&#xff0c;Lauric acid PEG Mal| 月桂酸PEG马来酰亚胺 | CAS&#xff1a;N/A | 端基取代率&#xff1a;95%一、试剂参数信息&#xff1a; 外观&#xff08;Appearance&#xff09;&#xff1a;灰白色/白色固体或粘性液体取决于分子量 溶解性&am…...

FB使用入口点函数例子

一、DLL的入口点 1.1 VFB的自带DLL模式入口 FB是把代码转成C&#xff08;GCC编译&#xff09;或者汇编&#xff08;GAS编译&#xff09;后编译的&#xff0c;本身就有一个main函数&#xff0c;所以在程序里其实不需要入口点&#xff0c;直接写就可以顺序执行&#xff0c;而有的…...

synchronized 学习

学习源&#xff1a; https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖&#xff0c;也要考虑性能问题&#xff08;场景&#xff09; 2.常见面试问题&#xff1a; sync出…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

多场景 OkHttpClient 管理器 - Android 网络通信解决方案

下面是一个完整的 Android 实现&#xff0c;展示如何创建和管理多个 OkHttpClient 实例&#xff0c;分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

10-Oracle 23 ai Vector Search 概述和参数

一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI&#xff0c;使用客户端或是内部自己搭建集成大模型的终端&#xff0c;加速与大型语言模型&#xff08;LLM&#xff09;的结合&#xff0c;同时使用检索增强生成&#xff08;Retrieval Augmented Generation &#…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

C++:多态机制详解

目录 一. 多态的概念 1.静态多态&#xff08;编译时多态&#xff09; 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1&#xff09;.协变 2&#xff09;.析构函数的重写 5.override 和 final关键字 1&#…...