一、JVM中的类加载器类型
从Java虚拟机的角度讲,只有两种不同的类加载器:启动类加载器和其他类加载器。
1.启动类加载器(Boostrap ClassLoader):这个是由c++实现的,主要负责JAVA_HOME/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作。
2.其他类加载器:由java实现,可以在方法区找到其Class对象。这里又细分为几个加载器
a).扩展类加载器(Extension ClassLoader):负责用于加载JAVA_HOME/lib/ext目录中的,或者被-Djava.ext.dirs系统变量指定所指定的路径中所有类库(jar),开发者可以直接使用扩展类加载器。java.ext.dirs系统变量所指定的路径的可以通过System.getProperty("java.ext.dirs")来查看。
b).应用程序类加载器(Application ClassLoader):负责java -classpath或-Djava.class.path所指的目录下的类与jar包装入工作。开发者可以直接使用这个类加载器。在没有指定自定义类加载器的情况下,这就是程序的默认加载器。
c).自定义类加载器(User ClassLoader):在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。
这四个类加载器的层级关系,如下图所示。
二、为什么要自定义类加载器
区分同名的类:假定在tomcat 应用服务器,上面部署着许多独立的应用,同时他们拥有许多同名却不同版本的类。要区分不同版本的类当然是需要每个应用都拥有自己独立的类加载器了,否则无法区分使用的具体是哪一个。
类库共享:每个web应用在tomcat中都可以使用自己版本的jar。但存在如Servlet-api.jar,java原生的包和自定义添加的Java类库可以相互共享。
加强类:类加载器可以在 loadClass 时对 class 进行重写和覆盖,在此期间就可以对类进行功能性的增强。比如使用javassist对class进行功能添加和修改,或者添加面向切面编程时用到的动态代理,以及 debug 等原理。
热替换:在应用正在运行的时候升级软件,不需要重新启动应用。比如toccat服务器中JSP更新替换。
三、自定义类加载器
3.1 ClassLoader实现自定义类加载器相关方法说明
要实现自定义类加载器需要先继承ClassLoader,ClassLoader类是一个抽象类,负责加载classes的对象。自定义ClassLoader中至少需要了解其中的三个的方法: loadClass,findClass,defineClass。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
loadClass:JVM在加载类的时候,都是通过ClassLoader的loadClass()方法来加载class的,loadClass使用双亲委派模式。如果要改变双亲委派模式,可以修改loadClass来改变class的加载方式。双亲委派模式这里就不赘述了。
findClass:ClassLoader通过findClass()方法来加载类。自定义类加载器实现这个方法来加载需要的类,比如指定路径下的文件,字节流等。
definedClass:definedClass在findClass中使用,通过调用传进去一个Class文件的字节数组,就可以方法区生成一个Class对象,也就是findClass实现了类加载的功能了。
贴上一段ClassLoader中loadClass源码,见见真面目...
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
}
源码说明...
/**
Loads the class with the specified <a href="#name">binary name</a>. The
default implementation of this method searches for classes in the
following order:
<ol>
<li><p> Invoke {@link #findLoadedClass(String)} to check if the class
has already been loaded. </p></li>
<li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
on the parent class loader. If the parent is <tt>null</tt> the class
loader built-in to the virtual machine is used, instead. </p></li>
<li><p> Invoke the {@link #findClass(String)} method to find the
class. </p></li>
</ol>
<p> If the class was found using the above steps, and the
<tt>resolve</tt> flag is true, this method will then invoke the {@link
#resolveClass(Class)} method on the resulting <tt>Class</tt> object.
<p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
#findClass(String)}, rather than this method. </p>
<p> Unless overridden, this method synchronizes on the result of
{@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
during the entire class loading process.
@param name
The <a href="#name">binary name</a> of the class
@param resolve
If <tt>true</tt> then resolve the class
@return The resulting <tt>Class</tt> object
@throws ClassNotFoundException
If the class could not be found
*/