热更新的class到虚拟机

本文详细介绍了Java类加载器的工作原理,包括系统提供的类加载器种类及其职责,并深入探讨了双亲委派模型。此外,还提供了如何通过自定义类加载器实现热更新的具体步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

需求

有个新需求,希望能热更新的class到虚拟机中

于是需要写一个新Class并且热更到虚拟机中
JAVA类加载器

简单介绍一下系统提供的类加载器

1)Bootstrap ClassLoader

负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

2)Extension ClassLoader

负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包

3)App ClassLoader

负责记载classpath中指定的jar包及目录中class

4)Custom ClassLoader

属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader
双亲委派

JAVA的类加载遵循双亲委派模型,简单说,加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

当然有几种不遵循双亲委派模型的,在此不赘述
编码过程
1.单纯的实现加载一个类


URLClassLoader ucl = new URLClassLoader(new URL[]{fileNew.toURI().toURL()});
Class<?> clz = ucl.loadClass("com.Test");
Object o = clz.newInstance();

在这段代码下面,可以继续写你的代码,调用这个类,没有问题

但是我们想实现的不是这样,是想让这个类在虚拟机中任何一段代码都可见

可以调用AppClassLoader
2. 调用AppClassLoader 加载Class

我们只需要将Test.class拷贝到ClassPath中的一个目录就行,包括包,比如lib目录


ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class<?> clz = ucl.loadClass("com.Test");

这样,这个类就加入到了虚拟机中,可以任意调用
3. 调用AppClassLoader 加载JAR,动态添加新JAR包

首先,我们需要调用URLClassLoader的addURL方法添加新的JAR包路径,但是这个方法是私有的,没关系,反射


URLClassLoader appClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Method add = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
add.setAccessible(true);
add.invoke(appClassLoader, new Object[] { url });

这样,我们就可以直接热更新的JAR包里新的类到代码里

还有个问题,就是如果我第一次加载了A.jar(包括Test.class), 我第二次又想加载A.jar(包括Test1.class)

这时候就有问题了,报错找不到Test1.class,这是因为ClassLoader已经缓存了A.jar

怎么办,继续反射清理

URLClassLoader appClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();

Field ucpField = URLClassLoader.class.getDeclaredField("ucp");
ucpField.setAccessible(true);
URLClassPath ucp = (URLClassPath) ucpField.get(appClassLoader);

Field pathField = URLClassPath.class.getDeclaredField("path");
pathField.setAccessible(true);
Field urlsField = URLClassPath.class.getDeclaredField("urls");
urlsField.setAccessible(true);
ArrayList<URL> path = (ArrayList<URL>) pathField.get(ucp);
Stack<URL> urls = (Stack<URL>) urlsField.get(ucp);
path.remove(url);
urls.remove(url);

Field loadersField = URLClassPath.class.getDeclaredField("loaders");
loadersField.setAccessible(true);
Field lmapField = URLClassPath.class.getDeclaredField("lmap");
lmapField.setAccessible(true);

ArrayList loaders = (ArrayList) loadersField.get(ucp);
Map lmap = (Map) lmapField.get(ucp);
String urlNoFragString = URLUtil.urlNoFragString(url);
Object o = lmap.remove(urlNoFragString);
if (o != null) {
loaders.remove(o);
}

JAVA是否能反射内部类

我们是否能在代码里反射内部类呢,答案是可以的,代码里连类都不能调用,那怎么反射啊 这样反射


Field field =ClassLoader.getSystemClassLoader().loadClass("com.Test$Inner").getDeclaredField("ucp");
或者
Field field = Class.forName("com.Test$Inner").getDeclaredField("ucp");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值