Application Class Loader

本文围绕Java的JVM内存管理和JVM Class Loader展开,重点阐述了App Class Loader、Web App Class Loader和EJB Class Loader的作用范围。还介绍了Hibernate在不同ClassLoader级别的配置,以及EJB规范相关问题,如EJB无IO操作原因,还给出了EAR包解决EJB相互调用的方法。

App Class Loader

作者:robbin (MSN:robbin_fan AT hotmail DOT com)

版权声明:本文严禁转载,如有转载请求,请和作者联系

Java本身是一种设计的非常简单,非常精巧的语言,所以Java背后的原理也很简单,归结起来就是两点:

1、JVM的内存管理

理解了这一点,所有和对象相关的问题统统都能解决

2、JVM Class Loader

理解了这一点,所有和Java相关的配置问题,包括各种App Server的配置,应用的发布问题统统都能解决

App Class Loader
|----- EJB Class Loader
|----- Web App Class Loader

如果在App Class Loader级别配置,是全局可见的。如果打包在EJB里面,那么就不会影响到Web Application,反之亦然,如果你在WEB-INF下面放置Hibernate,也不会影响到EJB。放在EJB Class Loader或者放在Web App Class Loader级别主要就是在局部范围内有效,不影响到其它的应用。

试想,如果在一个Weblogic上面配置多个虚拟域,你使用www.bruce.com域名,开发你的网站,我使用www.fankai.com开发我的网站,那么当然不希望我们的Hibernate相互干扰,所以就可以放在 EJB Class Loader级别来配置Hibernate。

进一步阐述一下EJB Class Loader的问题:

先再次强调一下,Hibernate和EJB,和App Server不存在兼容性问题,他们本来就是不相关的东西,就好像JDBC,相信没有人会认为JDBC和EJB不兼容吧,Hibernate也是一样,它只和JDBC驱动,和数据库有兼容性问题,而和EJB,和App Server完全是不搭界的两回事。凡是认为Hibernate和EJB不兼容的人,其实是都是因为对EJB学习的不到家,把责任推到Hibernate身上了。

我前面提到过Class Loader的层次,这里不重复了,总之我们先来看看Class Loader的作用范围:

((Boot Strap)) Class Loader:
load JRE/lib/rt.jar, sunrsasign.jar, charsets.jar, jce.jar, jsse.jar, plugin.jar
Ext Class Loader:
load JRE/lib/ext目录下的库文件, load JRE/classes目录下的类
App Class Loader:
load CLASSPATH变量指定路径下的类

以上的load路径都是写死在JVM的C++源代码里面的,不能改变,详细请见王森的《Java深度历险》

在一个特定的App Server上,Class Loader会继续向下继承,继承的层次会根据不同的App Server有所不同,但是肯定不会变的就是:

EJB Class Loader:

继承自App Class Loader,继承层次根据App Server有所不同,一个EJB Class Loader它的load Class的范围仅限于JAR或者EAR范围之内。

Web App Class Loader:

继承自App Class Loader,继承层次根据App Server有所不同,一个Web App Class Loader:
它的load Class的范围在 WEB-INF/lib下的库文件和WEB-INF/classes目录下的class文件。

Web App Class Loader很好理解,大家毕竟用的很多,App Server上的一个Web Application会创建一个Web App Class Loader的实例去负责load class,所以如果你想让Hibernate只在这个Web Application内生效,把它放到WEB-INF/lib下去就好了。

如果你把Hibernate放到了CLASSPATH变量指定的路径下,而你在WEB-INF/lib也放了一份,那么Web App Class Loader由于load范围所限,它会首先找到WEB-INF/lib下的那份Hibernate,按照它的配置来初始化Hibernate。

如果你把Hibernate放到了CLASSPATH变量指定的路径下,但你在WEB-INF/lib什么都没有放,那么Web App Class Loader由于load范围所限,它根本什么都找不到,于是它把load Hibernate的责任交给上一级的Class Loader,这样直到App Class Loader,它找到了Hibernate,按照它的配置来初始化Hibernate。

EJB Class Loader稍微复杂一点,不那么容易理解。App Server会针对每一个EJB包文件创建一个EJB Class Loader的实例,例如:

((Hello Robbin)).jar
((Hello Bruce)).jar

当你把这两个jar发布到App Server上以后,会创建两个EJB Class Loader的实例,分别去load这两个EJB包,比如说:

CLEJB_Robbin是load ((Hello Robbin)).jar的
CLEJB_Bruce是load ((Hello Bruce)).jar的

那么CLEJB_Robbin的load范围就仅仅限于HelloRobbin.jar之内,它load不到HelloRobbin.jar之外的任何文件,当然它也load不到HelloBruce.jar。

