当前所在位置:珠峰网资料 >> 计算机 >> 计算机等级考试 >> 正文
计算机二级辅导:java类装载(3)
发布时间:2010/3/13 9:08:46 来源:城市学习网 编辑:MOON
  我们不难发现,图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 1
  my static field b is 2
  程序的结果非常有意思,从编程者的角度,我们甚至可以把不在同一个分支的类装载器看作不同的java虚拟机,因为它们彼此觉察不到对方的存在。程序在使用具有分支的类装载的体系结构时要非常小心,弄清楚每个类装载器的类查找范围,尽量避免父类装载器和子类装载器的类查找范围中有相同类名的类(包括包名和类名),下面这个例子就是用来说明这种情况可能带来的问题。
  (6) 类如何被装载及类被装载的方式(Java类装载体系中的隔离性 盛戈歆)
  在java2中,JVM是如何装载类的呢,可以分为两种类型,一种是隐式的类装载,一种式显式的类装载。
  2.1 隐式的类装载
  隐式的类装载是编码中最常用得方式:
  A b = new A();
  如果程序运行到这段代码时还没有A类,那么JVM会请求装载当前类的类装器来装载类。问题来了,我把代码弄得复杂一点点,但依旧没有任何难度,请思考JVM得装载次序:
  package test;
  Public class A{
  public void static main(String args){
  B b = new B();
  }
  }
  class B{C c;}
  class C{}
  揭晓答案,类装载的次序为A-B,而类C根本不会被JVM理会,先不要惊讶,仔细想想,这不正是我们最需要得到的结果。我们仔细了解一下JVM装载顺序。当使用Java A命令运行A类时,JVM会首先要求类路径类装载器(AppClassLoader)装载A类,但是这时只装载A,不会装载A中出现的其他类(B类),接着它会调用A中的main函数,直到运行语句b = new B()时,JVM发现必须装载B类程序才能继续运行,于是类路径类装载器会去装载B类,虽然我们可以看到B中有有C类的声明,但是并不是实际的执行语句,所以并不去装载C类,也就是说JVM按照运行时的有效执行语句,来决定是否需要装载新类,从而装载尽可能少的类,这一点和编译类是不相同的。
  2.2 显式的类装载
  使用显示的类装载方法很多,我们都装载类test.A为例。
  使用Class类的forName方法。它可以指定装载器,也可以使用装载当前类的装载器。例如:
  Class.forName("test.A");
  它的效果和
  Class.forName("test.A",true,this.getClass().getClassLoader());
  是一样的。
  使用类路径类装载装载.
  ClassLoader.getSystemClassLoader().loadClass("test.A");
  使用当前进程上下文的使用的类装载器进行装载,这种装载类的方法常常被有着复杂类装载体系结构的系统所使用。
  Thread.currentThread().getContextClassLoader().loadClass("test.A")
  使用自定义的类装载器装载类
  public class MyClassLoader extends URLClassLoader{
  public MyClassLoader() {
  super(new URL[0]);
  }
  }
  MyClassLoader myClassLoader = new MyClassLoader();
  myClassLoader.loadClass("test.A");
  MyClassLoader继承了URLClassLoader类,这是JDK核心包中的类装载器,在没有指定父类装载器的情况下,类路径类装载器就是它的父类装载器,MyClassLoader并没有增加类的查找范围,因此它和类路径装载器有相同的效果。
  (7)ClassLoader的一些方法实现的功能:
  方法 loadClass
  ClassLoader.loadClass() 是 ClassLoader 的入口点。其特征
  Class loadClass( String name, boolean resolve ); name 参数指定了 JVM 需要的类的名称,该名称以包表示法表示,如 Foo 或 java.lang.Object。
  resolve 参数告诉方法是否需要解析类。在准备执行类之前,应考虑类解析。并不总是需要解析。如果 JVM 只需要知道该类是否存在或找出该类的超类,那么就不需要解析。
  在 Java 版本 1.1 和以前的版本中,loadClass 方法是创建定制的 ClassLoader 时唯一需要覆盖的方法。(Java 2 中 ClassLoader 的变动提供了关于 Java 1.2 中 findClass() 方法的信息。)
  方法 defineClass
  defineClass 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。
  defineClass 管理 JVM 的许多复杂、神秘和倚赖于实现的方面它把字节码分析成运行时数据结构、校验有效性等等。不必担心,您无需亲自编写它。事实上,即使您想要这么做也不能覆盖它,因为它已被标记成最终的。
  你可以看见native标记,知道defineClass是一个jni调用的方法,是由c++实现数据到内存的加载的;
  方法 findSystemClass
  findSystemClass 方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。(Java 2 中 ClassLoader 的变动提供了关于 Java 版本 1.2 这个过程变动的详细信息。)
  对于定制的 ClassLoader,只有在尝试其它方法装入类之后,再使用 findSystemClass。原因很简单:ClassLoader 是负责执行装入类的特殊步骤,不是负责所有类。例如,即使 ClassLoader 从远程的 Web 站点装入了某些类,仍然需要在本地机器上装入大量的基本 Java 库。而这些类不是我们所关心的,所以要 JVM 以缺省方式装入它们:从本地文件系统。这就是 findSystemClass 的用途。
  其工作流程
  请求定制的 ClassLoader 装入类。
  检查远程 Web 站点,查看是否有所需要的类。
  如果有,那么好;抓取这个类,完成任务。
  如果没有,假定这个类是在基本 Java 库中,那么调用 findSystemClass,使它从文件系统装入该类。
  在大多数定制 ClassLoaders 中,首先调用 findSystemClass 以节省在本地就可以装入的许多 Java 库类而要在远程 Web 站点上查找所花的时间。然而,正如,在下一章节所看到的,直到确信能自动编译我们的应用程序代码时,才让 JVM 从本地文件系统装入类。
  方法 resolveClass
  正如前面所提到的,可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的 loadClass 时,可以调用 resolveClass,这取决于 loadClass 的 resolve 参数的值。
  方法 findLoadedClass
  findLoadedClass 充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看ClassLoader 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法。
  每个类装载器有自己的命名空间,命名空间由所有以此装载器为创始类装载器的类组成。不同命名空间的两个类是不可见的,但只要得到类所对应的Class对象的reference,还是可以另一命名空间的类。
  例2演示了一个命名空间的类如何使用另一命名空间的类。在例子中,LoaderSample2由系统类装载器装载,LoaderSample3 由自定义的装载器loader负责装载,两个类不在同一命名空间,但LoaderSample2得到了LoaderSample3所对应的Class对象的reference,所以它可以LoaderSampl3中公共的成员(如age)。
  例2不同命名空间的类的
  /*LoaderSample2.java*/
  import java.net. * ;
  import java.lang.reflect. * ;
  public class LoaderSample2 {
  public static void main(String args) {
  try {
  String path = System.getProperty( " user.dir " );
  URL us = { new URL( " file:// " + path + " /sub/ " )};
  ClassLoader loader = new URLClassLoader(us);
  Class c = loader.loadClass( " LoaderSample3 " );
  Object o = c.newInstance();
  Field f = c.getField( " age " );
  int age = f.getInt(o);
  System.out.println( " age is " + age);
  } catch (Exception e) {
  e.printStackTrace();
  }
  }
  }
  /*sub/Loadersample3.java*/
  public class LoaderSample3 {
  static {
  System.out.println( " LoaderSample3 loaded " );
  }
  public int age = 30 ;
  }
  编译:javac LoaderSample2.java; javac sub/LoaderSample3.java
  运行:java LoaderSample2
  LoaderSample3 loaded
  age is 30
  从运行结果中可以看出,在类LoaderSample2中可以创建处于另一命名空间的类LoaderSample3中的对象并可以其公共成员age。
  运行时包(runtime package)
  由同一类装载器定义装载的属于相同包的类组成了运行时包,决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看的定义类装载器是否相同。只有属于同一运行时包的类才能互相包可见的类和成员。这样的限制避免了用户自己的代码冒充核心类库的类核心类库包可见成员的情况。假设用户自己定义了一个类java.lang.Yes,并用用户自定义的类装载器装载,由于java.lang.Yes和核心类库java.lang.* 由不同的装载器装载,它们属于不同的运行时包,所以java.lang.Yes不能核心类库java.lang中类的包可见的成员。
  (7)有关ClassLoader的重载
  扩展ClassLoader方法
  我们目的是从本地文件系统使用我们实现的类装载器装载一个类。为了创建自己的类装载器我们应该扩展ClassLoader类,这是一个抽象类。我们创建一个FileClassLoader extends ClassLoader。我们需要覆盖ClassLoader中的findClass(String name)方法,这个方法通过类的名字而得到一个Class对象。
  public Class findClass(String name) {
  bytedata = loadClassData(name);
  return defineClass(name, data, 0 , data.length);
  }
  我们还应该提供一个方法loadClassData(String name),通过类的名称返回class文件的字
  节数组。然后使用ClassLoader提供的defineClass()方法我们就可以返回Class对象了。
  publicteloadClassData(String name) {
  FileInputStream fis = null ;
  bytedata = null ;
  try {
  fis = new FileInputStream( new File(drive + name + fileType));
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  int ch = 0 ;
  while ((ch = fis.read()) != - 1 ) {
  baos.write(ch);
  }
  data = baos.toByteArray();
  } catch (IOException e) {
  e.printStackTrace();
  }
  return data;
  }
  :
广告合作:400-664-0084 全国热线:400-664-0084
Copyright 2010 - 2017 www.my8848.com 珠峰网 粤ICP备15066211号
珠峰网 版权所有 All Rights Reserved