zoukankan      html  css  js  c++  java
  • Java自定义类加载器

     1 //示例:
     2 package com.csair.soc;
     3 
     4 import java.io.IOException;
     5 import java.io.InputStream;
     6 
     7 public class MyClassLoader1   extends ClassLoader{
     8         @Override
     9         public Class<?> loadClass(String name) throws ClassNotFoundException{
    10                try{
    11                      String fileName = name.substring(name.lastIndexOf( ".")+1) + ".class";
    12                      InputStream is = this.getClass().getResourceAsStream(fileName);
    13                       byte[] b = new byte[is.available()];
    14                      is.read(b);
    15                       return defineClass(name, b, 0, b.length );
    16               } catch(IOException e){
    17                       throw new ClassNotFoundException(name);
    18               }
    19        }
    20 }
    21 
    22 
    23 package com.csair.soc;
    24 public class ClassLoaderTest {
    25         public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    26               MyClassLoader1 myLoader = new MyClassLoader1();
    27               Object obj = myLoader.loadClass("com.csair.soc.ClassLoaderTest" ).newInstance();
    28               System. out.println(obj.getClass());
    29               System. out.println(obj.getClass().getClassLoader());
    30               System. out.println(obj instanceof com.csair.soc.ClassLoaderTest);
    31        }
    32 }
    输出结果?
    Exception in thread "main" java.lang.NullPointerException
           at com.csair.soc.MyClassLoader1.loadClass( MyClassLoader1.java:13)
           at java.lang.ClassLoader.defineClass1( Native Method)
           at java.lang.ClassLoader.defineClassCond( ClassLoader.java:631)
           at java.lang.ClassLoader.defineClass( ClassLoader.java:615)
           at java.lang.ClassLoader.defineClass( ClassLoader.java:465)
           at com.csair.soc.MyClassLoader1.loadClass( MyClassLoader1.java:15)
           at com.csair.soc.ClassLoaderTest.main( ClassLoaderTest.java:7)
    为什么在自定义的MyClassLoader1中Override   loadClass会失败?ClassLoaderTest文件在当前目录下,为什么还会报空指针异常?
    在loadClass下,添加以下代码做测试。
    System.out.println(name);
    结果:
    com.csair.soc.ClassLoaderTest
    java.lang.Object
    Exception in thread "main" java.lang.NullPointerException
           at com.csair.soc.MyClassLoader1.loadClass( MyClassLoader1.java:13)
           at java.lang.ClassLoader.defineClass1( Native Method)
           at java.lang.ClassLoader.defineClassCond( ClassLoader.java:631)
           at java.lang.ClassLoader.defineClass( ClassLoader.java:615)
           at java.lang.ClassLoader.defineClass( ClassLoader.java:465)
           at com.csair.soc.MyClassLoader1.loadClass( MyClassLoader1.java:15)
           at com.csair.soc.ClassLoaderTest.main( ClassLoaderTest.java:7)
    为什么要加载两次?要加载的类是ClassLoaderTest,为什么还要加载java.lang.Object?
     
    带着问题,断点跟踪一下:loadClass
    执行MyClassLoader1   的defineClass(name, b, 0, b.length );方法时,其调用顺序如下:
     1 defineClass(name, b, 0, b.length );
     2 
     3 ---> ClassLoader.class
     4  protected final Class<?> defineClass(String name, byte[] b , int off, int len)
     5         throws ClassFormatError
     6     {
     7         return defineClass(name, b, off, len, null);
     8     }
     9 
    10 -->
    11 
    12 protected final Class<?> defineClass(String name, byte[] b, int off, int len,
    13                                    ProtectionDomain protectionDomain)
    14         throws ClassFormatError
    15     {
    16          return defineClassCond(name, b, off, len, protectionDomain, true);
    17     }
    18 
    19 --->
    20 
    21 private final Class<?> defineClassCond(String name,
    22                                            byte[] b, int off, int len,
    23                                            ProtectionDomain protectionDomain,
    24                                            boolean verify)
    25         throws ClassFormatError
    26     {
    27        protectionDomain = preDefineClass(name, protectionDomain);
    28 
    29        Class c = null;
    30         String source = defineClassSourceLocation(protectionDomain);
    31 
    32         try {
    33            c = defineClass1(name, b, off, len, protectionDomain, source,
    34                              verify);
    35        } catch (ClassFormatError cfe) {
    36            c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,
    37                                        source, verify);
    38        }
    defineClass1也就是提示程序出错的位置
    看看defineClass1方法是什么?在ClassLoader中,定义如下:
     private native Class defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source,
                                          boolean verify);
    也就是说defineClass1是native方法,继续跟踪代码,就发现又调用到自定义的loadClass方法中,此时传入的参数则变成了java.lang.Object。
    根据这些可以猜测,类的加载,会将其所有的父类都加载一遍,直到java.lang.Object。
    为了验证,这个猜想,写出以下示例。
    public class MyClassLoader1   extends ClassLoader{
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException{
                   try{
                         System. out.println(name);
                         String fileName = name.substring(name.lastIndexOf( ".")+1) + ".class";
                         InputStream is = this.getClass().getResourceAsStream(fileName);
                          byte[] b = new byte[is.available()];
                         is.read(b);
                          return defineClass(name, b, 0, b.length );
                  } catch(IOException e){
                          throw new ClassNotFoundException(name);
                  }
           }
    }
     
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
                  MyClassLoader1 myLoader = new MyClassLoader1();
                  Object obj = myLoader.loadClass("com.csair.soc.SubSample" ).newInstance();
           }
    SubSample有父类Sample。
    输出结果:
    com.csair.soc.SubSample
    com.csair.soc.Sample
    java.lang.Object
    Exception in thread "main" java.lang.NullPointerException
           at com.csair.soc.MyClassLoader1.loadClass( MyClassLoader1.java:13 )
           at java.lang.ClassLoader.defineClass1( Native Method )
           at java.lang.ClassLoader.defineClassCond( ClassLoader.java:631 )
           at java.lang.ClassLoader.defineClass( ClassLoader.java:615 )
           at java.lang.ClassLoader.defineClass( ClassLoader.java:465 )
           at com.csair.soc.MyClassLoader1.loadClass( MyClassLoader1.java:15 )
           at com.csair.soc.ClassLoaderTest.main( ClassLoaderTest.java:7 )
    测试结果和猜测的一样,ClassLoader在加载类的同时,会通过native方法defineClass1,将其所有的父类都加载。当ClassLoader加载父类时,由于loadClass方法被重写,defineClass1会调用自定义的classLoader方法加载父类,因此出现以上错误。过程如下:
    为了解决这个问题,可以使用以下方式,修改loadClass,当找不到类文件时,使用父类的ClassLoader试试。
      @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException{
                   try{
                         String fileName = name.substring(name.lastIndexOf( ".")+1) + ".class";
                         InputStream is = this.getClass().getResourceAsStream(fileName);
                          if(is == null ){
                                return super .loadClass(name);
                         }
                          byte[] b = new byte[is.available()];
                         is.read(b);
                          return defineClass(name, b, 0, b.length );
                  } catch(IOException e){
                          throw new ClassNotFoundException(name);
                  }
           }
    但最好的办法是不重写loadClass方法,而是重写findClass方法,同样可以达到目的。
    这点在ClassLoader的loadClass方法的注释中有提及
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
         * #findClass(String)}, rather than this method.  </p>
     
    重写findClass后,代码如下:
    @Override
            public Class<?> findClass(String name) throws ClassNotFoundException{
                   try{
                         String fileName = name.substring(name.lastIndexOf( ".")+1) + ".class";
                         InputStream is = this.getClass().getResourceAsStream(fileName);
                          byte[] b = new byte[is.available()];
                         is.read(b);
                          return defineClass(name, b, 0, b.length );
                  } catch(IOException e){
                          throw new ClassNotFoundException(name);
                  }
           }
    输出结果:
    class com.csair.soc.ClassLoaderTest
    com.csair.soc.MyClassLoader@bfc8e0
    false
    可以看到类的加载是使用自定义的类加载器,在判断obj instanceof com.csair.soc.ClassLoaderTest时,由于默认的com.csair.soc.ClassLoaderTest使用的是系统类加载器,因此输出为false。
    当然,也只有在父类加载器找不到类文件的时候,才会调用子类的findClass方法去寻找类文件。
  • 相关阅读:
    聚合根、实体、值对象
    哀悼的CSS 把网站变成灰色
    Ubuntu13.04更换aptget源
    JS判断用户终端,跳转到不同的页面.
    分享一个使用的FireFox 截图插件小巧方便
    Linux 下面FireFox 看CCTV直播
    Ubuntu 11.10后 Guest账户禁用!
    修改Ubuntu的启动画面plymouth
    ubuntu开机自启动小键盘
    Linux 用cat做图片种子||Windows 用copy做图片种子
  • 原文地址:https://www.cnblogs.com/pfxiong/p/4118445.html
Copyright © 2011-2022 走看看