说到这里,我相信大家应该已经明白为什么EJB规范不允许EJB有IO操作了吧?因为EJB Class Loader根本找不到jar包之外的文件!!!

如果现在你想实现HelloRobbin.jar和HelloBruce.jar的互相调用,那么该怎么办?他们使用了不同的EJB Class Loader,相互之间是找不到对方的。解决办法就是使用EAR。

现在假设HelloRobbin.jar和HelloBruce.jar都使用了Hibernate,看看该怎么打包和发布:

HelloEJB.ear

|------ ((Hello Robbin)).jar
|------ ((Hello Bruce)).jar
|------ Hibernate2.jar
|------ pojo.jar (定义所有的持久对象和hbm文件的jar包)
|------ cglib-asm.jar
|------ commons-beanutils.jar
|------ commons-collections.jar
|------ commons-lang.jar
|------ commons-logging.jar
|------ dom4j.jar
|------ odmg.jar
|------ log4j.jar
|------ jcs.jar
|------ hibernate.properties
|------ log4j.properties
|------ cache.ccf
|------ META-INF/application.xml (J2EE规范的要求,定义EAR包里面包括了哪几个EJB)

除此之外,按照EJB规范要求,HelloRobbin.jar和HelloBruce.jar还必须指出调用jar包之外的类库的名称,这需要在jar包的manifest文件中定义:

((Hello Robbin)).jar
|------ META-INF/MANIFEST.MF

MANIFEST.MF中必须包括如下一行:

Class-Path: log4j.jar hibernate2.jar cglib-asm.jar commons-beanutils.jar commons-collections.jar commons-lang.jar
commons-logging.jar dom4j.jar jcs.jar odmg.jar jcs.jar pojo.jar

这样就OK了,当把HelloEJB.ear发布到App Server上以后,App Server创建一个EJB Class Loader实例load EAR包里面的EJB,再根据EJB的jar包里面的MANIFEST.MF指出的Class-Path去寻找相应的jar包之外的类库。

所以一个EAR包有点类似一个Web Application,EJB Class Loader的load范围也就是EAR范围之内,它load不到EAR之外的文件。除非把Hibernate定义到CLASSPATH指定的路径下,在这种情况下,EJB Class Loader找不到Hibernate,只能交给上一级的Class Loader,最后由App Class Loader找到Hibernate,进行初始化。

由于EAR这样load Class规则,假设Robbin和Bruce都在同一个Weblogic上运行自己的网站,而我们都不希望自己的程序里面的Hibernate配置被对方的搞乱掉,那么我们就可以这样来做:

Robbin's Website:

Robbin.ear

|-------- robbin.war (把Web Application打包)
|-------- robbin.jar (把开发的EJB打包)
|-------- Hibernate2.jar
..........................
|-------- META-INF/application.xml

Bruce's Website:

Bruce.ear
|-------- bruce.war (把Web Application打包)
|-------- bruce.jar (把开发的EJB打包)
|-------- Hibernate2.jar
..........................
|-------- META-INF/application.xml

这样在同一个App Server上运行,就可以互相不干扰。

