NiceLeeのBlog 用爱发电 bilibili~

Java 关于类加载器ClassLoader的有趣实验(二)

2020-05-24
nIceLee

阅读:


关于热加载的一种尝试。

前言

  • 前传 - 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值
  • 以上这些都必须进一步拓展,把相关信息给记录下来,在此就不再赘述了

内容
隐藏