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