信息发布软件,b2b软件,广告发布软件

 找回密码
 立即注册
搜索
查看: 2474|回复: 2
打印 上一主题 下一主题

[『 Java 图文教程』] Java类别载入器和实例多教程用法

  [复制链接]

4

主题

11

帖子

97

积分

积分
97
跳转到指定楼层
宣传软件楼主
发表于 2016-9-25 11:17:22 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

软件教程首图:

软件教程分类:Java 图文教程 

软件图文教程视频教程分类:软件图文教程 

软件教程难易程度:软件初级教程 

软件教程发布日期:2016-09-25

软件教程关键字:Java类别载入器

① 本信息收集于网络,如有不对的地方欢迎联系我纠正!
② 本信息免费收录,不存在价格的问题!
③ 如果您的网站也想这样出现在这里,请您加好友情链接,我当天会审核通过!

④友情链接关键字:软件定制网站 网址:http://www.postbbs.com

软件教程详细描述

 1 Java的动态特性

Java的动态特性有两种,一是隐式的;另一种是显示的。隐式的(implicit)方法就是当程式设计师用到new 这个Java 关键字时,会让类别载入器依需求载入您所需要的类别,这种方式使用了隐式的(implicit)方法。显式的方法,又分成两种方式,一种是藉由java.lang.Class 里的forName()方法,另一种则

是藉由java.lang.ClassLoader 里的loadClass()方法。您可以任意选用其中一种方法。

  2 隐式的动态特性

在执行java文件时,只有单独的变量声明是不会载入相应的类的,只有在用new生成实例时才载入

如示例所示:

public class Main

public static void main(String args[])

{

A a1 = new A() ;

B b1 ;

}

类A和B相同,如下:

public class A

{

public void print(“using A”);

}

编译后,可用java –verbose:class Main运行,察看输出结果。可以看到JVM只载入了A,而没有载入B.

另外,类的载入只在执行到new一个类时,才载入,如果没有执行到new语句,则不载入。

如://类Office

public class Office

{

public static void main(String[] args)

{

Word myword=null;

Excel myexcel=null;

if (args[0].equals("Word"))

{

myword = new Word();

myword.start();

}

if (args[0].equals("Excel"))

{

myexcel = new Excel();

myexcel.start();

}

}

}


//类Word和Excel基本相同,如下

public class Word

{

public void start()

{

System.out.println("using word");

}

}

在dos命令提示符下,输入java –verbose Office Excel可以看到JVM只载入Excel类,而不载入Word类。

3 显示的动态特性

3.1 java.lang.Class里的forName()方法

在上一个Office示例中,进行如下修改:

一 加入Assembly类

public interface Assembly

{

public void start();

}

二 让Word和Excel类实现该接口

public class Word implements Assembly

{

public void start()

{

System.out.println("using word");

}

}

三 Office 类如下所示

public class Office

{

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

{

java.lang.Class c = java.lang.Class.forName(args[0]);

Object o = c.newInstance();

Assembly a = (Assembly)o;

a.start();

}

}


  在命令提示符下输入java –verbose Office Word 输出入下:

通过上图你可以看到,interface 如同class 一般,会由编译器产生一个独立的类别档(.class),当类别载入器载入类别时,如果发现该类别继承了其他类别,或是实作了其他介面,就会先载入代表该介面的类别档,也会载入其父类别的类别档,如果父类别也有其父类别,也会一并优先载入。换句话说,类别载入器会依继承体系最上层的类别往下依序载入,直到所有的祖先类别都载入了,才轮到自己载入。

下面介绍一下 forName 函数, 如果您亲自搜寻Java 2 SDK 说明档内部对於Class 这个类别的说明,您可以发现其实有两个forName()方法,一个是只有一个参数的(就是之前程式之中所使用的):

public static Class forName(String className)

另外一个是需要三个参数的:

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);

这里initialize参数指,在载入类之后是否进行初始化,对于该参数的作用可用如下示例察看:

类里的静态初始化块在类第一次被初始化时才被呼叫,且仅呼叫一次。在Word类里,加入静态初始化块

public class Word implements Assembly

{

static

{

System.out.println("word static initialization ");

}

public void start()

{

System.out.println("using word");

}

}

将类Office作如下改变:

public class Office