<think>我们正在讨论Java中的类加载器。用户的问题是关于不同类型的类加载器及其区别。根据提供的引用,我们可以整理出以下信息: 引用[1]提到:JVM类加载过程中使用同一个ClassLoader加载某个类时,无论加载多少次,在堆区生成的java.lang.Class对象始终只有一个。除非使用自定义类加载器破坏双亲委派机制,同一个类被不同类加载器加载,JVM才会将其视为两个不同的Class对象。 引用[2]说明:类加载器有几种,并举例java.lang.Object类,无论哪个类加载器需要加载这个类,最终都由Bootstrap ClassLoader加载,因此Object在程序各个类加载器环境中都是同一个类。同时强调了双亲委派模型的重要性,避免用户自定义的Object类造成混乱。 引用[3]则介绍了获取Class对象的几种方式,特别是字面常量方式(如Object.class)的优势:编译器检查、效率高、不会自动初始化类,并且适用于接口、数组和基本数据类型。 现在,我们需要回答用户的问题:Java中几种class loader的区别是什么? Java中有三种(实际上是四层)内置的类加载器,按照层次从高到低分别是: 1. Bootstrap ClassLoader(启动类加载器) 2. Extension ClassLoader(扩展类加载器) 3. Application ClassLoader(应用程序类加载器,也称系统类加载器) 4. 用户自定义类加载器(User-Defined ClassLoader) 它们之间的区别主要体现在以下几个方面: - 加载路径不同 - 加载的类不同 - 在JVM中的层次关系不同(双亲委派机制) 下面详细说明: ### 1. Bootstrap ClassLoader(启动类加载器) - **实现**:由C/C++实现,是JVM的一部分,不继承java.lang.ClassLoader。 - **职责**:加载Java核心库,如`$JAVA_HOME/jre/lib`下的核心类库(如rt.jar、charsets.jar等)以及被`-Xbootclasspath`参数指定的路径中的类。 - **特点**:是最顶层的类加载器,是其他所有类加载器的父加载器(注意:这里的父子关系不是继承,而是组合关系)。 - **示例**:如`java.lang.Object`、`java.lang.String`等核心类都由它加载[^2]。 ### 2. Extension ClassLoader(扩展类加载器) - **实现**:由Java实现,是`sun.misc.Launcher$ExtClassLoader`的实例。 - **职责**:加载扩展目录(`$JAVA_HOME/jre/lib/ext`)或由`java.ext.dirs`系统变量指定的路径中的所有类库。 - **特点**:它的父加载器是Bootstrap ClassLoader(虽然从Java角度看,其父类加载器为null,因为Bootstrap不是Java类,但逻辑上它是Extension ClassLoader的父加载器)。 - **示例**:加载扩展功能包,如javax包中的一些类。 ### 3. Application ClassLoader(应用程序类加载器) - **实现**:由Java实现,是`sun.misc.Launcher$AppClassLoader`的实例。 - **职责**:加载用户类路径(ClassPath)上的类库。如果没有自定义类加载器,默认情况下应用程序的类都由它加载。 - **特点**:它的父加载器是Extension ClassLoader。它是程序中默认的类加载器,通过`ClassLoader.getSystemClassLoader()`方法可以获取到它。 ### 4. User-Defined ClassLoader(用户自定义类加载器) - **实现**:由用户自定义的类加载器,继承`java.lang.ClassLoader`类。 - **职责**:根据用户需求加载特定路径的类。 - **特点**:可以打破双亲委派机制(通过重写`loadClass()`方法),实现热部署、模块化加载等。但如果不破坏双亲委派,则默认行为是委派给父类加载器。 ### 双亲委派机制(Parent Delegation Model) 类加载器在加载类时,首先会委托给父类加载器去尝试加载,只有当父类加载器无法加载时(在自己的搜索范围内找不到该类),才由自己加载。这种机制的好处是: 1. **避免重复加载**:确保一个类在同一个类加载器环境中只会被加载一次。 2. **安全**:防止核心API被篡改。例如,用户自定义一个`java.lang.Object`类,由于双亲委派机制,最终会由Bootstrap ClassLoader加载核心的Object类,而用户自定义的Object类不会被加载,从而保证了Java核心库的安全[^2]。 ### 类加载器的区别总结 | 类加载器类型 | 实现语言 | 加载路径/类库 | 父加载器 | 特点 | |--------------------|----------|-----------------------------------|----------------------|------------------------------------------| | Bootstrap | C/C++ | `$JAVA_HOME/jre/lib`核心库 | 无(最顶层) | 加载核心类,如java.lang包 | | Extension | Java | `$JAVA_HOME/jre/lib/ext`扩展库 | Bootstrap(逻辑上) | 加载扩展功能包 | | Application | Java | 用户类路径(ClassPath) | Extension | 默认的应用程序类加载器 | | User-Defined | Java | 用户指定路径 | 可指定(通常为App) | 可自定义行为,如打破双亲委派 | ### 示例:类加载过程 以加载用户自定义的`com.example.MyClass`为例: 1. `AppClassLoader`收到加载请求,先委派给父加载器`ExtClassLoader`。 2. `ExtClassLoader`再委派给父加载器`Bootstrap ClassLoader`。 3. `Bootstrap`在核心库中找不到该类,返回失败。 4. `ExtClassLoader`在扩展库中查找,也找不到,返回失败。 5. `AppClassLoader`在ClassPath中找到并加载`com.example.MyClass`。 ### 破坏双亲委派 在某些场景下(如热部署、OSGi模块化),需要打破双亲委派机制。例如,用户自定义类加载器重写`loadClass()`方法,先自己尝试加载类,而不是先委派给父类加载器。这样同一个类由不同的类加载器加载,会被JVM视为不同的类(即使全限定名相同),因为它们的类加载器不同[^1]。 ### 获取Class对象的方式 引用[3]提到,获取Class对象有几种方式,其中字面常量方式(如`Object.class`)是最简单、安全且高效的,因为它在编译期就确定,且不会触发类的初始化(而`Class.forName()`会触发静态初始化块)。这种方式也适用于基本数据类型和数组[^3]。 通过以上分析,我们可以清晰地理解不同类加载器的区别及其在Java类加载机制中的作用。</think>### Java类加载器类型及其区别 Java类加载器采用**双亲委派模型**,主要分为四类,区别如下: #### 1. **Bootstrap ClassLoader(启动类加载器)** - **实现语言**:C/C++(非Java实现) - **加载路径**:`$JAVA_HOME/jre/lib`核心库(如`rt.jar`、`charsets.jar`) - **特点**: - 最顶层的类加载器,是其他所有类加载器的逻辑父类 - **唯一不继承`java.lang.ClassLoader`**的加载器 - 负责加载Java核心类(如`java.lang.Object`)[^2] - **示例**: ```java System.out.println(Object.class.getClassLoader()); // 输出null(表示由Bootstrap加载) ``` #### 2. **Extension ClassLoader(扩展类加载器)** - **实现类**:`sun.misc.Launcher$ExtClassLoader`(Java实现) - **加载路径**:`$JAVA_HOME/jre/lib/ext`目录或`java.ext.dirs`指定路径 - **特点**: - 父加载器是Bootstrap ClassLoader - 加载标准扩展库(如`javax.*`包) - **示例**: ```java System.out.println(javax.crypto.Cipher.class.getClassLoader()); // 输出sun.misc.Launcher$ExtClassLoader@xxx ``` #### 3. **Application ClassLoader(应用类加载器/系统类加载器)** - **实现类**:`sun.misc.Launcher$AppClassLoader`(Java实现) - **加载路径**:用户类路径(ClassPath) - **特点**: - 父加载器是Extension ClassLoader - **默认的类加载器**,负责加载用户编写的类 - 通过`ClassLoader.getSystemClassLoader()`获取 - **示例**: ```java System.out.println(MyClass.class.getClassLoader()); // 输出sun.misc.Launcher$AppClassLoader@xxx ``` #### 4. **User-Defined ClassLoader(自定义类加载器)** - **实现方式**:继承`java.lang.ClassLoader`并重写`findClass()` - **加载路径**:用户指定路径(如网络、加密文件等) - **特点**: - 可打破双亲委派机制(重写`loadClass()`) - 同一类被不同加载器加载时,JVM视为不同类[^1] - **典型场景**: - 热部署(如Tomcat的WebappClassLoader) - 模块化加载(如OSGi) - 加载加密字节码 --- ### 核心机制:双亲委派模型 1. **工作流程**: - 类加载请求先委派给父加载器 - 父加载器无法完成时(如找不到类),子加载器才尝试加载 ```mermaid graph LR A[自定义加载器] --> B[AppClassLoader] B --> C[ExtClassLoader] C --> D[BootstrapClassLoader] D --失败--> C C --失败--> B B --失败--> A ``` 2. **核心作用**: - **避免重复加载**:确保类在JVM中唯一(同一加载器下)[^1] - **防止核心API篡改**:如用户定义`java.lang.Object`会被Bootstrap优先加载,自定义类无效[^2] - **安全隔离**:不同加载器的类互不可见(如Tomcat隔离Web应用) --- ### 关键区别总结 | 加载器类型 | 父加载器 | 加载范围 | 是否可被Java代码访问 | |------------------------|-------------------|--------------------------|----------------------| | Bootstrap ClassLoader | 无(顶级) | Java核心库 | ❌(返回null) | | Extension ClassLoader | Bootstrap | 扩展目录(`jre/lib/ext`)| ✔️ | | Application ClassLoader| Extension | 用户类路径(ClassPath) | ✔️ | | User-Defined | 可指定(默认App) | 自定义路径 | ✔️ | --- ### 示例:破坏双亲委派 自定义加载器重写`loadClass()`方法,跳过父加载器: ```java public class MyClassLoader extends ClassLoader { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { // 绕过双亲委派,直接自己加载 if (name.startsWith("com.myapp.")) { return findClass(name); } return super.loadClass(name); } } // 此时加载com.myapp.Foo,JVM会视为与AppClassLoader加载的com.myapp.Foo不同[^1] ``` --- ### 类加载与Class对象 - **Class对象唯一性**:同一类加载器对同一类多次加载,仍对应同一Class对象(`==`比较为`true`)[^1] - **获取Class对象方式**: - 安全高效:`MyClass.class`(字面量,不触发初始化)[^3] - 动态加载:`Class.forName("MyClass")`(触发初始化) --- ### 常见问题场景 1. **`ClassCastException`**: - 原因:同一类被不同加载器加载,JVM视为不同类 - 解决:确保关键类由同一加载器加载 2. **`NoClassDefFoundError`**: - 原因:父加载器加载的类依赖子加载器的类(违反委派机制) - 解决:调整类加载逻辑或使用线程上下文加载器(`Thread.currentThread().getContextClassLoader()`)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值