引言

上一篇文章我们介绍了类加载器的基础知识和双亲委派模型,这一章我们开发自己的类加载器,然后根据源码解析深度解析系统类加载器。

自定义文件系统类加载器

下面代码展示了一个自定义的类加载器,该类加载器用来加载存储在文件系统上的Java字节代码。

  1. public class FileSystemClassLoader extends ClassLoader {
  2. private final String rootDir;
  3. public FileSystemClassLoader(String rootDir) {
  4. this.rootDir = rootDir;
  5. }
  6. @Override
  7. protected Class<?> findClass(String name) throws ClassNotFoundException {
  8. byte[] classData = getClassData(name);
  9. if(classData == null){
  10. throw new ClassNotFoundException();
  11. }else {
  12. return defineClass(name,classData,0,classData.length);
  13. }
  14. }
  15. private byte[] getClassData(String className){
  16. String path = classNameToPath(className);
  17. try{
  18. InputStream is = new FileInputStream(path);
  19. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  20. int bufferSize = 4096;
  21. byte[] buffer = new byte[bufferSize];
  22. int bytesNumberRead = 0;
  23. while((bytesNumberRead = is.read(buffer)) != -1){
  24. baos.write(buffer,0,bytesNumberRead);
  25. }
  26. return baos.toByteArray();
  27. }catch (IOException e){
  28. e.printStackTrace();
  29. }
  30. return null;
  31. }
  32. private String classNameToPath(String className){
  33. return rootDir + File.separatorChar + className.replace('.',File.separatorChar) + ".class";
  34. }
  35. public static void main(String[] args) throws ClassNotFoundException {
  36. FileSystemClassLoader classLoader = new FileSystemClassLoader("/Users/cuihualong/develop/code/java/concurrency/target/classes");
  37. System.out.println(classLoader.getParent());
  38. Class<?> aClass = classLoader.loadClass("person.andy.concurrency.classload.SimpleObject");
  39. System.out.println(aClass.getClassLoader());
  40. System.out.println(aClass);
  41. SimpleObject simpleObject = new SimpleObject();
  42. System.out.println(aClass.isInstance(simpleObject));
  43. }
  44. }
  1. sun.misc.Launcher$AppClassLoader@18b4aac2
  2. sun.misc.Launcher$AppClassLoader@18b4aac2
  3. class person.andy.concurrency.classload.SimpleObject
  4. true

实现的逻辑很简单,就是重写了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也就很正常了。
现在,先看一个修改后的自定义类的实现:

  1. package person.andy.concurrency.classload;
  2. import java.io.*;
  3. public class FileSystemClassLoader1 extends ClassLoader {
  4. private String rootDir;
  5. public FileSystemClassLoader1(String rootDir) {
  6. this.rootDir = rootDir;
  7. }
  8. @Override
  9. public Class<?> loadClass(String name) throws ClassNotFoundException {
  10. byte[] classData = getClassData(name);
  11. if(null == classData){
  12. throw new ClassNotFoundException();
  13. }
  14. return defineClass(name,classData,0,classData.length);
  15. }
  16. private byte[] getClassData(String className){
  17. String path = classNameToPath(className);
  18. try{
  19. InputStream is = new FileInputStream(path);
  20. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  21. int bufferSize = 4096;
  22. byte[] buffer = new byte[bufferSize];
  23. int bytesNumberRead = 0;
  24. while((bytesNumberRead = is.read(buffer)) != -1){
  25. baos.write(buffer,0,bytesNumberRead);
  26. }
  27. return baos.toByteArray();
  28. }catch (IOException e){
  29. e.printStackTrace();
  30. }
  31. return null;
  32. }
  33. private String classNameToPath(String className){
  34. return rootDir + File.separatorChar + className.replace('.',File.separatorChar) + ".class";
  35. }
  36. public static void main(String[] args) throws ClassNotFoundException {
  37. FileSystemClassLoader1 classLoader1 = new FileSystemClassLoader1("/Users/cuihualong/develop/code/java/concurrency/target/classes");
  38. System.out.println(classLoader1.getParent());
  39. Class<?> aClass = classLoader1.loadClass("person.andy.concurrency.classload.SimpleObject");
  40. System.out.println(aClass.getClassLoader());
  41. SimpleObject simpleObject = new SimpleObject();
  42. System.out.println(aClass.isInstance(simpleObject));
  43. }
  44. }