{

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

{

Office off= new Office();

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

java.lang.Class c = java.lang.Class.forName(args[0],true,off.getClass().getClassLoader());

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

Object o = c.newInstance();

Object o2 = c.newInstance();

}

}


  如果第二个参数为true 则输出入下

如果为false ,则输出入下:

可见,类里的静态初始化块仅在初始化时才执行,且不过初始化几次,它仅执行一次(这里有一个条件,那就是只有它是被同一个类别载入器多次载入时,才是这样,如果被不同的载入器,载入多次,则静态初始化块会执行多次)。

关于第三个参数请见下节介绍

3.2 直接使用类别载入器 java.lang.ClassLoader

在Java 之中,每个类别最後的老祖宗都是Object,而Object 里有一个名为getClass()的方法,就是用来取得某特定实体所属类别的参考,这个参考,指向的是一个名为Class 类别(Class.class) 的实体,您无法自行产生一个Class 类别的实体,因为它的建构式被宣告成private,这个Class 类别的实体是在类别档(.class)第一次载入记忆体时就建立的,往後您在程式中产生任何该类别的实体,这些实体的内部都会有一个栏位记录着这个Class 类别的所在位置。

基本上,我们可以把每个Class 类别的实体,当作是某个类别在记忆体中的代理人。每次我们需要

查询该类别的资料(如其中的field、method 等)时,就可以请这个实体帮我们代劳。事实上,Java的Reflection 机制,就大量地利用Class 类别。去深入Class 类别的原始码,我们可以发现Class类别的定义中大多数的方法都是原生方法(native method)。

在Java 之中,每个类别都是由某个类别载入器(ClassLoader 的实体)来载入,因此,Class 类别的实体中,都会有栏位记录着载入它的ClassLoader 的实体(注意:如果该栏位是null,并不代表它不是由类别载入器所载入,而是代表这个类别由靴带式载入器(bootstrap loader,也有人称rootloader)所载入,只不过因为这个载入器并不是用Java 所写成,是用C++写的,所以逻辑上没有实体)。

系统里同时存在多个ClassLoader 的实体,而且一个类别载入器不限於只能载入一个类别,类别载入器可以载入多个类别。所以,只要取得Class 类别实体的参考,就可以利用其getClassLoader()方法篮取得载入该类别之类别载入器的参考。getClassLoader()方法最後会呼叫原生方法getClassLoader0(),其宣告如下:private native ClassLoader getClassLoader0();

最後,取得了ClassLoader 的实体,我们就可以叫用其loadClass()方法帮我们载入我们想要的类别,因此上面的Office类可做如下修改:

public class Office

{

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

{

Office off= new Office();

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

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

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

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

Object o = c.newInstance();

Object o2 = c.newInstance();

}

}


  其输出结果同forName方法的第二个参数为false时相同。可见载入器载入类时只进行载入,不进行初始化。

获取ClassLoader还可以用如下的方法:

public class Office

{

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

{

java.lang.Class cb = Office.class;

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

ClassLoader loader = cb.getClassLoader();

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

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

Object o = c.newInstance();

Object o2 = c.newInstance();

}

}

在此之前,当我们谈到使用类别载入器来载入类别时,都是使用既有的类别载入器来帮我们载

入我们所指定的类别。那麽,我们可以自己产生类别载入器来帮我们载入类别吗? 答案是肯定的。

利用Java 本身提供的java.net.URLClassLoader 类别就可以做到。

public class Office

{

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

{

URL u = new URL("file:/d:/myapp/classload/");

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

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

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

asm.start();

}

}

在这个范例中,我们自己产生java.net.URLClassLoader 的实体来帮我们载入我们所需要的类别。但是载入前,我们必须告诉URLClassLoader 去哪个地方寻找我们所指定的类别才行,所以我们必须给它一个URL 类别所构成的阵列,代表我们希望它去搜寻的所有位置。URL 可以指向网际网路上的任何位置,也可以指向我们电脑里的档案系统(包含JAR 档)。在上述范例中,我们希望URLClassLoader 到d:mylib 这个目录下去寻找我们需要的类别, 所以指定的URL为”file:/d:/my/lib/”。其实,如果我们请求的位置是主要类别(有public static void main(String args[])方法的那个类别)的相对目录,我们可以在URL 的地方只写”file:lib/”,代表相对於目前的目录。


  下面我们来看一下系统为我们提供的3个类别载入器:

