前言
上期我们说了,自定义类加载器是如何打破双亲委派机制,同时又是如何实现神奇的热部署的效果的。这期我们带来一个简单的demo,话不多说,开始了。
一、实现
1.1、自定义类加载器
在这里,我们通过继承ClassLoader类,覆盖了 ClassLoader
的 loadClass
方法来实现特定的类加载逻辑。
核心作用如下:负责加载 org.example.test
包下的类,并从项目的 target 目录下的相应 .class
文件加载这些类。如果请求加载的类不属于指定的包,则交由父类加载器处理。加载过程中,它使用 FileInputStream
读取类文件,并调用 defineClass
将字节码转换为 Class
对象。最后,确保在读取完成后关闭输入流。这个类加载器适用于需要动态加载特定包内类的场景。
java
package org.example.test;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
/**
* @author MADAO
*/
public class CustomClassLoader extends ClassLoader{
/**
* 这里可以获取项目的target根目录
*/
private final String loadPath = Objects.requireNonNull(CustomClassLoader.class.getResource("/").getPath());
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
//这里要做判断,只有项目下的类可以使用该加载器加载
if(!name.startsWith("org.example.test")) {
return super.loadClass(name);
}
//com.xx.xxx -> com/xx/xxx
final String s = name.replaceAll("\.", "/");
InputStream stream = null;
try {
System.out.println("loadPath: " + loadPath + s + ".class");
stream = new FileInputStream(loadPath + s + ".class");
final int len = stream.available();
final byte[] bytes = new byte[len];
stream.read(bytes);
//将byte[]转化为class对象
return defineClass(name, bytes,0 , len);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("读取资源时出现异常");
} finally {
try {
if(stream != null){
stream.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
1.2、实现需要被加载的类
这里没啥好说的,就是实现一个简单的类,让其被加载器加载
csharp
package org.example.test;
/**
* @author MADAO
*/
public class HotDeploy {
public void test1 () {
System.out.println("test");
}
}
1.3、实现运行
下面的代码展示了如何使用自定义的类加载器 CustomClassLoader
来实现实时编译和热部署的功能。具体来说,这段代码的作用如下:
- 循环加载和执行 :程序进入一个无限循环,在每次循环中,它使用
CustomClassLoader
加载并执行org.example.test.HotDeploy
类中的test1
方法。 - 方法调用 :通过反射机制,找到
HotDeploy
类中的test1
方法,并创建该类的一个实例,然后调用该方法。 - 休眠:每次方法调用之后,程序会休眠 5 秒钟。
- 编译源代码 :休眠结束后,程序尝试编译位于指定路径下的
HotDeploy.java
文件。编译命令使用的是javac
,并且指定了输出目录为E:/code/TechTrial/TechTrial_backend/api/target/classes
。 - 检查编译结果:编译命令执行完毕后,程序会检查编译命令的退出码来验证编译是否成功。
java
package org.example.test;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) {
while (Boolean.TRUE) {
try {
//使用自定义类加载器进行加载类
final CustomClassLoader loader1 = new CustomClassLoader();
final Class<?> clazz = loader1.loadClass("org.example.test.HotDeploy");
final Method method = clazz.getMethod("test1");
final Object o = clazz.getConstructor().newInstance();
method.invoke(o);
// 休眠5秒
Thread.sleep(5000);
// 执行编译命令,将HotDeploy.java文件编译成字节码文件(.class文件)
Process process = Runtime.getRuntime().exec(
"cmd /c cd E:\code\TechTrial\TechTrial_backend\api\src\main\java\org\example\test && javac -d E:/code/TechTrial/TechTrial_backend/api/target/classes HotDeploy.java"
);
// 检查编译命令是否成功执行
int exitCode = process.waitFor();
if (exitCode != 0) {
System.err.println("编译命令执行失败,退出码:" + exitCode);
} else {
System.out.println("编译命令执行成功");
}
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
IllegalAccessException | InvocationTargetException | InterruptedException |
IOException e) {
e.printStackTrace();
}
}
}
}
二、总结
通过本次的探索,我们了解了如何利用自定义类加载器来实现Java应用中的热部署功能。自定义类加载器打破了传统的类加载机制,允许我们在不重启应用的情况下更新代码逻辑。具体而言,我们定义了一个 CustomClassLoader
,它专门负责加载 org.example.test
包下的类,并且通过读取本地的 .class
文件来动态加载这些类。
总之,自定义类加载器为我们提供了一种灵活的方式来处理动态加载和更新代码的需求,同时也是一次有趣的实践机会,让我们深入理解了Java类加载机制的工作原理及其灵活性。希望这次的学习能够帮助你在未来的开发工作中更加得心应手。