zoukankan      html  css  js  c++  java
  • 深入类别载入器 forName() loadClass()

    转自:http://kevin_yang.itpub.net/post/172/31025

     1.java执行

    java.exe 是利用几个基本原则来寻找Java

    Runtime Environment(JRE),然后把类别档(.class)直接转交给JRE 执行之后,java.exe   就功成身退。类别加载器也是构成JRE 的其中一个重要成员,所以最后类别加载器就会自动从所在之JRE 目录底下的librt.jar 载入基础类别函式库。所以在上图里,一定是因为java.exe 定位到c:j2sdk1.4.0jre,所以才会有此输出结果。

    2.预先加载与依需求加载

    像基础类别函式库这样的加载方法我们叫做预先加载(pre-loading),这是因为基础类别函式库里头的类别大多是Java 程序执行时所必备的类别,所以为了不要老是做浪费时间的I/O 动作(读取档案系统,然后将类别文件加载内存之中),预先加载这些类别会让Java 应用程序在执行时速度稍微快一些。相对来说,我们自己所撰写的类别之加载方式,叫做依需求加载(load-on-demand),也就是Java 程序真正用到该类别的时候,才真的把类别文件从档案系统之中加载内存

     

    3.Java 提供两种方法来达成动态性

    一种是隐式的(implicit),另一种是显式的(explicit)。

    隐式的(implicit)方法我们已经谈过了,也就是当程序设计师用到new 这个Java 关键词时,会让类别加载器依需求加载您所需要的类别,这种方式使用了隐式的(implicit)方法。显式的方法,又分成两种方式,一种是藉由java.lang.Class 里的forName()方法,另一种则是藉由java.lang.ClassLoader 里的loadClass()方法。

    4.forName方法

    public static Class forName(String name, boolean initialize,

    ClassLoader loader)

    这两个方法,最后都是连接到原生方法forName0(),其宣告如下:

    private static native Class forName0(String name, boolean initialize, ClassLoader loader)

    throws ClassNotFoundException;只有一个参数的forName()方法,最后叫用的是:

    forName0(className, true, ClassLoader.getCallerClassLoader());而具有三个参数的forName()方法,最后叫用的是:forName0(name, initialize, loader);initialized的用法:true,表示载入实例的同时也载入静态初始化区块;false,那么就只会命令类别加载器加载该类别,但不会叫用其静态初始化区块,只有等到整个程序第一次实体化某个类别时,静态初始化区块才会被调用。

    档案:Office.java

    public class Office

    {

    public static void main(String args[]) throws Exception

    {

    Office off = new Office() ;

    System.out.println("类别准备载入") ;

    Class c =

    Class.forName(args[0],false,off.getClass().getClassLoader()) ;

    System.out.println("类别准备实体化") ;

    Object o = c.newInstance() ;

    Object o2 = c.newInstance() ;

    }

    }

    则输出变成:

     

    5用显式的方法来达成动态性:直接使用类别加载器

    这种情形与使用Class 类别的forName()方法时,第二个参数传入false

    几乎是相同的结果。

    档案:Office.java

    public class Office

    {

    public static void main(String args[]) throws Exception

    {

    Office off = new Office() ;

    System.out.println("类别准备载入") ;

    ClassLoader loader = off.getClass().getClassLoader() ;

    Class c = loader.loadClass(args[0]) ;

    System.out.println("类别准备实体化") ;

    Object o = c.newInstance() ;

    Object o2 = c.newInstance() ;

    }

    }

    执行

    java Office Word

    6自己建立类别加载器来加载类别

    档案:Office.java

    import java.net.* ;

    public class Office

    {

    public static void main(String args[]) throws Exception

    {

    URL u = new URL("file:/d:/my/lib/") ;

    URLClassLoader ucl = new URLClassLoader(new URL[]{ u }) ;

    Class c = ucl.loadClass(args[0]) ;

    Assembly asm = (Assembly) c.newInstance() ;

    asm.start() ;

    }

    }

    7类别被哪个类别载入器载入

    我们将上述的程序代码稍作修改,修改后的程序代码如下:

    档案:Office.java

    import java.net.* ;

    public class Office

    {

    public static void main(String args[]) throws Exception

    {

    URL u = new URL("file:/d:/my/lib/") ;

    URLClassLoader ucl = new URLClassLoader(new URL[]{ u }) ;

    Class c = ucl.loadClass(args[0]) ;

    Assembly asm = (Assembly) c.newInstance() ;

    asm.start() ;

    URL u1 = new URL("file:/d:/my/lib/") ;

    URLClassLoader ucl1 = new URLClassLoader(new URL[]{ u1 }) ;

    Class c1 = ucl1.loadClass(args[0]) ;

    Assembly asm1 = (Assembly) c1.newInstance() ;

    asm1.start() ;

    System.out.println(Office.class.getClassLoader()) ;

    System.out.println(u.getClass().getClassLoader()) ;

    System.out.println(ucl.getClass().getClassLoader()) ;

    System.out.println(c.getClassLoader()) ;

    System.out.println(asm.getClass().getClassLoader()) ;

    System.out.println(u1.getClass().getClassLoader()) ;

    System.out.println(ucl1.getClass().getClassLoader()) ;

    System.out.println(c1.getClassLoader()) ;

    System.out.println(asm1.getClass().getClassLoader()) ;

    }

    }

    执行后输出结果如下图:

    从输出中我们可以得知,Office.class 由AppClassLoader(又称做System Loader,系统加载器)所加载,URL.class 与URLClassLoader.class 由Bootstrap Loader 所加载(注意:输出null 并非代表不是由类别载入器所载入。在Java 之中,所有的类别都必须由类别加载器加载才行,只不过Bootstrap Loader 并非由Java 所撰写而成,而是由C++实作而成,因此以Java 的观点来看,逻辑上并没有Bootstrap Loader 的类别实体)。而Word.class 分别由两个不同的URLClassLoader 实体加载。至于Assembly.class,本身应该是由AppClassLoader 加载,但是由于多型(Polymorphism)的关系,所指向的类别实体(Word.class)由特定的加载器所加载,导致打印在屏幕上的内容是其所参考的类别实体之类别加载器。Interface 这种型态本身无法直接使用new 来产生实体,所以在执行getClassLoader()的时候,叫用的一定是所参考的类别实体的getClassLoader(),要知道Interface 本身由哪个类别加载器加载,您必须使用底下程序代码:Assembly.class.getClassLoader()

     

    8一切都是由Bootstrap Loader 开始 : 类别载入器的阶层体系

    当我们在命令列输入java xxx.class 的时候,java.exe 根据我们之前所提过的逻辑找到了

    JRE(Java Runtime Environment),接着找到位在JRE 之中的jvm.dll(真正的Java 虚拟机器),最后加载这个动态联结函式库,启动Java 虚拟机器。这个动作的详细介绍请回头参阅第一章。虚拟机器一启动,会先做一些初始化的动作,比方说抓取系统参数等。一旦初始化动作完成之后,就会产生第一个类别载入器,即所谓的Bootstrap Loader,Bootstrap Loader 是由C++所撰写而成(所以前面我们说,以Java 的观点来看,逻辑上并不存在Bootstrap Loader 的类别实体,所以在Java 程序代码里试图印出其内容的时候,我们会看到的输出为null),这个Bootstrap Loader 所做的初始工作中,除了也做一些基本的初始化动作之外,最重要的就是加载定义在sun.misc 命名空间底下的Launcher.java 之中的ExtClassLoader(因为是inner class,所以编译之后会变成

    Launcher$ExtClassLoader.class),并设定其Parent 为null,代表其父加载器为Bootstrap

    Loader。然后Bootstrap Loader 再要求加载定义于sun.misc 命名空间底下的Launcher.java 之中的AppClassLoader(因为是inner class,所以编译之后会变成Launcher$AppClassLoader.class),并设定其Parent 为之前产生的ExtClassLoader 实体。这里要请大家注意的是,Launcher$ExtClassLoader.class 与Launcher$AppClassLoader.class 都是由Bootstrap Loader 所加载,所以Parent 和由哪个类别加载器加载没有关系。我们可以用下图来表示:

     

    这个由Bootstrap Loader   ExtClassLoader   AppClassLoader,就是我们所谓「类别载入

    器的阶层体系」。

    在此要请大家注意的是,AppClassLoader 和ExtClassLoader 都是URLClassLoader 的子类别。由于它们都是URLClassLoader 的子类别,所以它们也应该有URL 作为搜寻类别档的参考,由原始码中我们可以得知,AppClassLoader 所参考的URL 是从系统参数java.class.path 取出的字符串所决定,而java.class.path 则是由我们在执行java.exe 时,利用 –cp 或-classpath 或CLASSPATH 环境变量所决定。我们可以用底下程序代码测试之:

    档案:test.java

    public class test

    {

    public static void main(String args[])

    {

    String s = System.getProperty("java.class.path");

    System.out.println(s) ;

    }

    }

    输出结果如下:

    从这个输出结果,我们可以看出,在预设情况下,AppClassLoader 的搜寻路径为”.”(目前所在目录),如果使用-classpath 选项(与-cp 等效),就可以改变AppClassLoader 的搜寻路径,如果没有指定-classpath 选项,就会搜寻环境变量CLASSPATH。如果同时有CLASSPATH 的环境设定与-classpath 选项,则以-classpath 选项的内容为主,CLASSPATH 的环境设定与-classpath 选项两者的内容不会有加成的效果。

    至于ExtClassLoader 也有相同的情形,不过其搜寻路径是参考系统参数 java.ext.dirs。我们可以用底下程序代码测试:

    档案:test.java

    public class test

    {

    public static void main(String args[])

    {

    String s = System.getProperty("java.ext.dirs");

    System.out.println(s) ;

    }

    }

    输出结果如下:

    输出结果告诉我们,系统参数java.ext.dirs 的内容,会指向java.exe 所选择的JRE 所在位置下的

    libext 子目录。系统参数java.ext.dirs 的内容可以在一开始下命列的时候来更改,如下:

    最后一个类别加载器是Bootstrap Loader , 我们可以经由查询由系统参数

    sun.boot.class.path 得知Bootstrap Loader 用来搜寻类别的路径。请使用底下的程序代码测试之:

    档案:test.java

    public class test

    {

    public static void main(String args[])

    {

    String s = System.getProperty("sun.boot.class.path");

    System.out.println(s) ;

    }

    }

    输出结果如下:

    系统参数sun.boot.class.path 的内容可以在一开始下命列的时候来更改,如下:

     

    从这三个类别加载器的搜寻路径所参考的系统参数的名字中,其实还透漏了一个讯息。请回头看到java.class.path 与sun.boot.class.path,也就是说,AppClassLoader 与Bootstrap Loader会搜寻它们所指定的位置(或JAR 文件),如果找不到就找不到了,AppClassLoader 与Bootstrap Loader不会递归式地搜寻这些位置下的其它路径或其它没有被指定的JAR 檔。反观ExtClassLoader,所参考的系统参数是java.ext.dirs,意思是说,他会搜寻底下的所有JAR 文件以及classes 目录,作为其搜寻路径(所以您会发现上面我们在测试的时候,如果加入 -Dsun.boot.class.path=c:winnt选项时,程序的起始速度会慢了些,这是因为c:winnt 目录下的档案很多,必须花额外的时间来列举JAR 檔)。

    在命令列下参数时,使用 –classpath / -cp / 环境变量CLASSPATH 来更改AppClassLoader

    的搜寻路径,或者用 –Djava.ext.dirs 来改变ExtClassLoader 的搜寻目录,两者都是有意义的。可是用–Dsun.boot.class.path 来改变Bootstrap Loader 的搜寻路径是无效。这是因为AppClassLoader 与ExtClassLoader 都是各自参考这两个系统参数的内容而建立,当您在命令列下变更这两个系统参数之后, AppClassLoader 与ExtClassLoader 在建立实体的时候会参考这两个系统参数,因而改变了它们搜寻类别文件的路径;而系统参数sun.boot.class.path 则是预设与Bootstrap Loader 的搜寻路径相同,就算您更改该系统参与,与Bootstrap Loader 完全无关。如果您手边有原始码, 以JDK 1.4.x 为例, 请参考< 原始码根目录>hotspotsrcsharevmruntimeos.cpp 这个档案里头的os::set_boot_path 方法,您将看到程序代码片段:

    static const char classpathFormat[] =

    "%/lib/rt.jar:"

    "%/lib/i18n.jar:"

    "%/lib/sunrsasign.jar:"

    "%/lib/jsse.jar:"

    "%/lib/jce.jar:"

    "%/lib/charsets.jar:"

    "%/classes";

    JDK 1.4.x 比JDK 1.3.x 新增了一些核心类别函式库(例如jsse.jar 与jce.jar),所以如果您测试时用的是1.4.x 版的JDK,sun.boot.class.path 内容应该如下:

     

    9. 委派模型

    如果您对整个类别加载的方式仍有所疑问,请容笔者重新解释一下之前程序

    档案:Office.java

    import java.net.* ;

    public class Office

    {

    public static void main(String args[]) throws Exception

    {

  • 相关阅读:
    SharePoint 2013 配置基于表单的身份认证
    SharePoint 2013 场解决方案包含第三方程序集
    SharePoint 2010 站点附加数据升级到SP2013
    SharePoint 2013 在母版页中插入WebPart
    SharePoint 2013 搭建负载均衡(NLB)
    SharePoint 部署解决方案Feature ID冲突
    SharePoint 2013 配置基于AD的Form认证
    SharePoint Server 2016 Update
    SharePoint 2013 为用户组自定义EventReceiver
    SharePoint 2013 JavaScript API 记录
  • 原文地址:https://www.cnblogs.com/kelin1314/p/1867542.html
Copyright © 2011-2022 走看看