java.exe 是利用几个基本原则来寻找Java Runtime Environment(JRE),然後把类别档(.class)直接转交给JRE 执行之後,java.exe 就功成身退。类别载入器也是构成JRE 的其中一个重要成员,所以最後类别载入器就会自动从所在之JRE 目录底下的lib t.jar 载入基础类别函式库。

当我们在命令列输入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,代表其父载入器为BootstrapLoader。然後Bootstrap Loader 再要求载入定义於sun.misc 命名空间底下的Launcher.java 之中的AppClassLoader(因为是inner class,所以编译之後会变成Launcher$AppClassLoader.class),并设定其Parent 为之前产生的ExtClassLoader 实体。

这里要请大家注意的是,Launcher$ExtClassLoader.class 与Launcher$AppClassLoader.class 都可能是由Bootstrap Loader 所载入,所以Parent 和由哪个类别载入器载入没有关系。

三个载入器的层次关系可通过运行下面的例子察看:

public class Test

{

public static void main(String[] args)

{

ClassLoader cl1 = Test.class.getClassLoader();

System.out.println(cl1);

ClassLoader cl2 = cl1.getParent();

System.out.println(cl2);

ClassLoader cl3 = cl2.getParent();

System.out.println(cl3);

}

}

运行结果:

////////////////////////////////////////////////////////////

sun.misc.Launcher$AppClassLoader@1a0c10f

sun.misc.Launcher$ExtClassLoader@e2eec8

null

//////////////////////////////////////////////////////////

如果在上述程式中,如果您使用程式码:

cl1.getClass.getClassLoader()及cl2.getClass.getClassLoader(),您会发现印出的都是null,

这代表它们都是由Bootstrap Loader 所载入。这里也再次强调,类别载入器由谁载入(这句话有点

诡异,类别载入器也要由类别载入器载入,这是因为除了Bootstrap Loader 之外,其余的类别载

入器皆是由Java 撰写而成),和它的Parent 是谁没有关系,Parent 的存在只是为了某些特殊目的,

这个目的我们将在稍後作解释。

在此要请大家注意的是,AppClassLoader 和ExtClassLoader 都是URLClassLoader 的子类别。

由於它们都是URLClassLoader 的子类别,所以它们也应该有URL 作为搜寻类别档的参考,由原始码

中我们可以得知,AppClassLoader 所参考的URL 是从系统参数java.class.path 取出的字串所决定,

而java.class.path 则是由我们在执行java.exe 时,利用–cp 或-classpath 或CLASSPATH 环境变

数所决定。


用如下示例测试:

public class AppLoader

{

public static void main(String[] args)

{

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

System.out.println(s);

}

}

/////////////////////////////////////////////////////////////////

D:myappclassload>java AppLoader

.;D:myjavaTomcat5.0webappsaxisWEB-INFlibaxis.jar;D:myjavaTomcat5.0weba

ppsaxisWEB-INFlibcommons-logging.jar;D:myjavaTomcat5.0webappsaxisWEB-IN

Flibcommons-discovery.jar;Cracleora81jdbclibclasses12.zip;D:myjavaJDB

CforSQLserverlibmssqlserver.jar;D:myjavaJDBCforSQLserverlibmsbase.jar;D:m

yjavaJDBCforSQLserverlibmsutil.jar;D:myjavaTomcat5.0commonlibservlet-api

.jar;D:myjavaj2sdk1.4.2_04jrelib t.jar;C:sunappserverlibj2ee.jar;D:myj

avaj2sdk1.4.2_04libjaxp.jar;D:myjavaj2sdk1.4.2_04libsax.jar;

D:myappclassload>java -classpath .;d:myapp AppLoader

.;d:myapp

/////////////////////////////////////////////////////////////////

从这个输出结果,我们可以看出,在预设情况下,AppClassLoader 的搜寻路径为”.”(目前所在目

录),如果使用-classpath 选项(与-cp 等效),就可以改变AppClassLoader 的搜寻路径,如果没有

指定-classpath 选项,就会搜寻环境变数CLASSPATH。如果同时有CLASSPATH 的环境设定与

-classpath 选项,则以-classpath 选项的内容为主,CLASSPATH 的环境设定与-classpath 选项两者

的内容不会有加成的效果。

至於ExtClassLoader 也有相同的情形,不过其搜寻路径是参考系统参数java.ext.dirs。

