- 浏览: 38157 次
- 性别:
- 来自: 北京
最新评论
-
chenxing1990:
楼主能不能把配置文件的头什么的贴出来,appservice.j ...
PHPRPC for Java Spring的例子 -
好晓白:
拿回去学习学些
Hibernate映射关系配置:XML方式和注解方式 -
悲剧了:
我完全看糊涂了
Java堆、栈和常量池详解 -
lich0079:
这帖子晚发了10年
XML初学者必须知道的十条基础知识 -
sy197661944:
说实话,咋感觉不是学xml,是来宣传xml的...
XML初学者必须知道的十条基础知识
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:\my\lib\ 这个目录下去寻找我们需要的类别, 所以指定的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\rt.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:\myapp\classload>java AppLoader
.;D:\myjava\Tomcat5.0\webapps\axis\WEB-INF\lib\axis.jar;D:\myjava\Tomcat5.0\weba
pps\axis\WEB-INF\lib\commons-logging.jar;D:\myjava\Tomcat5.0\webapps\axis\WEB-IN
F\lib\commons-discovery.jar;C:\oracle\ora81\jdbc\lib\classes12.zip;D:\myjava\JDB
CforSQLserver\lib\mssqlserver.jar;D:\myjava\JDBCforSQLserver\lib\msbase.jar;D:\m
yjava\JDBCforSQLserver\lib\msutil.jar;D:\myjava\Tomcat5.0\common\lib\servlet-api
.jar;D:\myjava\j2sdk1.4.2_04\jre\lib\rt.jar;C:\sun\appserver\lib\j2ee.jar;D:\myj
ava\j2sdk1.4.2_04\lib\jaxp.jar;D:\myjava\j2sdk1.4.2_04\lib\sax.jar;
D:\myapp\classload>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 所在位置下的\lib\ext 子目录。Java.exe使用的JRE是在系统变量path里指定的,可以通过修改path从而修改ExtCLassLoader的搜寻路径,也可以如下命令参数来更改,
java –Djava.ext.dirs=c:\winnt\ AppLoader //注意 =号两边不能有空格。-D也不能和java分开。
////////////////////////////////////////////////////////////////
D:\myapp\classload>java ExtLoader
D:\myjava\j2sdk1.4.2_04\jre\lib\ext
D:\myapp\classload>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:\myjava\j2sdk1.4.2_04\jre\lib\rt.jar;D:\myjava\j2sdk1.4.2_04\jre\lib\i18n.jar;
D:\myjava\j2sdk1.4.2_04\jre\lib\sunrsasign.jar;D:\myjava\j2sdk1.4.2_04\jre\lib\j
sse.jar;D:\myjava\j2sdk1.4.2_04\jre\lib\jce.jar;D:\myjava\j2sdk1.4.2_04\jre\lib\
charsets.jar;D:\myjava\j2sdk1.4.2_04\jre\classes
///////////////////////////////////////////////////////////////////////////////////////
更重要的是,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 所在目录>\lib\ext\classes 底下(在我的机器上是:D:\myjava\j2sdk1.4.2_04\jre\lib\ext\classes,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 目录,作为其搜寻路径。
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:\my\lib\ 这个目录下去寻找我们需要的类别, 所以指定的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\rt.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:\myapp\classload>java AppLoader
.;D:\myjava\Tomcat5.0\webapps\axis\WEB-INF\lib\axis.jar;D:\myjava\Tomcat5.0\weba
pps\axis\WEB-INF\lib\commons-logging.jar;D:\myjava\Tomcat5.0\webapps\axis\WEB-IN
F\lib\commons-discovery.jar;C:\oracle\ora81\jdbc\lib\classes12.zip;D:\myjava\JDB
CforSQLserver\lib\mssqlserver.jar;D:\myjava\JDBCforSQLserver\lib\msbase.jar;D:\m
yjava\JDBCforSQLserver\lib\msutil.jar;D:\myjava\Tomcat5.0\common\lib\servlet-api
.jar;D:\myjava\j2sdk1.4.2_04\jre\lib\rt.jar;C:\sun\appserver\lib\j2ee.jar;D:\myj
ava\j2sdk1.4.2_04\lib\jaxp.jar;D:\myjava\j2sdk1.4.2_04\lib\sax.jar;
D:\myapp\classload>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 所在位置下的\lib\ext 子目录。Java.exe使用的JRE是在系统变量path里指定的,可以通过修改path从而修改ExtCLassLoader的搜寻路径,也可以如下命令参数来更改,
java –Djava.ext.dirs=c:\winnt\ AppLoader //注意 =号两边不能有空格。-D也不能和java分开。
////////////////////////////////////////////////////////////////
D:\myapp\classload>java ExtLoader
D:\myjava\j2sdk1.4.2_04\jre\lib\ext
D:\myapp\classload>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:\myjava\j2sdk1.4.2_04\jre\lib\rt.jar;D:\myjava\j2sdk1.4.2_04\jre\lib\i18n.jar;
D:\myjava\j2sdk1.4.2_04\jre\lib\sunrsasign.jar;D:\myjava\j2sdk1.4.2_04\jre\lib\j
sse.jar;D:\myjava\j2sdk1.4.2_04\jre\lib\jce.jar;D:\myjava\j2sdk1.4.2_04\jre\lib\
charsets.jar;D:\myjava\j2sdk1.4.2_04\jre\classes
///////////////////////////////////////////////////////////////////////////////////////
更重要的是,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 所在目录>\lib\ext\classes 底下(在我的机器上是:D:\myjava\j2sdk1.4.2_04\jre\lib\ext\classes,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 目录,作为其搜寻路径。
发表评论
-
XML初学者必须知道的十条基础知识
2011-06-27 23:04 10021.XML是用来组织数据结构的 结构数据包括如:电子数 ... -
Java堆、栈和常量池详解
2011-06-19 19:01 986下面主要介绍JAVA中的堆、栈和常量池: 1.寄存器: ... -
JSP使用FCKeditor详解
2011-06-19 18:10 561FCKeditor是sourceforge.net上面的 ... -
Java基础之理解JNI原理
2011-06-19 18:02 1008JNI是JAVA标准平台 ... -
Hibernate映射关系配置:XML方式和注解方式
2011-06-14 08:17 1116下面是我总结的Hibernate映射关系配置,分别为XML方式 ... -
phprpc for javascript 从java中获取数据 返回不了数据的解决方法
2011-06-03 09:27 1415返回获取不到返回值的jsp写法: <%@ page ... -
oracle的权限管理
2011-06-03 09:02 754一、系统的默认用户 sys;//系统管理员,拥有最高权限 ... -
RIM公司发布黑莓Java SDK 7测试版
2011-06-02 14:25 1812Research In Motion公司已经推出了其最新 ... -
java远程调用 PHPRPC for Java的例子
2011-06-02 10:58 1664PHPRPC 是一个轻型的 ... -
PHPRPC for Java Spring的例子
2011-06-01 00:53 21331.需要在web.xml中配置 web.xml文 ... -
Spring中事务传播行为种类
2011-04-30 00:03 613Spring在TransactionDefinition接口中 ... -
Spring中bean的实例化顺序
2011-04-30 00:03 931加载顺序: 先构造函数——>然后是b的set方法注 ... -
spring中连接池的配置
2011-04-30 00:02 664在默认通过myeclipse生成的配置里,spring使用的是 ... -
Java包含一个非常简单的享元模式
2011-04-29 23:54 897具体看如下代码 public class AutoBox ... -
61条Java面向对象设计的经验原则
2011-04-29 23:45 655(1)所有数据都应该隐 ... -
Java程序把Word转换成Html文件
2011-04-29 23:41 669Jacob是Java和Windows下的Com桥,通过它我们可 ... -
Java包的导入机制
2011-04-23 10:47 621java中有两种包的导入机制,总结如下: 单类型导入(s ... -
JDK 7 开发者预览版发布
2011-04-23 07:26 753Oracle 本周三发布了 JDK 7 的开发者预览版,可从下 ...
相关推荐
1. 类别载入器的层次结构:Java类别载入器通常由Bootstrap ClassLoader(引导类加载器)、Extension ClassLoader(扩展类加载器)和AppClass ClassLoader(应用程序类加载器)组成,形成一个树状的父子关系。...
### 深入类别载入器 #### 一、引言与动态性的概念 在软件开发领域,动态性是指程序能够在运行时加载、卸载或替换代码的能力。这为开发者提供了极大的灵活性,使他们能够对正在运行的系统进行调整,而无需重启整个...
### Java深度历险:深入探索类加载器 在IT领域,特别是软件开发中,灵活性与动态性被视为系统设计的关键要素。对于程序员而言,能够创建既易于扩展又支持“即插即用”特性的应用程序,是一种高级技能。在《Java深度...
讲解类别载入器的运作机制:类别载入器兼具Java程序的弹性与安全性两项重大任务,您不能不了解它;import与package机制的探讨:撰写Java程序的人,绝对会遇到import与package机制。只要明白这个机制的运作原理,你会...
讲解类别载入器的运作机制:类别载入器兼具Java程序的弹性与安全性两项重大任务,您不能不了解它;import与package机制的探讨:撰写Java程序的人,绝对会遇到import与package机制。只要明白这个机制的运作原理,你会...
Java深度历险 深入Java 2 SDK 深入类别载入器 Java与MS Office 用Visual Studio.net操控Java虚拟机 package与import机制 Ant 附录A.Java 2 SDK原版码概观
讲解类别载入器的运作机制:类别载入器兼具Java程序的弹性与安全性两项重大任务,您不能不了解它;import与package机制的探讨:撰写Java程序的人,绝对会遇到import与package机制。只要明白这个机制的运作原理,你会...
例如,深入类别载入器是理解JVM工作原理的重要一环。Java的类加载器负责查找、加载和初始化类。默认的类加载器层次结构由Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader组成。开发者还可以自定义类...
《Java深度历险》是一本全面探讨Java技术的书籍,主要涵盖了Java 2 SDK的深入解析,类别载入器的工作原理,Java与Microsoft Office的集成,如何利用Visual Studio .NET来操控Java虚拟机(JVM),以及Java的package与...
深入类别载入器.pdf`直接指向ClassLoader的深入讨论,可能包括自定义ClassLoader的实现、双亲委派模型等主题。`CH_05.package与import机制.pdf`可能讲解了与ClassLoader相关的包和导入机制,因为它们与类的组织和...
深入类别载入器.pdf”。这部分内容主要围绕Java的类加载机制展开,这是理解Java应用程序运行基础的关键。Java的类加载器负责查找和加载类到Java虚拟机(JVM)中。它们遵循双亲委托模型,即当一个类加载器需要加载类...
深入类别载入器.pdf** - 这一部分深入讨论了Java的类加载机制。Java的类加载器负责查找和加载类,它是JVM(Java虚拟机)的重要组成部分。这部分内容可能会涵盖类加载器的工作原理,双亲委派模型,以及如何自定义类...
CH_01.深入Java 2 SDK CH_02.深入类别载入器 CH_03.Java与MS Office CH_04.用Visual Studio.net来操控Java虚拟机 CH_05.package与import机制 CH_06.Ant
**分类**是指根据输入特征预测类别标签的过程,在机器学习中是一种常见的任务类型。 - **选择分类器**:用户可以选择不同的分类算法,如决策树、朴素贝叶斯、支持向量机等。 - **测试选项**:配置分类器的测试方式...
- 示例中的`TestClass`展示了类加载的例子,`TestClass`在声明变量`test`时并不会被加载,只有在实例化`test`时才会触发类加载,输出“类别被载入”。 掌握Java反射机制对于理解和编写高级Java程序至关重要,尤其...
IBM Java Server Pages 类别:DW Suite 文件格式:mxp DW的JSP编码功能增强插件 Gradient Text 类别:DW Suite 文件格式:mxp 在网页里生成一段色彩渐变的文字 dHTML AutoScroll Area 类别:DW Command...
在Scikit-Learn中,neural_network模块提供了构建和训练神经网络的类,MLPClassifier是其中用于分类任务的一个类,可以用来构建多层感知器(即多层神经网络),并使用反向传播算法对模型进行训练。 在提供的文档...
首先,FCKEDITOR的性能是非常好的,用户只需很少的时间就可以载入FCKEDITOR所需文件.对于其他在线编辑器来说,这几乎是个很难解决的难题,因为在开启编辑器时需要装载太多的文件.比如CUTEEDITOR,虽然功能比FCKEDITOR还要...
增加静态编译功能,支持挂接第三方链接器(比如VC6中的link.exe)。 静态编译后的易语言可执行程序(exe)和动态链接库(dll),运行时不再依赖任何支持库文件,文件尺寸更小(相对以前的独立编译),PE结构更合理...
4.4.2、标题栏进度指示器 50 4.4.3、titleBar 高级实现方法(更美观) 51 4.4.4、获取标题栏和状态栏高度 57 4.4.5、标题栏显示简单的进度框 57 4.5、MENU 58 4.5.1、简单的代码 58 4.5.2、menu实现的两种方法 58 ...