引言
上一篇文章我们介绍了类加载器的基础知识和双亲委派模型,这一章我们开发自己的类加载器,然后根据源码解析深度解析系统类加载器。
自定义文件系统类加载器
下面代码展示了一个自定义的类加载器,该类加载器用来加载存储在文件系统上的Java字节代码。
public class FileSystemClassLoader extends ClassLoader {private final String rootDir;public FileSystemClassLoader(String rootDir) {this.rootDir = rootDir;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = getClassData(name);if(classData == null){throw new ClassNotFoundException();}else {return defineClass(name,classData,0,classData.length);}}private byte[] getClassData(String className){String path = classNameToPath(className);try{InputStream is = new FileInputStream(path);ByteArrayOutputStream baos = new ByteArrayOutputStream();int bufferSize = 4096;byte[] buffer = new byte[bufferSize];int bytesNumberRead = 0;while((bytesNumberRead = is.read(buffer)) != -1){baos.write(buffer,0,bytesNumberRead);}return baos.toByteArray();}catch (IOException e){e.printStackTrace();}return null;}private String classNameToPath(String className){return rootDir + File.separatorChar + className.replace('.',File.separatorChar) + ".class";}public static void main(String[] args) throws ClassNotFoundException {FileSystemClassLoader classLoader = new FileSystemClassLoader("/Users/cuihualong/develop/code/java/concurrency/target/classes");System.out.println(classLoader.getParent());Class<?> aClass = classLoader.loadClass("person.andy.concurrency.classload.SimpleObject");System.out.println(aClass.getClassLoader());System.out.println(aClass);SimpleObject simpleObject = new SimpleObject();System.out.println(aClass.isInstance(simpleObject));}}
sun.misc.Launcher$AppClassLoader@18b4aac2sun.misc.Launcher$AppClassLoader@18b4aac2class person.andy.concurrency.classload.SimpleObjecttrue
实现的逻辑很简单,就是重写了findClass方法,findClass方法主要是得到代表class文件的二进制字节数组,然后调用defineClass方法来得到Class实例。
前一篇文章中我们说过,在自定义类加载器时,推荐重写findClass方法而不是loadClass方法,因为类加载器的双亲委派模型是在loadClass方法中实现的,如果重写loadClass方法,可能导致该模型被破坏。
上面的FileSystemClassLoader没有破坏双亲委派模型,也正是这个原因,我们可以发现一些值得注意的问题:
在main方法中,我们使用自定义的类加载器加载了位于当前类ClassPath下的名为SimpleObject的class文件,得到了对应的Class的实例,然后输出了这个Class的类加载器,和FileSystemClassLoader的父类加载器,结果居然都是系统类加载器。自定义类加载器的父类加载器是系统类加载器这个不难理解,因为上一篇文章中我们有过介绍,但是为什么我们加载出来的类的getClassLoader方法返回的也是系统类加载器呢?这个问题涉及两个概念,一个就是双亲委派模型,一个就是系统类加载器的加载机制。
首先说双亲委派模型,由于我们在自定义的类加载器中只重写了findClass方法而不是loadClass方法,所以这个类加载器是遵循双亲委派模型的,那么当加载器一个类时,首先会请求父类加载器去完成这个请求,也就是系统类加载器,当然,系统类加载器同样遵循双亲委派模型,会将请求继续向上代理,但是ExtClassLoader和bootstrapClassLoader都只加载固定位置的类,只有系统类加载器才会加载ClassPath下的类,所以,最终是由系统类加载器来加载这个类的。
如果这个类是由系统类加载器进行加载的,那么aClass.isInstance(simpleObject)这段代码返回true也就很正常了。
现在,先看一个修改后的自定义类的实现:
package person.andy.concurrency.classload;import java.io.*;public class FileSystemClassLoader1 extends ClassLoader {private String rootDir;public FileSystemClassLoader1(String rootDir) {this.rootDir = rootDir;}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {byte[] classData = getClassData(name);if(null == classData){throw new ClassNotFoundException();}return defineClass(name,classData,0,classData.length);}private byte[] getClassData(String className){String path = classNameToPath(className);try{InputStream is = new FileInputStream(path);ByteArrayOutputStream baos = new ByteArrayOutputStream();int bufferSize = 4096;byte[] buffer = new byte[bufferSize];int bytesNumberRead = 0;while((bytesNumberRead = is.read(buffer)) != -1){baos.write(buffer,0,bytesNumberRead);}return baos.toByteArray();}catch (IOException e){e.printStackTrace();}return null;}private String classNameToPath(String className){return rootDir + File.separatorChar + className.replace('.',File.separatorChar) + ".class";}public static void main(String[] args) throws ClassNotFoundException {FileSystemClassLoader1 classLoader1 = new FileSystemClassLoader1("/Users/cuihualong/develop/code/java/concurrency/target/classes");System.out.println(classLoader1.getParent());Class<?> aClass = classLoader1.loadClass("person.andy.concurrency.classload.SimpleObject");System.out.println(aClass.getClassLoader());SimpleObject simpleObject = new SimpleObject();System.out.println(aClass.isInstance(simpleObject));}}
我们没有重写findClass方法,而是重写了loadClass方法,并且loadClass方法中没有调用父类去进行类的加载,而是直接获取了类的二进制字节数组然后通过defineClass去创建Class实例。如果你在想这个main方法会输出什么结果,那你就错了,因为会直接报错:
sun.misc.Launcher$AppClassLoader@18b4aac2java.io.FileNotFoundException: /Users/cuihualong/develop/code/java/concurrency/target/classes/java/lang/Object.class (No such file or directory)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 person.andy.concurrency.classload.FileSystemClassLoader1.getClassData(FileSystemClassLoader1.java:24)at person.andy.concurrency.classload.FileSystemClassLoader1.loadClass(FileSystemClassLoader1.java:14)at java.lang.ClassLoader.defineClass1(Native Method)at java.lang.ClassLoader.defineClass(ClassLoader.java:756)at java.lang.ClassLoader.defineClass(ClassLoader.java:635)at person.andy.concurrency.classload.FileSystemClassLoader1.loadClass(FileSystemClassLoader1.java:18)at person.andy.concurrency.classload.FileSystemClassLoader1.main(FileSystemClassLoader1.java:45)Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Objectat java.lang.ClassLoader.defineClass1(Native Method)at java.lang.ClassLoader.defineClass(ClassLoader.java:756)at java.lang.ClassLoader.defineClass(ClassLoader.java:635)at person.andy.concurrency.classload.FileSystemClassLoader1.loadClass(FileSystemClassLoader1.java:18)at person.andy.concurrency.classload.FileSystemClassLoader1.main(FileSystemClassLoader1.java:45)Caused by: java.lang.ClassNotFoundExceptionat person.andy.concurrency.classload.FileSystemClassLoader1.loadClass(FileSystemClassLoader1.java:16)... 5 more
第一行确实输出了自定义类加载器的父类加载器,就是AppClassLoader,但是调用loadClass方法就报错了,而且从报错信息我们可以看出,是我们定义的classLoader去加载java.lang.object这个类了,但是在我们的代码逻辑中,肯定是不能在rootDir下面找到这个类的,所以报错。从这里我们可以知道,当加载一个类时,会去加载这个类中用到的类,并且加载的动作是由同一个类加载器完成的。
我们简单的修改一下loadClass的实现,当name是java开头的时候,我们就让父类加载器去加载它。
@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {if(name.startsWith("java")){return getParent().loadClass(name);}byte[] classData = getClassData(name);if(null == classData){throw new ClassNotFoundException();}return defineClass(name,classData,0,classData.length);}
再次运行,就可以正常输出结果:
sun.misc.Launcher$AppClassLoader@18b4aac2person.andy.concurrency.classload.FileSystemClassLoader1@511d50c0false
我们看到,这次aClass.getClassLoader()输出的就是我们自定义的FileSystemClassLoader1而不是系统类加载器了,并且isInstance输出了false。
通过在子类中重写loadClass方法,我们能够破坏双亲委派模型。