系统参数java.ext.dirs 的内容,会指向java.exe 所选择的JRE 所在位置下的libext 子目录。Java.exe使用的JRE是在系统变量path里指定的,可以通过修改path从而修改ExtCLassLoader的搜寻路径,也可以如下命令参数来更改,

java –Djava.ext.dirs=c:winnt AppLoader //注意 =号两边不能有空格。-D也不能和java分开。

////////////////////////////////////////////////////////////////

D:myappclassload>java ExtLoader

D:myjavaj2sdk1.4.2_04jrelibext

D:myappclassload>java -Djava.ext.dirs=c:winnt ExtLoader

c:winnt

////////////////////////////////////////////////////////////////

最後一个类别载入器是Bootstrap Loader , 我们可以经由查询由系统参数sun.boot.class.path 得知Bootstrap Loader 用来搜寻类别的路径。该路径的修改与ExtClassLoader的相同。但修改后不影响Bootstrap的搜寻路径。

在命令列下参数时,使用–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 完全无关。

改变java.exe所使用的jre会改变Bootstrap Loader的搜寻路径。

Bootstrap Loader的搜寻路径一般如下:

///////////////////////////////////////////////////////////////////////////////////

D:myjavaj2sdk1.4.2_04jrelib t.jar;D:myjavaj2sdk1.4.2_04jrelibi18n.jar;

D:myjavaj2sdk1.4.2_04jrelibsunrsasign.jar;D:myjavaj2sdk1.4.2_04jrelibj

sse.jar;D:myjavaj2sdk1.4.2_04jrelibjce.jar;D:myjavaj2sdk1.4.2_04jrelib

charsets.jar;D:myjavaj2sdk1.4.2_04jreclasses

///////////////////////////////////////////////////////////////////////////////////////

更重要的是,AppClassLoader 与ExtClassLoader 在整个虚拟机器之中只会存有一份,一旦建

立了,其内部所参考的搜寻路径将不再改变,也就是说,即使我们在程式里利用System.setProperty()

来改变系统参数的内容,仍然无法更动AppClassLoader 与ExtClassLoader 的搜寻路径。因此,执

行时期动态更改搜寻路径的设定是不可能的事情。如果因为特殊需求,有些类别的所在路径并非在

一开始时就能决定,那麽除了产生新的类别载入器来辅助我们载入所需的类别之外,没有其他方法了。

下面我们将看一下载入器的委派模型

所谓的委派模型,用简单的话来讲,就是「类别载入器有载入类别的需求时,会先请示其Parent 使用其搜寻路径帮忙载入,如果Parent 找不到,那麽才由自己依照自己的搜寻路径搜寻类别」。

下面我们看一下小的示例:

public class Test

{

public static void main(String[] args)

{

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

TestLib tl = new TestLib();

tl.start();

}

}

public class TestLib

{

public void start()

{

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

}

}


  如果这两个类仅放在dos命令提示符的当前目录下,则输出结果如下:

//////////////////////////////////////////////////////

sun.misc.Launcher$AppClassLoader@1a0c10f

sun.misc.Launcher$AppClassLoader@1a0c10f

//////////////////////////////////////////////////////

如果这两个类同时又放在<JRE 所在目录>libextclasses 底下(在我的机器上是:D:myjavaj2sdk1.4.2_04jrelibextclasses,classes没有,需要自己建),输出结果如下:

/////////////////////////////////

sun.misc.Launcher$ExtClassLoader@e2eec8

sun.misc.Launcher$ExtClassLoader@e2eec8

////////////////////////////////////

最后如果在<JRE 所在目录>classes下放入这两个类,则输出结果为

/////////////////////////////////

null

null

////////////////////////////////////

如果把<JRE 所在目录>classes下的TestLib删去,则输出入下:

//////////////////////////////////////

null

Exception in thread "main" java.lang.NoClassDefFoundError: TestLib

at Test.main(Test.java:7)

//////////////////////////////////////

这是因为Test的classLoader是Bootstrap Loader ,因此TestLib的也默认为是Bootstrap Loader。Bootstrap Loader搜寻路径下的TestLib被删去了,Bootstrap Loader又没有parent,所以提示找不到。

其他的情况可以自己逐个添加或删除文件,然后执行java Test进行测试,察看输出结果。

