要摆脱双亲委派机制不仅要重写 findClass(),还需要重写 loadClass()。
在下面的 MyClassLoader 类里,我们重写了 loadClass() 方法:
package com.binroad.test;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
private String rootDir;
public MyClassLoader(String rootDir) {
this.rootDir = rootDir;
}
private byte[] loadClassData(String className) {
String path = rootDir + "/" + className.replace(".", "/") + ".class";
byte[] datas = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
is = new FileInputStream(path);
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int readLen = -1;
while ((readLen = is.read(buffer)) != -1) {
baos.write(buffer, 0, readLen);
}
datas = baos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return datas;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> clz = null;
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
clz = defineClass(name, classData, 0, classData.length);
}
return clz;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> clz = null;
//先自己加载,不行再交给父辈处理(已打破双亲委派机制)
try {
clz = findClass(name);
} catch(ClassNotFoundException e) {
System.out.println("自定义类加载器无法加载该类");
}
if (clz == null && getParent() != null) {
clz = getParent().loadClass(name);
System.out.println("由 " + getParent() + " 加载:" + clz);
}
return clz;
}
}
在 Demo5 里进行测试:
package com.binroad.test;
public class Demo5 {
public static void main(String[] args) {
MyClassLoader mcl = new MyClassLoader("D:/TestClassLoader");
try {
Class<?> clz = mcl.loadClass("com.binroad.test.HelloWorld");
System.out.println(clz);
System.out.println(clz.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果:
java.io.FileNotFoundException: D:\TestClassLoader\java\lang\Object.class (系统找不到指定的文件。)
……
……
……
自定义类加载器无法加载该类
由 sun.misc.Launcher$AppClassLoader@73d16e93 加载:class java.lang.Object
class com.binroad.test.HelloWorld
com.binroad.test.MyClassLoader@6d06d69c
上面代码虽然成功将 HelloWorld.class 成功加载,但报了一个 FileNotFoundException 的异常。
异常分析:
HelloWorld 类默认继承于 Object 类,在加载 HelloWorld 时,会先调用一次 loadClass() 加载其父类 Object,再调用一次 loadClass() 加载 HelloWorld 自己(递归调用)。当我们加载某个类时,其父类和子类的加载用的是同一个类加载器。这里我们使用的是自定义加载器来加载 HelloWorld,当我们尝试自定义类加载器先加载时,由于找不到父类 Object 故而抛出异常。
在 MyClassLoader 的 loadClass() 方法里,我们将以 java. 开头的包名的类交由 AppClassLoader 来处理,做了如下改动:
package com.binroad.test;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
private String rootDir;
public MyClassLoader(String rootDir) {
this.rootDir = rootDir;
}
private byte[] loadClassData(String className) {
String path = rootDir + "/" + className.replace(".", "/") + ".class";
byte[] datas = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
is = new FileInputStream(path);
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int readLen = -1;
while ((readLen = is.read(buffer)) != -1) {
baos.write(buffer, 0, readLen);
}
datas = baos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return datas;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> clz = null;
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
clz = defineClass(name, classData, 0, classData.length);
}
return clz;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> clz = null;
//将其他核心类库交给系统加载器来加载,此逻辑支线遵行双亲委派机制
if (name.startsWith("java.")) {
ClassLoader SystemClassLoader = ClassLoader.getSystemClassLoader();
clz = SystemClassLoader.loadClass(name);
if (clz != null) {
if (resolve) {
resolveClass(clz);
}
return clz;
}
}
//先自己加载,不行再交给父辈处理(已打破双亲委派机制)
try {
clz = findClass(name);
} catch(ClassNotFoundException e) {
System.out.println("自定义类加载器无法加载该类");
}
if (clz == null && getParent() != null) {
getParent().loadClass(name);
}
return clz;
}
}
再次以上面 Demo5 的代码测试,输出的结果为:
com.binroad.test.MyClassLoader@6d06d69c
然后在 MyClassLoader 里,将加载以 "java." 为开头的包名里的类的那段代码注释掉:
package com.binroad.test;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
private String rootDir;
public MyClassLoader(String rootDir) {
this.rootDir = rootDir;
}
private byte[] loadClassData(String className) {
String path = rootDir + "/" + className.replace(".", "/") + ".class";
byte[] datas = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
is = new FileInputStream(path);
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int readLen = -1;
while ((readLen = is.read(buffer)) != -1) {
baos.write(buffer, 0, readLen);
}
datas = baos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return datas;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> clz = null;
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
clz = defineClass(name, classData, 0, classData.length);
}
return clz;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> clz = null;
//将其他核心类库交给系统加载器来加载,此逻辑支线遵行双亲委派机制
//这段代码的主要功能是用来加载我们自定义类的父类Object
// if (name.startsWith("java.")) {
// ClassLoader SystemClassLoader = ClassLoader.getSystemClassLoader();
// clz = SystemClassLoader.loadClass(name);
// if (clz != null) {
// if (resolve) {
// resolveClass(clz);
// }
// return clz;
// }
// }
//先自己加载,不行再交给父辈处理(已打破双亲委派机制)
try {
clz = findClass(name);
} catch(ClassNotFoundException e) {
System.out.println("自定义类加载器无法找到该类");
}
if (clz == null && getParent() != null) {
clz = getParent().loadClass(name);
}
return clz;
}
}
再将 Demo5 里的代码改一下,将线程上下文类加载器设置为我们自己定义的类加载器,而后加载我们在其他路径下自定义的 java.lang.Long:
package com.binroad.test;
public class Demo5 {
public static void main(String[] args) {
// MyClassLoader mcl = new MyClassLoader("D:/TestClassLoader");
// try {
// Class<?> clz = mcl.loadClass("com.binroad.test.HelloWorld");
// System.out.println(clz.getClassLoader());
// } catch (ClassNotFoundException e) {
// e.printStackTrace();
// }
Thread.currentThread().setContextClassLoader(new MyClassLoader("D:/TestClassLoader"));
try {
Class<?> clz = Thread.currentThread().getContextClassLoader().loadClass("java.lang.Long");
System.out.println(clz);
System.out.println(clz.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
这次的运行结果没有报找不到 Object 的异常,却报了 SecurityException 异常,提示我们这个包名的被禁止的:
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
一般的,当我们使用没有打破双亲委派机制的自定义类加载器加载我们自己定义的与 Java 核心类库同包同名的类时,其实是引导类加载器加载了核心类库里的类,并不是自己定义的那个同包同名的类。
即使我们使用已打破了双亲委派机制自定义类加载器,也不能加载我们自定义的、与其他 Java 核心类库同包同名的类(具体的、底层的原因暂时不明)。这个问题我个人认为并不绝对,所以我留下一个很无脑的问题:
——那么,到底如何才能加载我们自己定义的与 Java 核心类库同包同名的类?!