首页 > 代码库 > 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 ClassFormatError15 {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 ClassFormatError26 {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方法去寻找类文件。
Java自定义类加载器
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。