类加载器基本概念
顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。
public abstract class ClassLoader
extends Object
类加载器的树状组织结构
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:
- 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自
java.lang.ClassLoader。其父类加载器为NULL。 - 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过
ClassLoader.getSystemClassLoader()来获取它。
除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过ClassLoader中给出的 getParent()方法可以得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根节点就是引导类加载器。图 1中给出了一个典型的类加载器树状组织结构示意图,其中的箭头指向的是父类加载器。
图 1. 类加载器树状组织结构示意图
类是由它的全名和类加载器来确定的,由此我们可以看到一个有趣的现象即
在同一个虚拟机中,可以有两个类,它们的类名和包名都是相同的。这项技术在加载来自多处的代码时非常有用如热布署就是应用这一特性来实现的。
JVM启动时,首先加载类加载器及其相对应的字节码(class),其顺序为:
先是bootstrap classloader------(rt.jar),然后是extension classloader
------(jre/lib/ext) ,最后才是system classloader
------(CLASSPATH) 。大家会发现加载的Class越是重要的越在靠前面。这样做的原因是出于安全性的考虑,试想如果system classloader“亲自”加载了一个具有破坏性的“java.lang.System”类的后果吧。这种委托机制保证了用户即使具有一个这样的类,也把它加入到了类路径中,但是它永远不会被载入,因为这个类总是由bootstrap classloader来加载的。大家可以执行一下以下的代码:
System.out.println(System.class.getClassLoader());
将会看到结果是null,这就表明java.lang.System是由bootstrap classloader加载的,因为bootstrap classloader(用C++实现的)不是一个真正的ClassLoader实例,而是由JVM实现的。
类的加载过程:
我们知道每个类都有它自已的类加载器(Class.getClassLoader), 除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过ClassLoader中给出的 getParent()方法可以得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。
编写你自已的类加载器:
只需要继承ClassLoader类,然后重写其findClass(String classname)即可。
ClassLoader超类的loadClass方法用于将类的加载操作委托给其
父类加载器去进行,只有当类尚未加载并且其所有父类加载器也没有加载此类时,才能调用你自已的类加载器中的findclass方法。
例法:
class CryptoClassLoader extends ClassLoader
{
/**
* Constructs a crypto class loader.
* @param k the decryption key
*/
public CryptoClassLoader(int k)
{
key = k;
}
protected Class findClass(String name)
{
byte[] classBytes = null;
try {
classBytes = loadClassBytes(name);
} catch (IOException e) {
e.printStackTrace();
}
Class cl = defineClass(name, classBytes, 0, classBytes.length);
if (cl == null)
try {
throw new ClassNotFoundException(name);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return cl;
}
/**
* Loads and decrypt the class file bytes.
* @param name the class name
* @return an array with the class file bytes
*/
private byte[] loadClassBytes(String name) throws IOException
{
// String cname="classes/com/jimmy/corejava/classloaderTest"+"/"+name+".class";
String cname="classes/com/jimmy/corejava/classloaderTest"+"/"+name+".class";
FileInputStream in = null;
in = new FileInputStream(cname);
try
{
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int ch;
while ((ch = in.read()) != -1)
{
buffer.write(ch);
}
in.close();
return buffer.toByteArray();
}
finally
{
in.close();
}
}
private int key;
}
为方便测试时,我们可事先给无法找到类的路径,以防止其其父类加载器加载其类,这们强迫它调用我们的类加载器,然后在我们的findclass、loadClassBytes 方法中给出能找到此类的路径,这样就能得到我们的目的了。
public class ClassLoaderTest
{
public static void main(String[] args)
{
// String firstClass="v2ch09.ClassLoaderTest.Calculator";
String firstClass="FirstClass";
try {
ClassLoader loader = new CryptoClassLoader(Integer.parseInt("3"));
System.out.println("=====>"+ClassLoaderTest.class.getClassLoader().toString());
Object fl= loader.loadClass(firstClass).newInstance();
System.out.println("=====>"+fl.getClass().getClassLoader().toString());
System.out.println(System.class.getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
打印结果为:
=====>sun.misc.Launcher$AppClassLoader@addbf1
=====>v2ch09.ClassLoaderTest.CryptoClassLoader@61de33
运用自定义的类加载器,我们可以实现代码的加密、解密等。
posted on 2012-05-30 22:35
jimmy2009 阅读(376)
评论(0) 编辑 收藏