JVM規範定義了兩種類型的類裝載器:啓動內裝載器(bootstrap)和用戶自定義裝載器(user-defined class loader)。
一. 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*/ 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是由系統類裝載器裝載的
二.
parent delegation模型
從1.2版本開始,Java引入了雙親委託模型,從而更好的保證Java平臺的安全。
在此模型下,當一個裝載器被請求裝載某個類時,它首先委託本身的
parent
去裝載,若
parent
能裝載,則返回這個類所對應的
Class
對象,若
parent
不能裝載,則由
parent
的請求者去裝載。
圖 1 parent delegation模型
如 圖1所示,loader2的parent爲loader1,loader1的parent爲system class loader。假設loader2被要求裝載類MyClass,在parent delegation模型下,loader2首先請求loader1代爲裝載,loader1再請求系統類裝載器去裝載MyClass。若系統裝載器能成 功裝載,則將MyClass所對應的Class對象的reference返回給loader1,loader1再將reference返回給 loader2,從而成功將類MyClass裝載進虛擬機。若系統類裝載器不能裝載MyClass,loader1會嘗試裝載MyClass,若 loader1也不能成功裝載,loader2會嘗試裝載。若全部的parent及loader2自己都不能裝載,則裝載失敗。 編程
如有一個能成功裝載,實際裝載的類裝載器被稱爲定義類裝載器,全部能成功返回Class對象的裝載器(包括定義類裝載器)被稱爲初始類裝載器。如圖1所 示,假設loader1實際裝載了MyClass,則loader1爲MyClass的定義類裝載器,loader2和loader1爲MyClass的 初始類裝載器。
須要指出的是,Class Loader是對象,它的父子關係和類的父子關係沒有任何關係。
那麼parent delegation模型爲何更安全了?由於在此模型下用戶自定義的類裝載器不可能裝載應該由父親裝載器裝載的可靠類,從而防止不可靠甚至惡意的代碼代替由父親裝載器裝載的可靠代碼。實際上,類裝載器的編寫者能夠自由選擇不用把請求委託給parent,但正如上所說,會帶來安全的問題。 bootstrap
三.命名空間及其做用
每一個類裝載器有本身的命名空間,命名空間由全部以此裝載器爲創始類裝載器的類組成。不一樣命名空間的兩個類是不可見的,但只要獲得類所對應的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中類的包可見的成員。
總結
命名空間並無徹底禁止屬於不一樣空間的類的互相訪問,雙親委託模型增強了Java的安全,運行時包增長了對包可見成員的保護。
二. 擴展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對象了。 spa
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; }