前言
打包(J2EE1.3的说明中第八章)让框架可以把一个J2EE应用的方方面面聚合到一起,但是,应用服务器提供商们可以自由地设计合适的类装载层次,来获得应用中的类和资源。典型的类装载层次应用比如:热部署和应用独立。
理解主流应用服务器类装载的架构帮助J2EE开发人员设计出轻便和高效的应用的包结构。在简要地描述一下类装载的基本知识之后,介绍一下三个主流的应用服务器(BEA WebLogic 6.1 SP2, IBM WebSphere 4.0, and HP-AS 8 Maintenance Pack 3)的类装载层次。讨论的范围限于J2EE application modules (.ear), EJB modules (.jar) and web application modules (.war).本文假设读者有J2EE packaging mechanisms (.ear, .war, and .jar)的知识,否则请参照说明文档。
读完这篇文章之后,J2EE开发人员将会对类装载架构如何影响J2EE的包的结构有更好的理解。附加的,类装载架构的知识在调试J2EE服务器抛出的ClassNotFoundException的时候有非常大的好处。
类装载基本
典型的类装载器是一个亲子结构。如果一个类加载的请求发送给了一个类加载器,他首先让他的父类加载器来完成这次请求,他的父类加载器也会轮流的要求他自己的父类来完成这次请求,直到类加载器层次的最高点。如果层次最高点的类加载器不能完成这次请求,被请求的这个类加载器会自己加载被请求的类。如果无法完成,那么请求将会发送给层次的下层类加载器,直到加载完成或者ClassNotFoundException被抛出。
图1描述了几个基本的类加载层次。注意到在一个确定层次被加载的类可能不会引用任何被低层次的类加载器加载的类。另外一个角度,类加载器不能看到他子孙类加载器加载的类。在图1中,如果类Foo被类加载器B加载,而且Foo依赖类Baz,这样的话,Baz必须被类加载器A或者B加载。如果Baz紧紧只能被C或者D看到,那么就会抛出ClassNotFoundException。
如果类Bar可以被两个同级的类加载器看到(图中的C和D),但是他们的父类加载器看不到,而起加载Bar的请求被同时发送给C和D,那么这个时候C和D都会加载自己的版本,被C加载的类和被D加载的类是不兼容的(也就是不是同一个类),这个基本的事实会导致一些难以理解的Bug,尤其是类加载层次不好理解的时候。
J2EE1.3的说明8.1.1.2节要求显式地支持通过Extension Mechanism Architecture支持独立的jar文件的绑定。应用服务器必须加载被列举在主jar文件的Manifest Class-Path的独立的jar文件(特别是EJB的jar文件)。但是这个要求ear和war文件支持。扩展机制是一个我们在打包一个应用的时候不可忽略的非常有价值的技术。任何时候如果可以,一定要调查和弄懂哪个类加载器会加载这些独立的jar文件。
通过编程来发现类加载器层次非常简单,但是输出结果可以帮我确定类是如何被加载的。下面的代码会从一个给定的Class的角度展示出类加载层次。
J2EE应用服务器一般都会应用至少两层的类加载器。如果没有准确地理解在一个应用服务器中类是如何加载的,有可能会导致困难的bug和令人迷惑的运行时错误。在这个基础上,让我们看看三个主流的应用服务器是如何选择实现他们的类加载器的。
WebLogic 6.1 with Service Pack 2
当我们在WebLogic 6.1 SP2上边部署我们的J2EE应用的时候,在标准的系统类加载器下,两个或者更多的新的类加载器会被创建,图2展示了这个类加载器架构。一个EJB类加载器作为系统的类加载器的子类被创建。他负责加载所有的在这个ear文件中的EJBjar文件。一个Web应用类加载器(web application class loaders)是EJB类加载器的子类,他负责加载war包中的类和在WEB-INF/classes 以及WEB-INF/lib下的jar文件和类。(注意: WebLogic 6.1的说明文档中说只有一个类加载器来加载所有的war文件是不准确的)
WebLogic 6.1 SP2中,在Manifest Class-Path被列举的所有的jar文件会被EJB类加载器加载。包括在ear中EJB的jar文件和war文件的Manifest Class-Path。但是注意,这个仅仅支持Manifest Class-Path在同一个ear文件中。
这个类加载架构的一个优势是web应用的类加载器可以看到所有的EJB类。但是,被web应用的类加载器加载的类(war文件中WEB-INF/classes 或者WEB-INF/lib下的类)不能被EJB看到。
如果war或者jar文件被独立(不在同一个ear文件中)地部署到WebLogic 6.1 SP2,他们会被看作是分开的应用,而且彼此会有不同的类加载器。这就是说独立的war不再能够默认地访问EJB类。
WebSphere 4.0
WebSphere 4.0的类加载架构比WebLogic 6.1相对负责,WebSphere的说明文档解释了为什么根据classpath而不是类加载器。
WebSphere 4.0定义了一个分离模式(isolation mode),这个特殊的分离模式(isolation mode)改变了一个类加载器必须关联其他的类加载器。在WebSphere 4.0中有四个isolation modes。
Module(模块):一个类加载器被创建给一个ear的每个模块。一个模块指的是一个war或EJB jar或者被列举在war或者jar的Manifest Class-Path中的jar文件。逻辑的类加载器层次由各个独立的模块来决定。比如:一个应用有两个war文件,两个EJB jar文件和两个普通的jar文件(在Manifest Class-Path中),那么就会创建六个类加载器。
Application:这个模式允许在同一个应用里所有的类加载器互相访问,就像整个应用只有一个类加载器。
Compatibility:这个模式是为了向后兼容WebSphere 3.5.x 和3.0.2.x的类加载语义。这个模式和WebLogic 6.1中,所有的war模块可以看到EJB模块一样,而且所有的EJB模块可以互相看到。
Server:这个模式逻辑地让整个server看起来只有一个类加载器。
图3展示了Module mode.的类加载层次架构。注意到所有模块的类加载器都在一个层次上。但是,在WebSphere 4.0中,这并不能说明这些类加载器互相完全不知道,类加载器组(class loader grouping mechanism)可以保证合适的类加载层次的语义。比如:在EJB1的Manifest Class-Path中有common.jar,这个jar会被其他的类加载器加载,但是EJB1可以逻辑地运行像是被common.jar class loader的子class loader加载的一样。grouping mechanism用来获得四个isolation modes的正确的语义,但是至于这些如何工作的文档中没有提到。
WebSphere 4.0的说明文档高度地建议我们用Module isolation mode来连接Manifest Class-Path的元素。这个结合是最轻便的方式。
有意思的是我们会注意到WebSphere 4.0 和WebLogic 6.1 SP2都支持jar或者war的Manifest Class-Path,但是,在同一个ear文件可以运行在一个上面却不一定能运行在另一个上面。这是因为WebLogic 6.1 SP2加载jar文件用的是EJB类加载器,只要一个jar或者war文件在Manifest Class-Path中说明了一个独立的jar文件,其他的所有的jar或者war都可以正确地引用这个jar。相对地,在WebSphere 4.0中需要每个模块逐一说明。
J2EE 1.3的8.1.1.2节中要求只有jar文件需要支持Manifest Class-Path。在war文件中支持Manifest Class-Path可以被认为是一个非标准的扩展。事实上,J2EE 1.3.1的参考实现也没有注意到war的Manifest Class-Path。但是在很多情况下,支持Manifest Class-Path在我们设计一个ear包结构的时候非常有帮助。但是,这个技术非标准的特性是我们使用的时候应该考虑的因素。
HP-AS 8 Maintenance Pack 3
HP-AS 8的类加载架构和WebSphere 4.0的非常像,虽然逻辑语义像WebLogic 6.1 SP2。图4展示了HP-AS 8的实现方式。
library class loader是application class loaders的父加载器。他搜索的classpath在application-classloader-service-config.xml中有说明。就像下面的格式:
<library name="common" url="file:///d:/lib/common.jar"/>
一个application class loader被创建为library class loader子加载器,给每个部署在HP-AS 8的J2EE应用。有一个EJB类加载器被创建,负责装载一个J2EE应用里所有的EJB jar文件。一个web application class loader被创建,负责装载所有的Web存档,每个war文件有自己的类加载器,这样允许所有的web应用独立打包。即使所有的应用独立部署,他们会通过application classloader联合到一起。
Application classloader是唯一的,但是自己不加载类,他把类加载的责任让他的子类代理,有四个子类加载器: connector classloader (not covered here), EJB classloader, 和web application classloader(s)。EJB jar互相可见,EJB jar能够被被所有的Web应用看到。这个逻辑语义非常像WebLogic 6.1 SP2。
到maintenance pack 3为止,HP-AS 8应用服务器不支持Manifest Class-Path来构建独立的应用。但是,可以预见这个特性在将来的maintenance pack 3中会有支持。我们可以把列举在Manifest Class-Path的jar文件和EJB一起打包,这样的话,类加载器会通过application-classloader-service-config.xml来加载他们,而且对于所有的EJB和Web应用都是可见的。
结论:
理解各种应用服务器的类加载层次有助于我们理解J2EE应用是如何被加载的。有了这些信息,J2EE开发人员可以设计轻便的J2EE包结构或者至少知道如何权衡来使用合适的技术。对于J2EE组件和框架提供商来说至关重要,他们必须为他们的组件是最轻便的作出额外的努力。
原文地址:
http://www.theserverside.com/tt/articles/article.tss?l=ClassLoading