关于热加载的一种尝试。
前言
- 前传 - 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值
 
 - 以上这些都必须进一步拓展,把相关信息给记录下来,在此就不再赘述了