首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

Java种装载体系中的隔离性

2012-12-19 
Java类装载体系中的隔离性图2:某个程序的类装载器的结构解释一下上面的图,ClassLoaderA为自定义的类装载器

Java类装载体系中的隔离性

图2:某个程序的类装载器的结构

解释一下上面的图,ClassLoaderA为自定义的类装载器,它的父类装载器为类路径装载器,它有两个子类装载器ClassLoaderAA和ClassLaderAB,ClassLoaderB为程序使用的另外一个类装载器,它没有父类装载器,但有一个子类装载器ClassLoaderBB。你可能会说,见鬼,我的程序怎么会使用这么复杂的类装载器结构。为了进行下面的讨论,暂且委屈一下。

3. 奇怪的隔离性

我们不难发现,图2中的类装载器AA和AB, AB和BB,AA和B等等位于不同分支下,他们之间没有父子关系,我不知道如何定义这种关系,姑且称他们位于不同分支下。两个位于不同分支的类装载器具有隔离性,这种隔离性使得在分别使用它们装载同一个类,也会在内存中出现两个Class类的实例。因为被具有隔离性的类装载器装载的类不会共享内存空间,使得使用一个类装载器不可能完成的任务变得可以轻而易举,例如类的静态变量可能同时拥有多个值(虽然好像作用不大),因为就算是被装载类的同一静态变量,它们也将被保存不同的内存空间,又例如程序需要使用某些包,但又不希望被程序另外一些包所使用,很简单,编写自定义的类装载器。类装载器的这种隔离性在许多大型的软件应用和服务程序得到了很好的应用。下面是同一个类静态变量为不同值的例子。

package test;public class A {  public static void main( String[] args ) {    try {      //定义两个类装载器      MyClassLoader aa= new MyClassLoader();      MyClassLoader bb = new MyClassLoader();      //用类装载器aa装载testb.B类      Class clazz=aa.loadClass("testb. B");      Constructor constructor=         clazz.getConstructor(new Class[]{Integer.class});      Object object =     constructor.newInstance(new Object[]{new Integer(1)});      Method method =     clazz.getDeclaredMethod("printB",new Class[0]);      //用类装载器bb装载testb.B类      Class clazz2=bb.loadClass("testb. B");      Constructor constructor2 =         clazz2.getConstructor(new Class[]{Integer.class});      Object object2 =     constructor2.newInstance(new Object[]{new Integer(2)});      Method method2 =     clazz2.getDeclaredMethod("printB",new Class[0]);      //显示test.B中的静态变量的值       method.invoke( object,new Object[0]);      method2.invoke( object2,new Object[0]);    } catch ( Exception e ) {      e.printStackTrace();    }  }}

?

//Class B 必须位于MyClassLoader的查找范围内,//而不应该在MyClassLoader的父类装载器的查找范围内。package testb;public class B {    static int b ;    public B(Integer testb) {        b = testb.intValue();    }    public void printB() {        System.out.print("my static field b is ", b);    }}

?

public class MyClassLoader extends URLClassLoader{  private static File file = new File("c:\\classes ");  //该路径存放着class B,但是没有class A  public MyClassLoader() {    super(getUrl());  }  public static URL[] getUrl() {    try {      return new URL[]{file.toURL()};    } catch ( MalformedURLException e ) {      return new URL[0];    }  }}

程序的运行结果为:

my static field b is 1my static field b is 2

程序的结果非常有意思,从编程者的角度,我们甚至可以把不在同一个分支的类装载器看作不同的java虚拟机,因为它们彼此觉察不到对方的存在。程序在使用具有分支的类装载的体系结构时要非常小心,弄清楚每个类装载器的类查找范围,尽量避免父类装载器和子类装载器的类查找范围中有相同类名的类(包括包名和类名),下面这个例子就是用来说明这种情况可能带来的问题。

假设有相同名字却不同版本的接口 A,

版本 1:package test;Intefer Same{ public String getVersion(); }版本 2:Package test;Intefer Same{ public String getName(); }

接口A两个版本的实现:

版本1的实现package test;public class Same1Impl implements Same {public String getVersion(){ return "A version 1";}}版本2的实现public class Same 2Impl implements Same {public String getName(){ return "A version 2";}}

我们依然使用图2的类装载器结构,首先将版本1的Same和Same的实现类Same1Impl打成包same1.jar,将版本2的Same和Same的实现类Same1Impl打成包same2.jar。现在,做这样的事情,把same1.jar放入类装载器ClassLoaderA的类查找范围中,把same2.jar放入类装器ClassLoaderAB的类查找范围中。当你兴冲冲的运行下面这个看似正确的程序。

实际上这个错误的是由父类载器优先装载的机制造成,当类装载器ClassLoaderAB在装载Same2Impl类时发现必须装载接口test.Same,于是按规定请求父类装载器装载,父类装载器发现了版本1的test.Same接口并兴冲冲的装载,但是却想不到Same2Impl所希望的是版本2 的test.Same,后面的事情可想而知了,异常被抛出。

我们很难责怪Java中暂时并没有提供区分版本的机制,如果使用了比较复杂的类装载器体系结构,在出现了某个包或者类的多个版本时,应特别注意。

掌握和灵活运用Java的类装载器的体系结构,对程序的系统设计,程序的实现,已经程序的调试,都有相当大的帮助。希望以上的内容能够对您有所帮助。

热点排行