// 待类加载的类
package com.infuq;
public class Ticket { }
自定义类加载器 MyClassLoader
package com.infuq;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* 自定义类加载器
*
* 加载指定路径scanPath下的类文件
*
*/
public class MyClassLoader extends ClassLoader {
// MyClassLoader可以扫描的路径. 例如: /usr/include
private final String scanPath ;
public MyClassLoader(String scanPath){
super();
this.scanPath = scanPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = readFileData(name);
if (classData != null) {
return this.defineClass(name, classData, 0, classData.length);
}
return null;
}
// 读取clazzName对应的文件内容, 返回字节数组
private byte[] readFileData(String clazzName) {
File file;
/*
* 假如scanPath=/usr/include
* 由于入参clazzName是类的全限定名, 例如java.lang.Object
* 为了读取clazzName对应的文件, 需要将字符串`java.lang.Object`转成`java/lang/Object`
* 最后得到完整文件路径/usr/include/java/lang/Object.class
*/
if (System.getProperty("os.name").contains("Windows")) {
clazzName = clazzName.replace(".", "\\");
file = new File(scanPath + "\\" + clazzName + ".class");
}
else {
clazzName = clazzName.replace(".", "/");
file = new File(scanPath + "/" + clazzName + ".class");
}
// 读取
if (file.exists()) {
FileInputStream in = null;
ByteArrayOutputStream out;
try {
in = new FileInputStream(file);
out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int size;
while ((size = in.read(buffer)) != -1) {
out.write(buffer, 0, size);
}
return out.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
测试类 Example
package com.infuq;
import java.util.Scanner;
public class Example {
public static void main(String[] args) throws Exception {
System.out.print("请输入类加载的路径: ");
Scanner scanner = new Scanner(System.in);
/*
* 从命令行读取指定扫描的路径
* 例如
* Unix : /usr/bin/
* Windows : D:\\repository\\class
*/
String scanPath = scanner.next();
MyClassLoader myClassLoader = new MyClassLoader(scanPath);
/*
* 读取类的全限定名, 例如 java.lang.Object
*
*/
System.out.print("请输入待加载的类全限定名称: ");
String packageNamePath = scanner.next();
System.out.println();
Class<?> clazz = myClassLoader.loadClass(packageNamePath);
if (clazz != null)
System.out.println(packageNamePath + "类加载器是: " + clazz.getClassLoader());
System.out.println("系统类加载器扫描文件路径: " + System.getProperty("java.class.path"));
}
}
编译 Ticket.java , MyClassLoader.java , Example.java 三个源文件, 文件结构图如下
以上源文件在 /Users/infuq/Repository/GitLab/infuq-others/Lab/2022-3-22 目录下
其中还把待加载的Ticket.class文件拷贝一份到了自定义的JAVA-INF目录下, 后面会用到
【测试一】
本计划让自定义类加载器MyClassLoader 加载/Users/infuq/Repository/GitLab/infuq-others/Lab/2022-3-22/JAVA-INF目录下的com.infuq.Ticket类, 根据双亲委派, 委派给了系统类加载器. 而当前目录下就有一个com.infuq.Ticket类, 因此这个Ticket类被系统类加载器加载了.
改个名称, 故意让系统类加载器找不到Ticket类
继续上面的相同执行
这个时候com.infuq.Ticket就被自定义类加载器加载了.