关于热加载的一种尝试。
前言
- 前传 - Java 关于类加载器ClassLoader的有趣实验
- 自定义一个类加载器,实现类的动态(编译与)加载,这点毫无问题。
- 现在的问题是,同一个类加载器,能否重复加载同名类(先剧透,不能😳
- 不能的话,那么可以考虑使用一次加载就new一个类加载器。
这里要自己建立一个类管理器…(不考虑其它的各种依赖
工程结构
目录
src
|
|---nicelee.bilibili
| |
| |---Test.java // 原始测试类,nicelee.bilibili.Test.java
| |
| |
| |---plugin
| |-------CustomClassLoader.java // 类加载器
| |
| |
| |-------PluginManager.java // 测试入口,以及简单编译、加载的实现
|
某文件夹
|
|------- 1
| |
| |---Test.java // 第一次动态加载的测试类,nicelee.bilibili.Test.java
|
|------- 2
| |
| |---Test.java // 第二次动态加载的测试类,nicelee.bilibili.Test.java
|
Test.java
package nicelee.bilibili;
public class Test {
public void run() {
System.out.println("原始实现"); // 三个Test.java实现差不多,打印的语句不同
//System.out.println("加载实现1");
//System.out.println("加载实现2");
}
}
CustomClassLoader.java
自定义findClass方法,没啥好讲的…
package nicelee.bilibili.plugin;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
public class CustomClassLoader extends ClassLoader {
protected Class<?> findClass(String classPath, String className) {
try {
FileInputStream in = new FileInputStream(new File(classPath));
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
for (int len = 0; (len = in.read(buffer)) != -1;) {
out.write(buffer, 0, len);
}
in.close();
byte[] bytes = out.toByteArray();
return this.defineClass(className, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
尝试一个ClassLoader重复加载
PluginManager.java
package nicelee.bilibili.plugin;
import java.io.File;
import java.io.IOException;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import nicelee.bilibili.Test;
public class PluginManager {
// 类加载器,全局只用这一个
public static CustomClassLoader ccloader = new CustomClassLoader();
/**
* 编译指定的java文件,成功后,在同一目录下将得到若干个.class(数目取决于是否有匿名/内部类)
*
* @param javaFile
* @return
*/
public static boolean compile(File javaFile) {
try {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int result = compiler.run(null, null, null, javaFile.getCanonicalPath());
return result == 0;
} catch (Exception e) {
return false;
}
}
/**
* 使用静态的ccloader加载类
*
* @param pluginDir .class文件所在的文件夹
* @param fileNameWithoutSuffix 不带后缀的文件名
* @param classNameWithPackage 类的全称
* @throws IOException
*/
public static void loadClass(File pluginDir, String fileNameWithoutSuffix, String classNameWithPackage)
throws IOException {
for (File f : pluginDir.listFiles()) {
if (f.getName().endsWith(".class")) {
String name = f.getName().replaceFirst("\\.class$", "");
// 考虑Test$1.class等匿名类、内部类,这些也是要自动加载的
if (name.startsWith(fileNameWithoutSuffix)) {
ccloader.findClass(f.getCanonicalPath(), classNameWithPackage);
}
}
}
}
public static void main(String[] args) {
Test test = new Test();
test.run();
try {
// 文件夹1下的各种文件
File pluginDir = new File("D:\\Workspace\\javaweb-springboot\\plugin\\1");
File javaFile = new File("D:\\Workspace\\javaweb-springboot\\plugin\\1\\Test.java");
// 编译
boolean result = PluginManager.compile(javaFile);
// 加载
PluginManager.loadClass(pluginDir, "Test", "nicelee.bilibili.Test");
// 获取ccloader下的某 类
Class<?> clazz = Class.forName("nicelee.bilibili.Test", true, ccloader);
// 测试运行run方法
clazz.getMethod("run").invoke(clazz.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
try {
// 文件夹2下的各种文件
File pluginDir = new File("D:\\Workspace\\javaweb-springboot\\plugin\\2");
File javaFile = new File("D:\\Workspace\\javaweb-springboot\\plugin\\2\\Test.java");
// 编译
boolean result = PluginManager.compile(javaFile);
// 加载
PluginManager.loadClass(pluginDir, "Test", "nicelee.bilibili.Test");
// 获取ccloader下的某 类
Class<?> clazz = Class.forName("nicelee.bilibili.Test", true, ccloader);
// 测试运行run方法
clazz.getMethod("run").invoke(clazz.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
try {
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果
加入简单的类管理
PluginManager.java
package nicelee.bilibili.plugin;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import nicelee.bilibili.Test;
public class PluginManager {
// 使用map保存类名与对应的class
static HashMap<String, Class<?>> clazzMap = new HashMap<String, Class<?>>();
// 提供外部的类访问接口
public static Class<?> getClass(String className) {
return clazzMap.get(className);
}
/**
* 编译指定的java文件,成功后,在同一目录下将得到若干个.class(数目取决于是否有匿名/内部类)
*
* @param javaFile
* @return
*/
public static boolean compile(File javaFile) {
try {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int result = compiler.run(null, null, null, javaFile.getCanonicalPath());
return result == 0;
} catch (Exception e) {
return false;
}
}
/**
* 每次使用不同的ccloader加载类
*
* @param pluginDir .class文件所在的文件夹
* @param fileNameWithoutSuffix 不带后缀的文件名
* @param classNameWithPackage 类的全称
* @throws IOException
*/
public static void loadClass(File pluginDir, String fileNameWithoutSuffix, String classNameWithPackage)
throws IOException {
CustomClassLoader ccloader = new CustomClassLoader();
for (File f : pluginDir.listFiles()) {
if (f.getName().endsWith(".class")) {
String name = f.getName().replaceFirst("\\.class$", "");
// 考虑Test$1.class等匿名类、内部类,这些也是要自动加载的
if (name.startsWith(fileNameWithoutSuffix)) {
Class<?> clazz = ccloader.findClass(f.getCanonicalPath(), classNameWithPackage);
clazzMap.put(classNameWithPackage, clazz);
}
}
}
}
public static void main(String[] args) {
Test test = new Test();
test.run();
try {
// 文件夹1下的各种文件
File pluginDir = new File("D:\\Workspace\\javaweb-springboot\\plugin\\1");
File javaFile = new File("D:\\Workspace\\javaweb-springboot\\plugin\\1\\Test.java");
// 编译
boolean result = PluginManager.compile(javaFile);
// 加载
PluginManager.loadClass(pluginDir, "Test", "nicelee.bilibili.Test");
// 获取自己实现的管理提取某类
Class<?> clazz = PluginManager.getClass("nicelee.bilibili.Test");
// 测试运行run方法
clazz.getMethod("run").invoke(clazz.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
try {
File pluginDir = new File("D:\\Workspace\\javaweb-springboot\\plugin\\2");
File javaFile = new File("D:\\Workspace\\javaweb-springboot\\plugin\\2\\Test.java");
// 编译
boolean result = PluginManager.compile(javaFile);
// 加载
PluginManager.loadClass(pluginDir, "Test", "nicelee.bilibili.Test");
// 获取自己实现的管理提取某类
Class<?> clazz = PluginManager.getClass("nicelee.bilibili.Test");
// 测试运行run方法
clazz.getMethod("run").invoke(clazz.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果
后续
- 判断一个java文件是否做出了改变
- 可以读取该文件最后的更改时间
- 可以比较MD5值
- 以上这些都必须进一步拓展,把相关信息给记录下来,在此就不再赘述了