我们没有重写findClass方法,而是重写了loadClass方法,并且loadClass方法中没有调用父类去进行类的加载,而是直接获取了类的二进制字节数组然后通过defineClass去创建Class实例。如果你在想这个main方法会输出什么结果,那你就错了,因为会直接报错:

  1. sun.misc.Launcher$AppClassLoader@18b4aac2
  2. java.io.FileNotFoundException: /Users/cuihualong/develop/code/java/concurrency/target/classes/java/lang/Object.class (No such file or directory)
  3. at java.io.FileInputStream.open0(Native Method)
  4. at java.io.FileInputStream.open(FileInputStream.java:195)
  5. at java.io.FileInputStream.<init>(FileInputStream.java:138)
  6. at java.io.FileInputStream.<init>(FileInputStream.java:93)
  7. at person.andy.concurrency.classload.FileSystemClassLoader1.getClassData(FileSystemClassLoader1.java:24)
  8. at person.andy.concurrency.classload.FileSystemClassLoader1.loadClass(FileSystemClassLoader1.java:14)
  9. at java.lang.ClassLoader.defineClass1(Native Method)
  10. at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
  11. at java.lang.ClassLoader.defineClass(ClassLoader.java:635)
  12. at person.andy.concurrency.classload.FileSystemClassLoader1.loadClass(FileSystemClassLoader1.java:18)
  13. at person.andy.concurrency.classload.FileSystemClassLoader1.main(FileSystemClassLoader1.java:45)
  14. Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object
  15. at java.lang.ClassLoader.defineClass1(Native Method)
  16. at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
  17. at java.lang.ClassLoader.defineClass(ClassLoader.java:635)
  18. at person.andy.concurrency.classload.FileSystemClassLoader1.loadClass(FileSystemClassLoader1.java:18)
  19. at person.andy.concurrency.classload.FileSystemClassLoader1.main(FileSystemClassLoader1.java:45)
  20. Caused by: java.lang.ClassNotFoundException
  21. at person.andy.concurrency.classload.FileSystemClassLoader1.loadClass(FileSystemClassLoader1.java:16)
  22. ... 5 more

第一行确实输出了自定义类加载器的父类加载器,就是AppClassLoader,但是调用loadClass方法就报错了,而且从报错信息我们可以看出,是我们定义的classLoader去加载java.lang.object这个类了,但是在我们的代码逻辑中,肯定是不能在rootDir下面找到这个类的,所以报错。从这里我们可以知道,当加载一个类时,会去加载这个类中用到的类,并且加载的动作是由同一个类加载器完成的。
我们简单的修改一下loadClass的实现,当name是java开头的时候,我们就让父类加载器去加载它。

  1. @Override
  2. public Class<?> loadClass(String name) throws ClassNotFoundException {
  3. if(name.startsWith("java")){
  4. return getParent().loadClass(name);
  5. }
  6. byte[] classData = getClassData(name);
  7. if(null == classData){
  8. throw new ClassNotFoundException();
  9. }
  10. return defineClass(name,classData,0,classData.length);
  11. }

再次运行,就可以正常输出结果:

  1. sun.misc.Launcher$AppClassLoader@18b4aac2
  2. person.andy.concurrency.classload.FileSystemClassLoader1@511d50c0
  3. false

我们看到,这次aClass.getClassLoader()输出的就是我们自定义的FileSystemClassLoader1而不是系统类加载器了,并且isInstance输出了false。
通过在子类中重写loadClass方法,我们能够破坏双亲委派模型。