技术文章   Apusic应用服务器的ClassLoader体系结构

J2EE1.3规范定义了一个打包机制的框架,用来把J2EE应用的各个部分组织在一起。不同的应用服务器厂商可以自由的设计自己的类装载层次来获得应用中的类和资源。因此开发者必须非常清楚类和资源应该放置在什么位置对于J2EE应用才是可用的。理解Apusic应用服务器的类装载体系结构能够帮助J2EE应用的开发者设计高效和可移植应用打包结构。本文先介绍类装载的基本概念,然后讨论了Apusic应用服务器的类装载层次是如何设计的。通过这篇文章,J2EE应用的开发者能够更好的理解Apusic应用服务器的类装载体系结构是怎样从J2EE应用中获得类和资源,对于排除一般的ClassNotFoundException异常有很大的帮助。


类装载的基本概念


classloader一般被组织成父/子的层次结构。当一个类装载请求被提交到classloader时,它首先请求父classloader完成这个请求,而父classloader会依次逐级向上传递请求,直到类装载层次的顶部。如果顶部的classloader不能完成类装载请求,它的子classloader将被调用来完成类装载。如果子classloader也不能装载类,请求会继续向下传递,直到一个classloader完成这个请求,否则如果类装载层次底部的classloader都不能完成请求,就会抛出ClassNotFoundException异常。


上图显示了一个基本的ClassLoader的层次结构。在给定层次上的ClassLoader不能引用任何层次低于它的ClassLoader,另外,对于它的子ClassLoader装载的类是不可见的。在上图中,如果类Foo是由ClassLoader B装载的,并且Foo依赖于类Bar,那么类Bar必须有ClassLoader A或B装载。如果类Bar只是对ClassLoader C和D可见,那么将会发生ClassNotFoundException异常。


如果类Bar分别对于两个平级的ClassLoader可见(例如C和D),但对于它们的父ClassLoader不可见,那么当类装载请求发送到这两个ClassLoader时,每一个ClassLoader会装载自己版本的类。ClassLoader C装载的类Bar的实例将不兼容于D装载的类Bar的实例。如果对ClassLoader的层次结构不了解,上面的描述可能会引起类型不兼容的混乱。


通过编程显示ClassLoader的层次结构非常简单,但结果却能够非常有效的帮助我们标识类是如何被装载的。例如下面的程序将显示装载类Foo的ClassLoader的层次。


public class Foo {


public void showCLHierarchy() {


ClassLoader classLoader = getClass().getClassLoader();


System.out.println("the classloader is : " + classLoader + "\r");


classLoader = classLoader.getParent();


System.out.println("the parent classloader is : " + classLoader); }


public static void main(String["> args){


Foo foo = new Foo();


foo.showCLHierarchy();


} }


注意确定当前路径在classpath中,编译并运行上面的程序。例如在windows平台下:


E:\classloader\test>javac Foo.java


E:\classloader\test>java Foo


如果编译和运行环境为SUN公司的JDK1.3+,将看到下面的运行结果:


the classloader is : sun.misc.Launcher$AppClassLoader@e39a3e


the parent classloader is : sun.misc.Launcher$ExtClassLoader@a39137


Apusic应用服务器ClassLoader体系结构


Apusic应用服务器采用了两层的ClassLoader层次结构。当Apusic服务器启动后,它会创建一系列父/子关系ClassLoader,如下图所示。下面会讨论每一种ClassLoader的特性,包括它们可见的类和资源。


 


Bootstrap


这个ClassLoader装载Java虚拟机提供的基本运行时刻类,还包括放置在系统扩展目录($JAVA_HOME/jre/lib/ext)内的JAR文件中的类。


System


系统ClassLoader通常负责装载系统环境变量CLASSPATH中设置的类。由系统ClassLoader装载的类对于Apusic服务器内部的类和部署在Apusic服务器上的J2EE应用(通常打包成ear)都是可见的。%APUSIC_HOME%/lib目录下的jar文件是Apusic应用服务器的核心类,一般把这些jar文件都加在系统CLASSPATH中。


另外,一些公用类也可以加在系统CLASSPATH中,如JDBC 驱动程序等。


Ear ClassLoader


每个EAR有一个ClassLoader,用于装载EJB module和公共类。由于每个EAR的ClassLoader处于平级关系,所有不同应用中的类相互不可见。假设一个应用的目录结构如下: EAR ClassLoader负责装载的类路径为:MyApp/和MyApp/MyEJB/,也就是说在这两个目录下的类会被EAR ClassLoader装载,一般都是EJB moudle和公共类。


War ClassLoader


每个WAR有一个ClassLoader,是EAR ClassLoader的子ClassLoader,用于装载Servlet和JSP等。WAR ClassLoader负责装载的类路径包括:


· MyApp/MyWeb/WEB-INF/classes/ 目录下的类


· MyApp/MyWeb/WEB-INF/jar/ 目录下的jar文件


· %APUSIC_HOME%/scratch/hostname_port_MyApp/jsp/ 目录下的类,这些类是JSP页面对应的Servlet


由于WAR ClassLoader为EAR ClassLoader的子 ClassLoader,因此由EAR ClassLoader装载的EJB module和公共类对于Web应用中的JSP和Servlet是可见的。


理解了ClassLoader的原理和Apusic应用服务器ClassLoader的体系结构后,对于开发J2EE应用过程中遇到的ClassNotFoundException异常和类不兼容等问题的解决有很大的帮助。例如对于上面的应用MyApp,假设公共类com.apusic.app.util.Log用于记录日志,可以把这个类放在MyApp/MyEJB/com/apusic/app/util目录下,这样,这个类会由EAR ClassLoader装载,对于EJB和Web应用都是可见的。如果开发者把这个类的一个拷贝放在MyApp/MyWeb/WEB-INF/classes目录下,根据ClassLoader的代理模式,在Web应用中使用这个类时,Log类仍然会由EAR ClassLoader装载。因为发送给WAR ClassLoader的类装载请求会首先传递为父ClassLoader来执行,而EAR ClassLoader能够处理这个请求,所以会由它来装载Log类。如果开发者对服务器的这种行为没有很好的理解,可能会发生莫名其妙的错误:明明更改了MyApp/MyWeb/WEB-INF/classes目录下的Log类,但为什么在Web应用中还是执行旧的行为,即使重新启动了Apusic服务器也无济于事。原因很简单:Log类总是由EAR ClassLoader装载,放在MyApp/MyWeb/WEB-INF/classes目录下的Log类根本没有用!


Apusic应用服务器利用这种方便而灵活的ClassLoader策略,支持J2EE应用的部署和运行。相信随着开发者对Apusic应用服务器的熟悉,会越来越喜欢在Apusic上开发J2EE应用!