现在,该类不再通过 AppClassLoader 来加载,而是通过 ExtClassLoader 来加载了。如果我们试图把jar包拷贝到<Java_Runtime_Home>lib,尝试通过启动类加载器加载该类时,我们会发现编译器无法识别该类,因为启动类加载器除了指定目录外,还必须是特定名称的文件才能加载。
  三、自定义类加载器
  通常情况下,我们都是直接使用系统类加载器。但是,有的时候,我们也需要自定义类加载器。比如应用是通过网络来传输 Java 类的字节码,为保证安全性,这些字节码经过了加密处理,这时系统类加载器无法对其进行加载,这样则需要自定义类加载器来实现。自定义类加载器一般都是继承自 ClassLoader 类,从上面对 loadClass 方法来分析来看,我们只需要重写 findClass 方法即可。下面我们通过一个示例来演示自定义类加载器的流程:
  package com.paddx.test.classloading;
  import java.io.*;
  /**
  * Created by liuxp on 16/3/12.
  */
  public class MyClassLoader extends ClassLoader {
  private String root;
  protected Class<?> findClass(String name) throws ClassNotFoundException {
  byte[] classData = loadClassData(name);
  if (classData == null) {
  throw new ClassNotFoundException();
  } else {
  return defineClass(name, classData, 0, classData.length);
  }
  }
  private byte[] loadClassData(String className) {
  String fileName = root + File.separatorChar
  + className.replace('.', File.separatorChar) + ".class";
  try {
  InputStream ins = new FileInputStream(fileName);
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  int bufferSize = 1024;
  byte[] buffer = new byte[bufferSize];
  int length = 0;
  while ((length = ins.read(buffer)) != -1) {
  baos.write(buffer, 0, length);
  }
  return baos.toByteArray();
  } catch (IOException e) {
  e.printStackTrace();
  }
  return null;
  }
  public String getRoot() {
  return root;
  }
  public void setRoot(String root) {
  this.root = root;
  }
  public static void main(String[] args)  {
  MyClassLoader classLoader = new MyClassLoader();
  classLoader.setRoot("/Users/liuxp/tmp");
  Class<?> testClass = null;
  try {
  testClass = classLoader.loadClass("com.paddx.test.classloading.Test");
  Object object = testClass.newInstance();
  System.out.println(object.getClass().getClassLoader());
  } catch (ClassNotFoundException e) {
  e.printStackTrace();
  } catch (InstantiationException e) {
  e.printStackTrace();
  } catch (IllegalAccessException e) {
  e.printStackTrace();
  }
  }
  }
  运行上面的程序,输出结果如下:

  自定义类加载器的核心在于对字节码文件的获取,如果是加密的字节码则需要在该类中对文件进行解密。由于这里只是演示,我并未对class文件进行加密,因此没有解密的过程。这里有几点需要注意:
  1、这里传递的文件名需要是类的全限定性名称,即com.paddx.test.classloading.Test格式的,因为 defineClass 方法是按这种格式进行处理的。
  2、好不要重写loadClass方法,因为这样容易破坏双亲委托模式。
  3、这类 Test 类本身可以被 AppClassLoader 类加载,因此我们不能把 com/paddx/test/classloading/Test.class 放在类路径下。否则,由于双亲委托机制的存在,会直接导致该类由 AppClassLoader 加载,而不会通过我们自定义类加载器来加载。
  四、总结
  双亲委派机制能很好地解决类加载的统一性问题。对一个 Class 对象来说,如果类加载器不同,即便是同一个字节码文件,生成的 Class 对象也是不等的。也是说,类加载器相当于 Class 对象的一个命名空间。双亲委派机制则保证了基类都由相同的类加载器加载,这样避免了同一个字节码文件被多次加载生成不同的 Class 对象的问题。但双亲委派机制仅仅是Java 规范所推荐的一种实现方式,它并不是强制性的要求。近年来,很多热部署的技术都已不遵循这一规则,如 OSGi 技术采用了一种网状的结构,而非双亲委派机制。