a我考网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 73|回复: 1

[其他] JAVA技巧:Java类别载入器分析(2)

[复制链接]
发表于 2012-8-4 12:28:23 | 显示全部楼层 |阅读模式
在命令提示符下输入java –verbose Office Word 输出入下:
0 Y9 E  O; l1 W5 @$ u/ K" Z4 d    通过上图你可以看到,interface 如同class 一般,会由编译器产生一个独立的类别档(.class),当类别载入器载入类别时,如果发现该类别继承了其他类别,或是实作了其他介面,就会先载入代表该介面的类别档,也会载入其父类别的类别档,如果父类别也有其父类别,也会一并优先载入。换句话说,类别载入器会依继承体系最上层的类别往下依序载入,直到所有的祖先类别都载入了,才轮到自己载入。
2 O+ y% X5 E% S7 ?# }, x    下面介绍一下 forName 函数, 如果您亲自搜寻Java 2 SDK 说明档内部对於Class 这个类别的说明,您可以发现其实有两个forName()方法,一个是只有一个参数的(就是之前程式之中所使用的):! G2 p, y5 U- D
    public static Class forName(String className)
; I; c+ h. h4 J1 e3 S, P    另外一个是需要三个参数的:
( f. {" x& Z: X+ o* j" ]    public static Class forName(String name, boolean initialize,ClassLoader loader)
1 A  J5 m, v" F8 w    这两个方法,最後都是连接到原生方法forName0(),其宣告如下:; V1 e- I7 k. D. R
    private static native Class forName0(String name, boolean initialize, ClassLoader loader)
$ k5 j5 _) @, R& m) @: r7 @3 m( E- z    throws ClassNotFoundException;
5 p* F; Q7 Y: D$ Q. O    只有一个参数的forName()方法,最後叫用的是:
9 s5 D- Q' l1 R4 B! ^) F! \    forName0(className, true, ClassLoader.getCallerClassLoader());
" Y6 p  N( B" T6 M" g  p) h9 A. i    而具有三个参数的forName()方法,最後叫用的是:# K8 D% I0 O' E: y; n; x, R
    forName0(name, initialize, loader);
2 s! A; U3 k& b- {    这里initialize参数指,在载入类之后是否进行初始化,对于该参数的作用可用如下示例察看:$ K( s) i/ N/ l% Y9 F, D
    类里的静态初始化块在类第一次被初始化时才被呼叫,且仅呼叫一次。在Word类里,加入静态初始化块: R2 Z* Z# z" ?9 H
    public class Word implements Assembly% s: w3 D5 c$ |1 t
    {( j9 D; l: ^3 }7 z0 S& C/ Q3 ]9 q
    static
# w% w4 C. W- S/ @7 Z/ d; |9 x    {
% f0 ~/ e$ B5 A. K. Y+ o% }0 ?! K1 k    System.out.println("word static initialization ");
7 e3 h- {5 G  z/ T. O$ Y- y( z    }' v- W; B9 r1 ~4 Z: v- K
    public void start()
0 R+ I- w, e* x    {
) z+ R" W- t8 ?    System.out.println("using word");( A, X% `" e( }$ [: P. M$ y4 C
    }
# U& b0 s0 l" W* L* r9 d  z    }
% [( V5 G+ E/ }9 {* t9 D, F6 K    将类Office作如下改变:
( y1 E; i& h9 y; N( c    public class Office
# ~( F/ d- u( F3 ^. a    {
+ E* V) j" }/ p. t    public static void main(String[] args) throws Exception
* ?; V9 G! r* g! l% {0 z    {
" x8 X* z/ {1 G( P5 p0 q& D    Office off= new Office();0 E0 @1 A$ x! ^: W# h: G
    System.out.println("类别准备载入");+ u9 ?7 o# Q1 e3 n+ S
    java.lang.Class c = java.lang.Class.forName(args[0],true,off.getClass().getClassLoader());
* C( m! f" I/ M9 n3 u8 }) _    System.out.println("类别准备实体化");
" n+ X8 u- G& ?. Z  I$ m    Object o  = c.newInstance();
; V+ w% p0 M! Q8 x    Object o2 = c.newInstance();( c; z3 A% r8 |& M; j
    }* n6 l- [; U  s7 e2 Z$ ?
    }1 P* z6 C* B; L3 B1 H
    如果第二个参数为true 则输出入下
, U( f- Z6 J5 T' z    如果为false ,则输出入下:
% {- g# P! u' |5 o& m/ v' ?    可见,类里的静态初始化块仅在初始化时才执行,且不过初始化几次,它仅执行一次(这里有一个条件,那就是只有它是被同一个类别载入器多次载入时,才是这样,如果被不同的载入器,载入多次,则静态初始化块会执行多次)。
; |% B- f% W5 B$ |    关于第三个参数请见下节介绍4 n3 k8 b/ v- s
    3.2 直接使用类别载入器 java.lang.ClassLoader
) z- {: `% b: B4 F5 i' k- m    在Java 之中,每个类别最後的老祖宗都是Object,而Object 里有一个名为getClass()的方法,就是用来取得某特定实体所属类别的参考,这个参考,指向的是一个名为Class 类别(Class.class) 的实体,您无法自行产生一个Class 类别的实体,因为它的建构式被宣告成private,这个Class 类别的实体是在类别档(.class)第一次载入记忆体时就建立的,往後您在程式中产生任何该类别的实体,这些实体的内部都会有一个栏位记录着这个Class 类别的所在位置。
  X$ O0 U! d5 e6 `& m* v    基本上,我们可以把每个Class 类别的实体,当作是某个类别在记忆体中的代理人。每次我们需要
" M1 _0 {, n) F# d2 v4 Y0 m0 Y    查询该类别的资料(如其中的field、method 等)时,就可以请这个实体帮我们代劳。事实上,Java的Reflection 机制,就大量地利用Class 类别。去深入Class 类别的原始码,我们可以发现Class类别的定义中大多数的方法都是原生方法(native method)。
9 S$ m8 M) W- y! S2 o5 D2 H    在Java 之中,每个类别都是由某个类别载入器(ClassLoader 的实体)来载入,因此,Class 类别的实体中,都会有栏位记录着载入它的ClassLoader 的实体(注意:如果该栏位是null,并不代表它不是由类别载入器所载入,而是代表这个类别由靴带式载入器(bootstrap loader,也有人称rootloader)所载入,只不过因为这个载入器并不是用Java 所写成,是用C++写的,所以逻辑上没有实体)。/ i# ~9 ?/ p/ Q2 L% d# C0 A$ j
    系统里同时存在多个ClassLoader 的实体,而且一个类别载入器不限於只能载入一个类别,类别载入器可以载入多个类别。所以,只要取得Class 类别实体的参考,就可以利用其getClassLoader()方法篮取得载入该类别之类别载入器的参考。getClassLoader()方法最後会呼叫原生方法getClassLoader0(),其宣告如下:private native ClassLoader getClassLoader0();0 x( p, z! m% ^  m
    最後,取得了ClassLoader 的实体,我们就可以叫用其loadClass()方法帮我们载入我们想要的类别,因此上面的Office类可做如下修改:  h; ~9 Y+ f- N5 R9 U
    public class Office% K* ]( d- T- \8 N
    {7 M5 t2 s" a, L( g
    public static void main(String[] args) throws Exception/ C$ y& n  Y1 B; i; k
    {
- y5 B" D/ L+ B% N+ t8 X    Office off= new Office();
& ?, S! U/ `  ~. n    System.out.println("类别准备载入");
7 S4 y& ]# n, t, m+ F2 z    ClassLoader loader = off.getClass().getClassLoader();
+ I' _' z# \7 j7 g3 L    java.lang.Class c = loader.loadClass(args[0]);( c1 ~7 I$ q. F/ s0 t  l9 C
    System.out.println("类别准备实体化");( b- M" |3 X# p9 N% K9 W$ ?
    Object o  = c.newInstance();. t7 z: ?$ X. O3 M# t/ w- n
    Object o2 = c.newInstance();
, C" d4 c5 _# W3 t2 o) O0 @5 \' K. y    }# o7 N" D5 J0 b8 E2 \2 Y4 H3 `. L
    }
回复

使用道具 举报

 楼主| 发表于 2012-8-4 12:28:24 | 显示全部楼层

JAVA技巧:Java类别载入器分析(2)

其输出结果同forName方法的第二个参数为false时相同。可见载入器载入类时只进行载入,不进行初始化。
5 ?; V, e: ?1 S, D  h) X! r# E    获取ClassLoader还可以用如下的方法:1 P3 O! s6 J! r) p5 ?- C% M
    public class Office
+ Z" R3 ~/ |+ L) B- e; ^% s    {1 ?$ \0 t/ Y: U  h# J
    public static void main(String[] args) throws Exception+ P( a% K! e8 B
    {9 }, C+ f% s8 `# _
    java.lang.Class cb = Office.class;
5 s  V* D/ J$ d$ e* B    System.out.println("类别准备载入");
+ a2 d, L6 X3 ?& e' f8 r: `    ClassLoader loader = cb.getClassLoader();- G4 {' n( L& [% }, V, u6 p
    java.lang.Class c = loader.loadClass(args[0]);
* k- ^$ t, d$ }! R9 g- P6 Q" m    System.out.println("类别准备实体化");
' [2 p; C: C. ^0 O% Z, x3 |8 Z    Object o  = c.newInstance();
7 g! Z7 g9 y8 Y+ r/ x; `+ Z6 p9 J    Object o2 = c.newInstance();1 \" [7 R1 e9 {; Q
    }
) \; U! [( V, U8 e. Y4 Y    }8 [6 U# o2 h  ]' n4 E) {
    在此之前,当我们谈到使用类别载入器来载入类别时,都是使用既有的类别载入器来帮我们载
9 P6 c+ R: h# B  o$ }    入我们所指定的类别。那麽,我们可以自己产生类别载入器来帮我们载入类别吗? 答案是肯定的。7 {- r, d# m# l$ S/ a, P" t
    利用Java 本身提供的java.net.URLClassLoader 类别就可以做到。
. @. b" \0 M7 j1 h4 |    public class Office+ Y& F& k8 G$ }- t
    {4 q3 K0 k. N0 f
    public static void main(String[] args) throws Exception* P: k# {5 [6 ~+ y5 X" R
    {0 W% h9 k4 c, f6 t( r6 \  S
    URL u = new URL("file:/d:/myapp/classload/");" V9 ?4 a$ t2 D0 |4 ~9 P: L1 G
    URLClassLoader ucl = new URLClassLoader(new URL[]{u});1 J; x1 S' r! [! ^7 T3 \) D. v4 F
    java.lang.Class c = ucl.loadClass(args[0]);
& T' P; D% N4 E5 [+ x+ ]    Assembly asm = (Assembly)c.newInstance();  D5 d# V" G1 Z, ~' ^
    asm.start();  k4 D5 F; n  \0 Z/ n9 b
    }
/ d; p2 a8 |3 R5 r- G! z) q( D    }9 |( r: d2 t* S* M* k: `) U% i3 }
    在这个范例中,我们自己产生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/”,代表相对於目前的目录。! p7 w2 a. W# `* u2 q; ^! N) m( I4 _$ \
    下面我们来看一下系统为我们提供的3个类别载入器:
- a) s1 h- ^9 d- D1 l" ?    java.exe 是利用几个基本原则来寻找Java Runtime Environment(JRE),然後把类别档(.class)直接转交给JRE 执行之後,java.exe 就功成身退。类别载入器也是构成JRE 的其中一个重要成员,所以最後类别载入器就会自动从所在之JRE 目录底下的\lib\rt.jar 载入基础类别函式库。
- j7 S* ]/ ]- @$ X1 f    当我们在命令列输入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 所
# d' @' m: ?6 Y  N. r    做的初始工作中,除了也做一些基本的初始化动作之外,最重要的就是载入定义在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 实体。' p, R$ q0 x) g, w# e7 ^" P
    这里要请大家注意的是,Launcher$ExtClassLoader.class 与Launcher$AppClassLoader.class 都可能是由Bootstrap Loader 所载入,所以Parent 和由哪个类别载入器载入没有关系。
回复 支持 反对

使用道具 举报

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

本版积分规则

Archiver|手机版|小黑屋|Woexam.Com ( 湘ICP备18023104号 )

GMT+8, 2024-5-21 21:57 , Processed in 0.206236 second(s), 23 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2017 Comsenz Inc.

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