The default implementation of this method searches for classes in the following order:
- Invoke findLoadedClass(String) to check if the class has already been loaded.
- Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead.
- Invoke the findClass(String) method to find the class.
稍微理解下:
- 首先调用findLoadedClass(String),如果被加载过直接返回true,没有执行下一个步骤
- 调用父级类加载器的loaderClass方法,如果父级类加载器为空,就使用JVM默认的类加载器
- 调用findClass方法
这个是模版方法设计模式的运用。
编写自定义类加载器
需要继承ClassLoader类,重写findClass方法。
package lorenzo;
public class MyClassLoader extends ClassLoader {
private String rootDir;
public MyClassLoader(String rootDir) {
this.rootDir = rootDir;
}
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("自定义类加载器");
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
}
编写需要加载的类
package lorenzo;
public class MyClass {
@Override
public String toString() {
return "Hello Lorenzo!";
}
}
编写测试类
package lorenzo;
public class Client {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader myClassLoader = new MyClassLoader("/data");
Class clazz = myClassLoader.loadClass("lorenzo.MyClass");
System.out.println(clazz.newInstance().toString());
System.out.println(myClassLoader.getClass().getClassLoader());
}
}
注意:以上三个类都在/data/lorenzo文件夹下面
mac控制台输入:
cd /data/lorenzo
javac MyClassLoader.java
javac MyClass.java
javac Client.java
cd ..
java ava lorenzo/Client
output:
Hello Lorenzo!
sun.misc.Launcher$AppClassLoader@2a139a55
分析结果:
根据类加载器树形图可知
MyClassLoader.java和Client.java以及MyClass.java都在classpath目录下,他们由AppClassLoader类加载器加载。根据loadClass(String)的模版方法可知,MyClassLoader的父级类加载器AppClassLoader可以加载binary name 为lorenzo.MyClass的类,程序不会执行到loadClass的第三步,所以是AppClassLoader而不是MyClassLoader加载MyClass。
修改代码
把MyClass.class移动到data1/lorenzo的文件夹下
修改Client中代码:
MyClassLoader myClassLoader = new MyClassLoader("/data");
修改后的代码为
MyClassLoader myClassLoader = new MyClassLoader("/data1");
mac控制台输入:
cd /data/lorenzo
javac Client.java
cd ..
java lorenzo/Client
output:
自定义类加载器
Hello Lorenzo!
sun.misc.Launcher$AppClassLoader@2a139a55
注意确保/data/lorenzo目录没有MyClass.class。
把自定义的类加载器挂载到ExtClassLoader
- 把MyClassLoader.class打成jar包
cd /data
jar cvf lorenzo.jar lorenzo/MyClassLoader.class
- 把lorenzo.jar复制到../jre/lib/ext/目录下
java lorenzo/Client
output:
自定义类加载器
Hello Lorenzo!
sun.misc.Launcher$ExtClassLoader@75b84c92
写在最后
加密的类加载器思路,首先编译生成class文件,加密class文件,在自定义的类加载器解密。