ClassLoader浅析

 
类加载到虚拟机当中需要通过该类的ClassLoader完成,通常的ClassLoader有三种:bootStrap classLoader、extClassLaoder、appClassLoader。除此之外还可以由开发者自定义ClassLoader。
ClassLoader以何种规则加载Class呢?java2/jdk1.2推出了“委派双亲”模型(delegating parent model)。该模型的主要思想是,所有这些classLoader形成一条继承链,链条中一个节点代表一个ClassLoader,上一个节点是下一个节点的“parent”,当在A类中加载B类时,加载A类的ClassLoader(称为ClassLoader-A)把加载B类的任务委托给ClassLoader-A的父节点ClassLoader-A1,ClassLoader-A1又委托给它自己的父节点……直到bootStrap classLoader,即链条顶端。若bootStrap加载成功便罢,否则传给它的子节点,如此这般直到ClassLoader-A,若ClassLoader-A依然不能加载则宣判失败,抛出异常ClassNotFoundException。
链条前端的三个节点便是bootStrap、extClassLoader、appClassLoader。用以下代码验证之:
public   static   void  main(String[] args)  {
        ClassLoader cur 
= new MyClass().getClass().getClassLoader();
        
while(cur != null{
            System.out.println(cur.getClass());
            cur 
= cur.getParent();
        }

    }

  
输出结果:
class sun.misc.Launcher$AppClassLoader
class sun.misc.Launcher$ExtClassLoader
可见加载MyClass的加载器便是appClassLoader,其父自然是ExtClassLoader,祖父bootStrap却不见踪影,变成null,何故?bootStrap并非是pure java,乃是用C语言(或许还有汇编?)写成,而ext、app二者却是java所写,因此bootStrap这位幕后英雄便不得以java object的身份示人吧?
那么这三种ClassLoader分别用来加载哪些class呢?依然以代码判断:
public static void main(String[] args) {
       System.out.println("bootStrap??:" + System.getProperty("sun.boot.class.path"));
       System.out.println("extClassLoader??:" + System.getProperty("java.ext.dirs"));
       System.out.println("appClassLoader??:" + System.getProperty("java.class.path"));
    }
Jdk把信息都放到了System所辖的properties当中了,分别以“sun.boot.class.path”、“java.ext.dirs”、“java.class.path”为key自可取出,整理得到如下结果:
ClassLoader
加载的class路径
bootStrap
C:/Program Files/Java/jre1.6.0_01/lib/resources.jar;
C:/Program Files/Java/jre1.6.0_01/lib/rt.jar;
C:/Program Files/Java/jre1.6.0_01/lib/sunrsasign.jar;
C:/Program Files/Java/jre1.6.0_01/lib/jsse.jar;
C:/Program Files/Java/jre1.6.0_01/lib/jce.jar;
C:/Program Files/Java/jre1.6.0_01/lib/charsets.jar;
C:/Program Files/Java/jre1.6.0_01/classes
extClassLoader
C:/Program Files/Java/jre1.6.0_01/lib/ext;
C:/WINDOWS/Sun/Java/lib/ext
appClassLoader
.
可以看到bootStrap用于加载jvm的核心类库,extClassLoader加载的类是相对固定的对核心类的扩展,appClassLoader则加载应用程序类。我若在shell中用set classpath重新设定应用路径自可改变表格里appClassLoader的结果。
下面开始验证“delegating parent”机制并引出压轴大戏context classLoader。定义两个类MyClass、MyOther:
public   class  MyClass  {
public static void main(String[] args) {
        
try {
            Class other_class 
= Class.forName("MyOther");
            exec(other_class);
        }

        
catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }

    }


public static void exec(Class clazz) {
        String arg 
= "my_arg";
        Method[] methods 
= clazz.getMethods();
        Method print_some 
= null;
        
for (int i = 0; i < methods.length; i++{
            
if (methods[i].getName().equals("print_some")) {
                print_some 
= methods[i];
            }

        }

        
if (print_some == null{
            System.out.println(
"The method was not found");
            
return;
        }

        
else {
            
try {
                print_some.invoke(
nullnew Object[] {arg});
            }

            
catch (InvocationTargetException ex) {
                ex.printStackTrace();
            }

            
catch (IllegalArgumentException ex) {
                ex.printStackTrace();
            }

            
catch (IllegalAccessException ex) {
                ex.printStackTrace();
            }

        }

    }

}


public   class  MyOther  {
    
public static void print_some(String some_arg) {
        System.out.println(
"$$$Something in MyOther:" + some_arg);
    }

}

 
(1)在shell中以javac编译,在源码目录(设为${classpath})下得到MyClass.class和MyOther.class,并以“java MyClass”运行,得到结果:
$$$Something in MyOther:my_arg
(2)我把MyOther.class扔到${jre}/lib/ext/classes(设为${ext-path})当中,在${classpath}运行“java MyClass”结果仍然正确。
(3)现在我把MyClass.class也扔到${ext-path}中,并在${ext-path}中运行“java MyClass”,结果当然还是正确的。
(4)最后我把MyOther.class放回${classpath},在shell中设定classpath时加上${classpath},并在${ext-path}中运行MyClass,却得到:
java.lang.ClassNotFoundException: MyOther
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClassInternal(Unknown Source)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Unknown Source)
        at MyClass.func(MyClass.java:9)
        at MyClass.main(MyClass.java:84)
两个 class在同一目录下的情形如(1)、(3)都容易理解,而(2)和(4)则正好验证了“delegating parent”模型。${ext-path}中的class(直接在目录中或者在jar中)正是由extClassLoader来加载,而${classpath}中的class是由appClassLoader加载,其中appClassLoader也叫做System ClassLoader。前面说过extClassLoader是appClassLoader的“parent”。
在这四种情形中,当两个class在同一目录下时会由同一个classLoader加载,因为当前classLoader的祖祖辈辈都找不到MyOther,正好由当前MyClass的类加载器完成对MyOther的加载。
当MyClass在${class-path}中而MyOther在${ ext-path }中时,app-ClassLoader把加载委托给ext-classLoader,后者在委托给bootStrap,bootStrap对此无所作为又还给ext-classLoader,ext-classLoader正好在其辖区找到MyOther,于是乎大功告成。
反之,当MyClass在${ ext-path }当中时,就会由加载MyClass的ext-ClassLoader去加载MyOther,而MyOther却在${classpath}当中,ext-ClassLoader以及其父bootStrap均束手无策,只好告知“ClassNotFoundException”。
这一束手无策就带来了一个问题。当开发诸如jdbc这样的应用时,jdk总是提供一组接口,具体driver的实现则开放给别人。这样接口保持不变并且和具体的jdbc-driver解除了耦合,增强了灵活性。问题在于接口是在jdk层次提供的,即由bootStrap或者extClassLoader加载,而实现类却是由appClassLoader加载,因此在“delegating parent model”下接口调用实现时是找不到实现类的,只有灰头土脸的说“ClassNotFoundException”了。
解决方案就着落在context ClassLoader上,将MyClass代码改为:
public   static   void  main(String[] args)  {
        ClassLoader cls_load 
= Thread.currentThread().getContextClassLoader();
        
try {
            Class other_class 
= cls_load.loadClass("MyOther");
            exec(other_class);
        }

        
catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }

    }

 
如此编译后再把MyClass放到${ext-path},把MyOther放到${classpath},在shell中设定好classpath后在${ext-path}中运行MyClass,结果正确。
每个线程都有一个context classLoader用于加载类和资源,如果该线程未设定就由该线程的父线程来提供。应用程序初始化时当ExtClassLoader和AppClassLoader创建完毕后线程的context ClassLoader就会设为AppClassLoader,因此上述代码从线程上下文中找出AppClassLoader再从${class-path}中就不难找到MyOther,问题解决。
另外一个办法是调用Class.forName的另一个重载版本public static Class,在其中指定classLoader。 forName( String name, boolean initialize, ClassLoader loader)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值