Java自定义类的加载器

一、ClassLoader作用

Java程序在运行的时候,JVM通过类加载机制(ClassLoader)把class文件加载到内存中,只有class文件被载入内存,才能被其他class引用,使程序正确运行起来.


二、ClassLoader的分类

Java中的ClassLoader有三种:Bootstrap ClassLoader 、Extension ClassLoader、App ClassLoader。

1. Bootstrap ClassLoader

由C++写的,由JVM启动.

启动类加载器,负责加载java基础类,对应的文件是%JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等

2.Extension ClassLoader

Java类,继承自URLClassLoader 扩展类加载器,

对应的文件是 %JRE_HOME/lib/ext 目录下的jar和class等

3.App ClassLoader

Java类,继承自URLClassLoader 系统类加载器,

对应的文件是应用程序classpath目录下的所有jar和class等


三、ClassLoader的加载机制

Java的加载机制是双亲委派机制来加载类,

 

为什么要使用这种方式?这个是为了保证 如果加载的类是一个系统类,那么会优先由Bootstrap ClassLoader 、Extension ClassLoader先去加载,而不是使用我们自定义的ClassLoader去加载,保证系统的安全!

这三种类加载器存在父子关系,App ClassLoader的父类加载器是Extension ClassLoader,Extension ClassLoader的父类加载器是Bootstrap ClassLoader,要注意的一点是,这里的父子并不是继承关系.

 
  1. public class ClassLoaderParentMain {

  2.  
  3. public static void main(String[] args){

  4. ClassLoader ClassLoader1 = ClassLoaderParentMain.class.getClassLoader();

  5. ClassLoader ClassLoader2 = ClassLoader1.getParent();

  6. ClassLoader ClassLoader3 = ClassLoader2.getParent();

  7.  
  8. System.out.println(ClassLoader1);

  9. System.out.println(ClassLoader2);

  10. System.out.println(ClassLoader3);

  11. }

  12. }

输出的结果为:

Java自定义类的加载器

 

当这三者中的某个ClassLoader要加载一个类时,会先委托它的父类加载器尝试加载,一直往上,如果最顶级的父类加载器没有找到该类,那么委托者则亲自到特定的地方加载

 
  1. protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

  2. synchronized (getClassLoadingLock(name)) { // 首先,检测是否已经加载

  3. Class<?> c = findLoadedClass(name);

  4. if (c == null) {

  5. long t0 = System.nanoTime();

  6. try {

  7. if (parent != null) { //父加载器不为空则调用父加载器的loadClass

  8. c = parent.loadClass(name, false);

  9. } else { //父加载器为空则调用Bootstrap Classloader

  10. c = findBootstrapClassOrNull(name);

  11. }

  12. } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader

  13. }

  14. if (c == null) { // If still not found, then invoke findClass in order // to find the class.

  15. long t1 = System.nanoTime(); //父加载器没有找到,则调用findclass

  16. 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();

  17. }

  18. }

  19. if (resolve) { //调用resolveClass()

  20. resolveClass(c);

  21. }

  22. return c;

  23. }

  24. }

先加载Extension ClassLoader ,如果没有加载到,那么使用Bootstrap ClassLoader去加载,如果都没有,那么使用App ClassLoader去加载。如果没找到,那么就抛出异常ClassNotFoundException.

这里画张图来表示下

Java自定义类的加载器

只有被同一个类加载器实例加载并且文件名相同的class文件才被认为是同一个class.

 


四、自定义ClassLoader

1、为什么要自定义ClassLoader

因为系统的ClassLoader只会加载指定目录下的class文件,如果你想加载自己的class文件,那么就可以自定义一个ClassLoader.

而且我们可以根据自己的需求,对class文件进行加密和解密。

2.如何自定义ClassLoader

2.1

新建一个类继承自java.lang.ClassLoader,重写它的findClass方法。

2.2

将class字节码数组转换为Class类的实例

2.3

调用loadClass方法即可

下面的代码就是自定义Class的例子

 
  1. public class MyClassLoader extends ClassLoader {

  2. //指定路径

  3. private String path ;

  4.  
  5.  
  6. public MyClassLoader(String classPath){

  7. path=classPath;

  8. }

  9.  
  10. /**

  11. * 重写findClass方法

  12. * @param name 是我们这个类的全路径

  13. * @return

  14. * @throws ClassNotFoundException

  15. */

  16. @Override

  17. protected Class<?> findClass(String name) throws ClassNotFoundException {

  18. Class log = null;

  19. // 获取该class文件字节码数组

  20. byte[] classData = getData();

  21.  
  22. if (classData != null) {

  23. // 将class的字节码数组转换成Class类的实例

  24. log = defineClass(name, classData, 0, classData.length);

  25. }

  26. return log;

  27. }

  28.  
  29. /**

  30. * 将class文件转化为字节码数组

  31. * @return

  32. */

  33. private byte[] getData() {

  34.  
  35. File file = new File(path);

  36. if (file.exists()){

  37. FileInputStream in = null;

  38. ByteArrayOutputStream out = null;

  39. try {

  40. in = new FileInputStream(file);

  41. out = new ByteArrayOutputStream();

  42.  
  43. byte[] buffer = new byte[1024];

  44. int size = 0;

  45. while ((size = in.read(buffer)) != -1) {

  46. out.write(buffer, 0, size);

  47. }

  48.  
  49. } catch (IOException e) {

  50. e.printStackTrace();

  51. } finally {

  52. try {

  53. in.close();

  54. } catch (IOException e) {

  55.  
  56. e.printStackTrace();

  57. }

  58. }

  59. return out.toByteArray();

  60. }else{

  61. return null;

  62. }

  63.  
  64.  
  65. }

  66. }

可以再getData里面做很多事情 ,比如加密解密之类的 都是可以的。

 

1、我们生成一个.class文件的测试样品

 
  1. public class Log {

  2. public static void main(String[] args) {

  3. System.out.println("load Log class successfully");

  4. }

  5. }

2、我们执行命令行 javac Log.java 生成我们的Log.class文件

Java自定义类的加载器

3、再main函数入口 写配置参数

 
  1. public class ClassLoaderMain {

  2.  
  3. public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {

  4. //这个类class的路径

  5. String classPath = "/Users/mac/CommProjects/ClassLoaderDemo/src/com/xuhuawei/classloaderdemo/Log.class";

  6.  
  7. MyClassLoader myClassLoader = new MyClassLoader(classPath);

  8. //类的全称

  9. String packageNamePath = "com.xuhuawei.classloaderdemo.Log";

  10.  
  11. //加载Log这个class文件

  12. Class<?> Log = myClassLoader.loadClass(packageNamePath);

  13.  
  14. System.out.println("类加载器是:" + Log.getClassLoader());

  15.  
  16. //利用反射获取main方法

  17. Method method = Log.getDeclaredMethod("main", String[].class);

  18. Object object = Log.newInstance();

  19. String[] arg = {"ad"};

  20. method.invoke(object, (Object) arg);

  21. }

  22.  
  23.  
  24. }

输出的结果为:

Java自定义类的加载器