通过源码,我们可以看到类加载器最重要的就两个方法,一个是loadClass,另一个是findClass。loadClass实现了双亲委派机制。findClass实现了查找类,但是应用类加载器和扩展类加载器都没有实现这个功能,而是交给URLClassLoader实现。
在下面的例子里面,我们重写一下findClass的过程
package com.test.test;
import java.io.FileInputStream;
public class MyClassLoader extends ClassLoader{
//读取文件
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream( "D:/test/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
//读取字节数组
byte[] bytes = loadByte(name);
//定义类
return defineClass(name,bytes,0,bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> aClass = myClassLoader.loadClass("com.test.test.User");
System.out.println(aClass.getClassLoader());
Class<?> bClass = myClassLoader.loadClass("com.test.test.User1");
System.out.println(bClass.getClassLoader());
}
}
这是我的文件结构
image-20210522160222860然后我在D盘放了User1和User两个类。
执行结果
"C:\Program Files\Java\jdk1.8.0_161\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\lib\idea_rt.jar=9658:C:\Program Files\JetBrains\IntelliJ IDEA 2020.2.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_161\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\rt.jar;F:\workspace\test\target\classes" com.test.test.MyClassLoader
sun.misc.Launcher$AppClassLoader@18b4aac2
com.test.test.MyClassLoader@1540e19d
分析结果
User类的类加载器是应用加载器。这是因为采用的是双亲委派机制,执行路线MyClassLoader->AppClassLoader->ExtClassLoader->BootstrapLoader->ExtClassLoader->AppClassLoader。
User1类的类加载器是MyClassLoader。这是因为父加载器都没有,才轮到自己执行。
如何打破双亲委派机制
其实也不复杂,只需要重写一下loadClass里面的代码就可以实现。
如果只是删除parent.loadClass是不够的。这是因为所有的类的父类都是Object。删除掉了parent.loadClass这意味着扩展类加载器和引导类加载器都不会进行加载。但是加载User和User1的前提,必须是先加载Object.class。所以还需要追加代码,完整代码如下:
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
long t1=0;
if (c == null) {
long t0 = System.nanoTime();
//删除双亲委派这一部分代码就可以了
//追加这部分代码是为了解决java.io.FileNotFoundException: D:\test\java\lang\Object.class (系统找不到指定的路径。)
//如果不是指定类那么就走传统的双亲委派
if (!"com.test.test.User".equals(name)){
c=super.loadClass(name,resolve);
}else {
// If still not found, then invoke findClass in order
// to find the class.
t1 = System.nanoTime();
c = findClass(name);
}
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
错误:
java.io.FileNotFoundException: D:\test\java\lang\Object.class (系统找不到指定的路径。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
at com.test.test.MyClassLoader.loadByte(MyClassLoader.java:9)
at com.test.test.MyClassLoader.findClass(MyClassLoader.java:21)
at com.test.test.MyClassLoader.loadClass(MyClassLoader.java:42)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at com.test.test.MyClassLoader.findClass(MyClassLoader.java:22)
at com.test.test.MyClassLoader.loadClass(MyClassLoader.java:42)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at com.test.test.MyClassLoader.main(MyClassLoader.java:58)
Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at com.test.test.MyClassLoader.findClass(MyClassLoader.java:22)
at com.test.test.MyClassLoader.loadClass(MyClassLoader.java:42)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at com.test.test.MyClassLoader.main(MyClassLoader.java:58)
Process finished with exit code 1
网友评论