(1)jvm的裝載過程以及裝載原理
所謂裝載就是尋找一個類或是一個接口的二進制形式並用該二進制形式來構造代表這個類或是這個接口的class對象的過程,
其中類或接口的名稱是給定了的。當然名稱也可以通過計算得到,但是更常見的是通過搜索源代碼經過編譯器編譯後所得到
的二進制形式來構造。 在Java中,類裝載器把一個類裝入Java虛擬機中,要經過三個步驟來完成:裝載、鏈接和初始化,
其中鏈接又可以分成校驗、準備和解析三步,除了解析外,其它步驟是嚴格按照順序完成的,各個步驟的主要工作如下:
裝載:查找和導入類或接口的二進制數據;
鏈接:執行下面的校驗、準備和解析步驟,其中解析步驟是可以選擇的;
校驗:檢查導入類或接口的二進制數據的正確性;
準備:給類的靜態變量分配並初始化存儲空間;
解析:將符號引用轉成直接引用;
初始化:激活類的靜態變量的初始化Java代碼和靜態Java代碼塊。
(2):java中的類是什麼?
一個類代表要執行的代碼,而數據則表示其相關狀態。狀態時常改變,而代碼則不會。當我們將一個特定的狀態與一個類相對應起來,也就意味着將一個類事例化。儘管相同的類對應的實例其狀態千差萬別,但其本質都對應着同一段代碼。在JAVA中,一個類通常有着一個.class文件,但也有例外。在JAVA的運行時環境中(Java runtime),每一個類都有一個以第一類(first-class)的Java對象所表現出現的代碼,其是java.lang.Class的實例。我們編譯一個JAVA文件,編譯器都會嵌入一個public, static,
final修飾的類型爲java.lang.Class,名稱爲class的域變量在其字節碼文件中。因爲使用了public修飾,我們可以採用如下的形式對其訪問:
java.lang.Class klass = Myclass.class;
一旦一個類被載入JVM中,同一個類就不會被再次載入了(切記,同一個類)。這裏存在一個問題就是什麼是“同一個類”?正如一個對象有一個具體的狀態,即標識,一個對象始終和其代碼(類)相關聯。同理,載入JVM的類也有一個具體的標識,我們接下來看。
在Java中,一個類用其完全匹配類名(fully qualified class name)作爲標識,這裏指的完全匹配類名包括包名和類名。但在JVM中一個類用其全名和一個加載類ClassLoader的實例作爲唯一標識。因此,如果一個名爲Pg的包中,有一個名爲Cl的類,被類加載器KlassLoader的一個實例kl1加載,Cl的實例,即C1.class在JVM中表示爲(Cl, Pg, kl1)。這意味着兩個類加載器的實例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的,被它們所加載的類也因此完全不同,互不兼容的。那麼在JVM中到底有多少種類加載器的實例?下一節我們揭示答案。
(3):java的幾種ClassLoader:
在java中,我們可以取得這麼以下三個ClassLoader類:
一. ClassLoader基本概念
1.ClassLoader分類
類裝載器是用來把類(class)裝載進JVM的。
JVM規範定義了兩種類型的類裝載器:啓動內裝載器(bootstrap)和用戶自定義裝載器(user-defined class loader)。
JVM在運行時會產生三個ClassLoader:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。Bootstrap是用C++編寫的,我們在Java中看不到它,是null,是JVM自帶的類裝載器,用來裝載核心類庫,如java.lang.*等。
AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent爲Bootstrap ClassLoader。
Java提供了抽象類ClassLoader,所有用戶自定義類裝載器都實例化自ClassLoader的子類。 System Class Loader是一個特殊的用戶自定義類裝載器,由JVM的實現者提供,在編程者不特別指定裝載器的情況下默認裝載用戶類。系統類裝載器可以通過ClassLoader.getSystemClassLoader() 方法得到。
例1,測試你所使用的JVM的ClassLoader
/*LoaderSample1.java*/
public class LoaderSample1 {
public static void main(String[] args) {
Class c;
ClassLoader cl;
cl = ClassLoader.getSystemClassLoader();
System.out.println(cl);
while (cl != null ) {
cl = cl.getParent();
System.out.println(cl);
}
try {
c = Class.forName( " java.lang.Object " );
cl = c.getClassLoader();
System.out.println( " java.lang.Object's loader is " + cl);
c = Class.forName( " LoaderSample1 " );
cl = c.getClassLoader();
System.out.println( " LoaderSample1's loader is " + cl);
} catch (Exception e) {
e.printStackTrace();
}
}
}
在我的機器上(Sun Java 1.4.2)的運行結果
sun.misc.Launcher$AppClassLoader@1a0c10f
sun.misc.Launcher$ExtClassLoader@e2eec8
null
java.lang.Object's loader is null
LoaderSample1's loader is sun.misc.Launcher$AppClassLoader@1a0c10f
第一行表示,系統類裝載器實例化自類sun.misc.Launcher$AppClassLoader
第二行表示,系統類裝載器的parent實例化自類sun.misc.Launcher$ExtClassLoader
第三行表示,系統類裝載器parent的parent爲bootstrap
第四行表示,核心類java.lang.Object是由bootstrap裝載的
第五行表示,用戶類LoaderSample1是由系統類裝載器裝載的
注意,我們清晰的看見這個三個ClassLoader類之間的父子關係(不是繼承關係),父子關係在ClassLoader的實現中有一個ClassLoader類型的屬性,我們可以在自己實現自定義的ClassLoader的時候初始化定義,而這三個系統定義的ClassLoader的父子關係分別是
AppClassLoader——————》(Parent)ExtClassLoader——————————》(parent)BootClassLoader(null c++實現)
系統爲什麼要分別指定這麼多的ClassLoader類呢?
答案在於因爲java是動態加載類的,這樣的話,可以節省內存,用到什麼加載什麼,就是這個道理,然而系統在運行的時候並不知道我們這個應用與需要加載些什麼類,那麼,就採用這種逐級加載的方式
(1)首先加載核心API,讓系統最基本的運行起來
(2)加載擴展類
(3)加載用戶自定義的類
package org.corey.clsloader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import sun.net.spi.nameservice.dns.DNSNameService;
public class ClsLoaderDemo {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(System.getProperty("sun.boot.class.path"));
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(System.getProperty("java.class.path"));
}
}
程序結果爲:
E:/MyEclipse 6.0/jre/lib/rt.jar;E:/MyEclipse 6.0/jre/lib/i18n.jar;E:/MyEclipse 6.0/jre/lib/sunrsasign.jar;E:/MyEclipse 6.0/jre/lib/jsse.jar;E:/MyEclipse 6.0/jre/lib/jce.jar;E:/MyEclipse 6.0/jre/lib/charsets.jar;E:/MyEclipse 6.0/jre/classes
E:/MyEclipse 6.0/jre/lib/ext
E:/workspace/ClassLoaderDemo/bin
在上面的結果中,你可以清晰看見三個ClassLoader分別加載類的路徑;也知道爲什麼我們在編寫程序的時候,要把用到的jar包放在工程的classpath下面啦,也知道我們爲什麼可以不加載java.lang.*包啦!其中java.lang.*就在rt.jar包中;
(4)ClassLoader的加載機制:
現在我們設計這種一下Demo:
package java.net;
public class URL {
private String path;
public URL(String path) {
this.path = path;
}
public String toString() {
return this.path + " new Path";
}
}
package java.net;
import java.net.*;
public class TheSameClsDemo {
/**
* @param args
*/
public static void main(String[] args) {
URL url = new URL("http://www.baidu.com");
System.out.println(url.toString());
}
}
在這種情況下,系統會提示我們出現異常,因爲我們有兩個相同的類,一個是真正的URL,一個是我在上面實現的僞類;出現異常是正常的,因爲你想想,如果我們在執行一個applet的時候,程序自己實現了一個String的類覆蓋了我們虛擬機上面的真正的String類,那麼在這個String裏面,不懷好意的人可以任意的實現一些功能;這就造成極不安全的隱患;所以java採用了一種名爲“雙親委託”的加載模式;
以下是jdk源代碼:
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
在上面的代碼中,我們可以清晰的看見,我們調用一個ClassLoader加載程序的時候,這個ClassLoader會先調用設置好的parent ClassLoader來加載這個類,如果parent是null的話,則默認爲Boot ClassLoader類,只有在parent沒有找的情況下,自己纔會加載,這就避免我們重寫一些系統類,來破壞系統的安全;
再來看一個明顯的例子:
package org.corey;
public class MyCls{
private String name;
public MyCls(){
}
public MyCls(String name){
this.name=name;
}
public void say(){
System.out.println(this.name);
}
}
把上面這個MyCls類打成jar包,丟進ext classLoader的加載路徑;
然後寫出main類:
package org.corey.clsloader;
import org.corey.MyCls;
public class TheSameClsDemo {
/**
* @param args
*/
public static void main(String[] args) {
MyCls myClsOb=new MyCls("name");
myClsOb.say();
System.out.println(MyCls.class.getClassLoader());
System.out.println(System.getProperty("java.class.path"));
System.out.println(TheSameClsDemo.class.getClassLoader());
}
}
並且把MyCls類加入biild-path裏面方便引用;
結果是:
name
sun.misc.Launcher$ExtClassLoader@16930e2
E:/workspace/ClassLoaderDemo/bin;E:/MyEclipse 6.0/jre/lib/ext/corey.jar
sun.misc.Launcher$AppClassLoader@7259da
從上面的例子可以清晰的看出ClassLoader之間的這種雙親委託加載模式;
再來看下一個例子(摘自http://bbs.cnw.com.cn/viewthread.php?tid=95389)
下面我們就來看一個綜合的例子。
首先在eclipse中建立一個簡單的java應用工程,然後寫一個簡單的JavaBean如下:
package classloader.test.bean;
publicclass TestBean {
public TestBean() {}
}
在現有當前工程中另外建立一測試類(ClassLoaderTest.java)內容如下:
測試一:
publicclass ClassLoaderTest {
publicstaticvoid main(String[] args) {
try {
//查看當前系統類路徑中包含的路徑條目
System.out.println(System.getProperty("java.class.path"));
//調用加載當前類的類加載器(這裏即爲系統類加載器)加載TestBean
Class typeLoaded = Class.forName("classloader.test.bean.TestBean");
//查看被加載的TestBean類型是被那個類加載器加載的
System.out.println(typeLoaded.getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
}
對應的輸出如下:
D:"DEMO"dev"Study"ClassLoaderTest"bin
sun.misc.Launcher$AppClassLoader@197d257
(說明:當前類路徑默認的含有的一個條目就是工程的輸出目錄)
測試二:
將當前工程輸出目錄下的…/classloader/test/bean/TestBean.class打包進test.jar剪貼到< Java_Runtime_Home >/lib/ext目錄下(現在工程輸出目錄下和JRE擴展目錄下都有待加載類型的class文件)。再運行測試一測試代碼,結果如下:
D:"DEMO"dev"Study"ClassLoaderTest"bin
sun.misc.Launcher$ExtClassLoader@7259da
對比測試一和測試二,我們明顯可以驗證前面說的雙親委派機制,系統類加載器在接到加載classloader.test.bean.TestBean類型的請求時,首先將請求委派給父類加載器(標準擴展類加載器),標準擴展類加載器搶先完成了加載請求。
測試三:
將test.jar拷貝一份到< Java_Runtime_Home >/lib下,運行測試代碼,輸出如下:
D:"DEMO"dev"Study"ClassLoaderTest"bin
sun.misc.Launcher$ExtClassLoader@7259da
測試三和測試二輸出結果一致。那就是說,放置到< Java_Runtime_Home >/lib目錄下的TestBean對應的class字節碼並沒有被加載,這其實和前面講的雙親委派機制並不矛盾。虛擬機出於安全等因素考慮,不會加載< Java_Runtime_Home >/lib存在的陌生類,開發者通過將要加載的非JDK自身的類放置到此目錄下期待啓動類加載器加載是不可能的。做個進一步驗證,刪除< Java_Runtime_Home >/lib/ext目錄下和工程輸出目錄下的TestBean對應的class文件,然後再運行測試代碼,則將會有ClassNotFoundException異常拋出。有關這個問題,大家可以在java.lang.ClassLoader中的loadClass(String name, boolean resolve)方法中設置相應斷點運行測試三進行調試,會發現findBootstrapClass0()會拋出異常,然後在下面的findClass方法中被加載,當前運行的類加載器正是擴展類加載器(sun.misc.Launcher$ExtClassLoader),這一點可以通過JDT中變量視圖查看驗證。
(5)被不同的ClassLoader加載的兩個類之間有什麼限制和不同?
現在我們來看一下一個現象:
在eclipse裏面我是這樣做的:
OneCls.java
package org.corey.one;
import org.corey.two.TwoCls;
public class OneCls {
public OneCls() {
System.out.println();
TwoCls two = new TwoCls();
two.say();
}
}
TwoCls.java
package org.corey.two;
public class TwoCls {
public void say() {
System.out.println("i am two");
}
}
Demo.java:
package org.corey.Demo;
import org.corey.one.OneCls;
public class Demo {
/**
* @param args
*/
public static void main(String[] args) {
OneCls one=new OneCls();
}
}
在這裏,我們來仔細看下,one引用了two,demo引用了one,這是三個類都是由AppClassLoader加載的;運行正常;
把OneCls打成jar包,放在lib/ext路徑下面,然後在工程裏面引入這個jar包;運行:異常,這是因爲:
Demo是由AppClassLoader載入,委託給雙親加載失敗後,由AppClassLoader加載,而加載OneCls的時候,委託給雙親,被ExtClassLoader加載成功,但是在載入OneCls的時候,同時引用了TwoCls,但是ExtClassLoader引用TwoCls失敗,但是他只會委託給雙親,而不會委託給AppClassLoader這個兒子,所以會出現異常;
3. 奇怪的隔離性
我們不難發現,圖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) {
byte [] data = loadClassData(name);
return defineClass(name, data, 0 , data.length);
}
我們還應該提供一個方法loadClassData(String name),通過類的名稱返回class文件的字
節數組。然後使用ClassLoader提供的defineClass()方法我們就可以返回Class對象了。
public byte [] loadClassData(String name) {
FileInputStream fis = null ;
byte [] data = 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;
}