AppClassLoader 与Bootstrap Loader会搜寻它们所指定的位置(或JAR 档),如果找不到就找不到了,AppClassLoader 与Bootstrap Loader不会递回式地搜寻这些位置下的其他路径或其他没有被指定的JAR 档。反观ExtClassLoader,所参考的系统参数是java.ext.dirs,意思是说,他会搜寻底下的所有JAR 档以及classes 目录,作为其搜寻路径。



unto详解reflect Java的反射机制多方法实例教程next高性能JAVA代码之_内存管理和多方法实例教程
回复

使用道具 举报

4

主题

11

帖子

97

积分

积分
97
信息发布软件沙发
 楼主| 发表于 2016-9-25 11:18:08 | 只看该作者
多方法实例教程2


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


{



回复 支持 反对

使用道具 举报

4

主题

11

帖子

97

积分

积分
97
推广工具板凳
 楼主| 发表于 2016-9-25 11:19:00 | 只看该作者
多实例教程3

Java在需要使用类别的时候,才会将类别加载,Java的类别载入是由类别载入器(Class loader)来达到的,预设上,在程序启动之后,主要会有三个类别加载器:Bootstrap Loader、ExtClassLoader与AppClassLoader。

Bootstrap Loader是由C++撰写而成,预设上它负责搜寻JRE所在目录的classes或lib目录下的.jar档案中(例如rt.jar)是否有指定的类别并加载(实际上是由系统参数sun.boot.class.path指定);预设上ExtClassLoader负责搜寻JRE所在目录的lib/ext 目录下的classes或.jar中是否有指定的类别并加载(实际上是由系统参数java.ext.dirs指定);AppClassLoader则搜寻 Classpath中是否有指定的classes并加载(由系统参数java.class.path指定)。

Bootstrap Loader会在JVM启动之后载入,之后它会载入ExtClassLoader并将ExtClassLoader的parent设为Bootstrap Loader,然后BootstrapLoader再加载AppClassLoader,并将AppClassLoader的parent设定为 ExtClassLoader。

在加载类别时,每个类别加载器会先将加载类别的任务交由其parent,如果parent找不到,才由自己负责加载,如果自己也找不到,就会丢出 NoClassDefFoundError。

每一个类别被载入后,都会有一个Class的实例来代表它,每个Class的实例都会记得是哪个ClassLoader加载它的,可以由Class的getClassLoader()取得加载该类别的ClassLoader。

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

相关导读
信息发布软件AIWROK软件找字与OCR方法汇总示例
AIWROK软件找字与OCR方法汇总示例
信息发布软件AIWROK软件找图方法汇总示例
AIWROK软件找图方法汇总示例
信息发布软件AIWROK软件滑动方法集合示例
AIWROK软件滑动方法集合示例
信息发布软件AIWROK软件安卓AIWROK汇集软件点击
AIWROK软件安卓AIWROK汇集软件点击
信息发布软件苹果系统点击方法综合示例
苹果系统点击方法综合示例
信息发布软件AIWROK苹果系统找图方法完整示例集合
AIWROK苹果系统找图方法完整示例集合
信息发布软件苹果系统找图方法完整示例集合
苹果系统找图方法完整示例集合
信息发布软件苹果IOS系统找字OCR方法例子
苹果IOS系统找字OCR方法例子
信息发布软件AIWORK软件数组高级示例
AIWORK软件数组高级示例
信息发布软件AIWROK软件运算符封装库示例
AIWROK软件运算符封装库示例
信息发布软件AIWROK软件语法运行小示例
AIWROK软件语法运行小示例
信息发布软件AIWROK软件JS循环小示例
AIWROK软件JS循环小示例
信息发布软件AIWROK软件H5网页被主脚本获取值用法
AIWROK软件H5网页被主脚本获取值用法
信息发布软件AIWROK软件创建可暂停恢复的多线程任务
AIWROK软件创建可暂停恢复的多线程任务
信息发布软件AIWROK软件类型转换方法例子
AIWROK软件类型转换方法例子
信息发布软件AIWROK软件H5脚本执行与进度显示
AIWROK软件H5脚本执行与进度显示 .
信息发布软件AIWROK软件根据时间段执行异步任务支持多线程并行处理
AIWROK软件根据时间段执行异步任务支持多线程并行处理
信息发布软件H5自动开关执行脚本功能演示
H5自动开关执行脚本功能演示
信息发布软件AIWROK软件H5单选脚本运行示例
AIWROK软件H5单选脚本运行示例
信息发布软件H5任务脚本选择与执行中心
H5任务脚本选择与执行中心
信息发布软件H5里CheckBox控件演示
H5里CheckBox控件演示
信息发布软件AIWROK软件正则用法实际例子
AIWROK软件正则用法实际例子
信息发布软件AIWROK软件权限管理器实现
AIWROK软件权限管理器实现
信息发布软件AIWORK软件节点方法无碍示例子
AIWORK软件节点方法无碍示例子
信息发布软件JSON.stringify 和 JSON.parse 完整示例
JSON.stringify 和 JSON.parse 完整示例
信息发布软件AIWROK软件展示JavaScript各种语句标识符的用法
AIWROK软件展示JavaScript各种语句标识符的用法
信息发布软件JS巧妙地组合使用各种条件语句
JS巧妙地组合使用各种条件语句
信息发布软件AIWROK手机数据库MySQL数据库截图片批量上传操作脚本
AIWROK手机数据库MySQL数据库截图片批量上传操作脚本
信息发布软件HID中文输入智能打字功能
HID中文输入智能打字功能
信息发布软件AIWROK软件对象工具函数库例子
AIWROK软件对象工具函数库例子
信息发布软件AIWROK软件H5交互演示黄色主题
AIWROK软件H5交互演示黄色主题
信息发布软件H5单按钮执行脚本示例
H5单按钮执行脚本示例
信息发布软件苹果H5界面完整调用脚本示例
苹果H5界面完整调用脚本示例
信息发布软件AIWROK软件平台设备信息全面检测工具例子
AIWROK软件平台设备信息全面检测工具例子
信息发布软件AIWROK创建和放大日志窗口并展示动态内容
AIWROK创建和放大日志窗口并展示动态内容
信息发布软件AIWROK软件device相关方法获取设备信息例子
AIWROK软件device相关方法获取设备信息例子[/backcolor]
信息发布软件数据库MySQL实时内容随机调用
数据库MySQL实时内容随机调用
信息发布软件AIWROK软件分享一个特效苹果H5页面
AIWROK软件分享一个特效苹果H5页面
信息发布软件数据库MYQ业务流程心跳程序启动
数据库MYQ业务流程心跳程序启动
信息发布软件数据库MySQL功能支持创建表插入中文数据查询删除功能例子
数据库MySQL功能支持创建表插入中文数据查询删除功能例子
信息发布软件AIWROK软件Zip 高级操作复杂示例
AIWROK软件Zip 高级操作复杂示例
信息发布软件AIWROK软件txt_文件读写方法小结
AIWROK软件txt_文件读写方法小结
信息发布软件AIWROK软件file文件操作方法小结
AIWROK软件file文件操作方法小结
信息发布软件AIWORK软件配置读写H5演示配套脚本
AIWORK软件配置读写H5演示配套脚本
信息发布软件AIWROK配置读写功能演示示例
AIWROK配置读写功能演示示例
信息发布软件AIWROK截图缓存工具
AIWROK截图缓存工具
信息发布软件AIWROK线程许可证工具
AIWROK线程许可证工具
信息发布软件整理了AIWROK环境下常用的Date对象和sleep对象方法
整理了AIWROK环境下常用的Date对象和sleep对象方法
信息发布软件FastUI界面普通用法
FastUI界面普通用法
信息发布软件FastUI界面类[window]方法小结
FastUI界面类[window]方法小结 方法 1:close(关闭指定窗口)方法 2:closeAll(关闭所有窗口)方法 3:loadUI(加载 UI 界面)方法 4:onClose(监听窗口关闭事件)方法 5:onLoad(监听窗口加载事件)方法 6:setFull(设置窗口全屏)方法 7:setHeight(设置窗口高度)方法 8:setHidden(隐藏窗口)方法 9:setLeft(设置窗口 X 轴坐标)方法 10:setTop(设置窗口 Y 轴坐标)方法 11:setVisable(显示隐藏的窗口)方
信息发布软件AIWROK软件按钮监听UI界面与事件监听功能演示
AIWROK软件按钮监听UI界面与事件监听功能演示.
信息发布软件AWIROK软件多选[uiCheckBox]方法小结
AWIROK软件多选方法小结 方法一:findByID 加载多选控件方法二:getAllChecked 获取所有选中项方法三:getAllSelect 获取所有选项方法四:getChecked 获取某个选项是否选中方法五:setChecked 设置某个选项是否选中方法六:setCheckeds 设置多个选项是否选中方法七:setHeight 设置高度
信息发布软件AIWROK日志演示开启日志显示 → 放大 → 关闭代码
AIWROK日志演示开启日志显示 → 放大 → 关闭代码
信息发布软件&#127983;AIWROK数组方法高级应用案例
🏯AIWROK数组方法高级应用案例
信息发布软件AIWROK软件日志悬浮窗简化版自动切换位置
AIWROK软件日志悬浮窗简化版自动切换位置
信息发布软件AIWROK软件String实例演示
AIWROK软件String实例演示
信息发布软件AIWROK软件S内置String类[String]方法小结
AIWROK软件S内置String类[String]方法小结 方法 1:charAt[/backcolor]方法 2:charCodeAt[/backcolor]方法 3:indexOf[/backcolor]方法 4:lastIndexOf[/backcolor]方法 5:length[/backcolor]方法 6:match[/backcolor]方法 7:replace[/backcolor]方法 8:replaceAll[/backcolor]方法 9:split[/backcolor]方法 10:startsWith[/backcolor]方法 11:substr[/backcolor]方法 12:substring[/backcolor]方法 13:trim[/backcol
信息发布软件AIWROK软件完整的WebSocket客户端示例
这段代码是一个完整的WebSocket客户端示例,用于连接到指定的WebSocket服务器并处理各种事件。具体来说,代码的作用如下: 定义服务器地址:首先定义了一个服务器的IP地址和端口号 var ip = "154.37.221.104:8886";。 创建WebSocket对象:尝试创建一个新的WebSocket对象 var ws = new WebSocket();。注意,这里的 new ws() 应该是 new WebSocket()。 添加事件监听器:代码中尝试为WebSocket对象添加事件监听器,但这里有一个错误。
信息发布软件AIWROK软件苹果系统中实现四种基本滑动操作
AIWROK软件苹果系统中实现四种基本滑动操作
信息发布软件hid的滑动没有百分比坐标滑动吗
hid的滑动没有百分比坐标滑动吗
信息发布软件单选控件[uiRadioButton]方法小结
单选控件方法小结 方法 1:加载单选控件[/backcolor]方法 2:获取选中项[/backcolor]方法 3:设置高度[/backcolor]方法 4:设置选中项[/backcolor]
信息发布软件AIWROK软件无障碍触摸操作示例:点击、左右滑动、上下滑动实例
AIWROK软件无障碍触摸操作示例:点击、左右滑动、上下滑动实例
信息发布软件AIWROK软件安卓随机工具应用函数生成
AIWROK软件安卓随机工具应用函数生成
信息发布软件用在AIWORK软件代码中的实用符号分类整理2
用在AIWORK软件代码中的实用符号分类整理2 软件IDE用Emoji符号分类整理(含用途说明)一、表情与情感1. 微笑 [*]😀 笑脸(基础开心反馈,用于操作成功后的友好提示) [*]😃 笑脸大眼睛(强化开心情绪,用于重要任务完成后的积极反馈) [*]😄 笑脸和微笑的眼睛(温和友好的状态,用于日常交互中的正向回应) [*]😁 带着微笑的眼睛(轻松愉悦的反馈,用于轻度成功或趣味操作) [*]
信息发布软件AIWROK软件图像二值化的各种方法和应用场景
AIWROK软件图像二值化的各种方法和应用场景

QQ|( 京ICP备09078825号 )

本网站信息发布软件,是可以发布论坛,发送信息到各大博客,各大b2b软件自动发布,好不夸张的说:只要手工能发在电脑打开IE能发的网站,用这个宣传软件就可以仿制动作,进行推送发到您想发送的B2B网站或是信息发布平台上,不管是后台,还是前台,都可以进行最方便的广告发布,这个广告发布软件,可以按月购买,还可以试用软件,对网站的验证码也可以完全自动对信息发布,让客户自动找上门,使企业轻松实现b2b发布,这个信息发布软件,均是本站原创正版开发,拥有正版的血统,想要新功能,欢迎提意见给我,一好的分类信息群发软件在手,舍我其谁。QQ896757558

GMT+8, 2026-2-6 16:55 , Processed in 1.215956 second(s), 46 queries .

宣传软件--信息发布软件--b2b软件广告发布软件

快速回复 返回顶部 返回列表