网站域名空间费用,织梦网站问题,郑州专业做网站的,网站做支付需要什么备案文章目录楔子第一步#xff1a;添加maven依赖第二步#xff1a;创建jar包路径构造类第三步#xff1a;定义需要被加载的jar的目录结构第四步#xff1a;创建自定义类加载器1 继承ClassLoader并实现Closeable接口2 标记该加载器支持并行类加载机制3 私有化构造方法#xff…
文章目录楔子第一步添加maven依赖第二步创建jar包路径构造类第三步定义需要被加载的jar的目录结构第四步创建自定义类加载器1 继承ClassLoader并实现Closeable接口2 标记该加载器支持并行类加载机制3 私有化构造方法避免该类被new出来4 添加一些属性5 单例模式获取对象6 创建静态内部内-自定义jar7 编写加载扩展jar的核心方法8 编写main方法9 启动main方法10 将测试jar包放入指定目录完整代码✨这里是第七人格的博客✨小七欢迎您的到来~✨ 系列专栏:【工作小札】 ✈️本篇内容: 自定义classloader实现热加载jar✈️ 本篇收录完整代码地址https://gitee.com/diqirenge/sheep-web-demo 楔子
小七最近收到一个需求需要加载符合条件的jar到正在运行的系统中。因为对热部署那一套小七以前有过简单的调研所以首先想到了Osgi、Sofa-Ark等框架但是仅仅只是想简单的热加载一个jar引入这种重量级的框架实属是杀鸡用牛刀于是小七思考是不是可以写一个自己的类加载器来实现这一个功能。
第一步添加maven依赖
dependenciesdependencygroupIdcom.google.guava/groupIdartifactIdguava/artifactIdversion30.1-jre/version/dependencydependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactIdversion3.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 ConcurrentHashMapString, Object objectPool new ConcurrentHashMap();/*** 锁*/
private final ReentrantLock lock new ReentrantLock();/*** jar包*/
private final ListCustomJar 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 ListObject 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();}ListObject 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));EnumerationJarEntry 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 {ListObject 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