摘自【
http://blog.chinaunix.net/uid-21227800-id-65885.html】
ClassLoader 详解及用途(写的不错)
2010-05-11 15:36:24
分类: Java
ClassLoader主要对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由ClassLoader返回这个类的class对象。 1.1 几个相关概念ClassLoader负责载入系统的所有Resources(Class,文件,来自网络的字节流等),通过ClassLoader从而将资源载入JVM
每个class都有一个reference,指向自己的ClassLoader。Class.getClassLoader()
array的ClassLoader就是其元素的ClassLoader,若是基本数据类型,则这个array没有ClassLoader
1.2 主要方法和工作过程Java1.1及从前版本中,ClassLoader主要方法:
Class loadClass( String name, boolean resolve ); ClassLoader.loadClass() 是 ClassLoader 的入口点
defineClass 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。
findSystemClass 方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。
resolveClass可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的 loadClass 时,可以调用 resolveClass,这取决于 loadClass 的 resolve 参数的值
findLoadedClass 充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法
一般load方法过程如下:
调用 findLoadedClass 来查看是否存在已装入的类。
如果没有,那么采用某种特殊的神奇方式来获取原始字节。(通过IO从文件系统,来自网络的字节流等)
如果已有原始字节,调用 defineClass 将它们转换成 Class 对象。
如果没有原始字节,然后调用 findSystemClass 查看是否从本地文件系统获取类。
如果 resolve 参数是 true,那么调用 resolveClass 解析 Class 对象。
如果还没有类,返回 ClassNotFoundException。
否则,将类返回给调用程序。
1.3 委托模型自从JDK1.2以后,ClassLoader做了改进,使用了委托模型,所有系统中的ClassLoader组成一棵树,ClassLoader在载入类库时先让Parent寻找,Parent找不到才自己找。
JVM在运行时会产生三个ClassLoader,Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。其中,Bootstrap ClassLoader是用C++编写的,在Java中看不到它,是null。它用来加载核心类库,就是在lib下的类库,Extension ClassLoader加载lib/ext下的类库,App ClassLoader加载Classpath里的类库,三者的关系为:App ClassLoader的Parent是Extension ClassLoader,而Extension ClassLoader的Parent为Bootstrap ClassLoader。加载一个类时,首先BootStrap进行寻找,找不到再由Extension ClassLoader寻找,最后才是App ClassLoader。
将ClassLoader设计成委托模型的一个重要原因是出于安全考虑,比如在Applet中,如果编写了一个java.lang.String类并具有破坏性。假如不采用这种委托机制,就会将这个具有破坏性的String加载到了用户机器上,导致破坏用户安全。但采用这种委托机制则不会出现这种情况。因为要加载java.lang.String类时,系统最终会由Bootstrap进行加载,这个具有破坏性的String永远没有机会加载。
委托模型还带来了一些问题,在某些情况下会产生混淆,如下是Tomcat的ClassLoader结构图:
Bootstrap
|
System
|
Common
/
Catalina Shared
/
Webapp1 Webapp2 ...
由 Common 类装入器装入的类决不能(根据名称)直接访问由 Web 应用程序装入的类。使这些类联系在一起的唯一方法是通过使用这两个类集都可见的接口。在这个例子中,就是包含由 Java servlet 实现的 javax.servlet.Servlet。
如果在lib或者lib/ext等类库有与应用中同样的类,那么应用中的类将无法被载入。通常在jdk新版本出现有类库移动时会出现问题,例如最初我们使用自己的xml解析器,而在jdk1.4中xml解析器变成标准类库,load的优先级也高于我们自己的xml解析器,我们自己的xml解析器永远无法找到,将可能导致我们的应用无法运行。
相同的类,不同的ClassLoader,将导致ClassCastException异常
1.4 线程中的ClassLoader每个运行中的线程都有一个成员contextClassLoader,用来在运行时动态地载入其它类,可以使用方法Thread.currentThread().setContextClassLoader(...);更改当前线程的contextClassLoader,来改变其载入类的行为;也可以通过方法Thread.currentThread().getContextClassLoader()来获得当前线程的ClassLoader。
实际上,在Java应用中所有程序都运行在线程里,如果在程序中没有手工设置过ClassLoader,对于一般的java类如下两种方法获得的ClassLoader通常都是同一个
this.getClass.getClassLoader();
Thread.currentThread().getContextClassLoader();
方法一得到的Classloader是静态的,表明类的载入者是谁;方法二得到的Classloader是动态的,谁执行(某个线程),就是那个执行者的Classloader。对于单例模式的类,静态类等,载入一次后,这个实例会被很多程序(线程)调用,对于这些类,载入的Classloader和执行线程的Classloader通常都不同。
1.5 Web应用中的ClassLoader回到上面的例子,在Tomcat里,WebApp的ClassLoader的工作原理有点不同,它先试图自己载入类(在ContextPath/WEB-INF/...中载入类),如果无法载入,再请求父ClassLoader完成。
由此可得:
对于WEB APP线程,它的contextClassLoader是WebAppClassLoader
对于Tomcat Server线程,它的contextClassLoader是CatalinaClassLoader
1.6 获得ClassLoader的几种方法可以通过如下3种方法得到ClassLoader
this.getClass.getClassLoader(); // 使用当前类的ClassLoader
Thread.currentThread().getContextClassLoader(); // 使用当前线程的ClassLoader
ClassLoader.getSystemClassLoader(); // 使用系统ClassLoader,即系统的入口点所使用的ClassLoader。(注意,system ClassLoader与根ClassLoader并不一样。JVM下system ClassLoader通常为App ClassLoader)
1.7 几种扩展应用用户定制自己的ClassLoader可以实现以下的一些应用
安全性。类进入JVM之前先经过ClassLoader,所以可以在这边检查是否有正确的数字签名等
加密。java字节码很容易被反编译,通过定制ClassLoader使得字节码先加密防止别人下载后反编译,这里的ClassLoader相当于一个动态的解码器
归档。可能为了节省网络资源,对自己的代码做一些特殊的归档,然后用定制的ClassLoader来解档
自展开程序。把java应用程序编译成单个可执行类文件,这个文件包含压缩的和加密的类文件数据,同时有一个固定的ClassLoader,当程序运行时它在内存中完全自行解开,无需先安装
动态生成。可以生成应用其他还未生成类的类,实时创建整个类并可在任何时刻引入JVM
2.0 资源载入
所有资源都通过ClassLoader载入到JVM里,那么在载入资源时当然可以使用ClassLoader,只是对于不同的资源还可以使用一些别的方式载入,例如对于类可以直接new,对于文件可以直接做IO等。 2.1 载入类的几种方法假设有类A和类B,A在方法amethod里需要实例化B,可能的方法有3种。对于载入类的情况,用户需要知道B类的完整名字(包括包名,例如"com.rain.B")
1. 使用Class静态方法 Class.forName
Class cls = Class.forName("com.rain.B");
B b = (B)cls.newInstance();
2. 使用ClassLoader
/* Step 1. Get ClassLoader */
ClassLoader cl; // 如何获得ClassLoader参考1.6
/* Step 2. Load the class */
Class cls = cl.loadClass("com.rain.B"); // 使用第一步得到的ClassLoader来载入B
/* Step 3. new instance */
B b = (B)cls.newInstance(); // 有B的类得到一个B的实例
3. 直接new
B b = new B();
2.2 文件载入(例如配置文件等)假设在com.rain.A类里想读取文件夹 /com/rain/config 里的文件sys.properties,读取文件可以通过绝对路径或相对路径,绝对路径很简单,在Windows下以盘号开始,在Unix下以"/"开始
对于相对路径,其相对值是相对于ClassLoader的,因为ClassLoader是一棵树,所以这个相对路径和ClassLoader树上的任何一个ClassLoader相对比较后可以找到文件,那么文件就可以找到,当然,读取文件也使用委托模型
1. 直接IO
/**
* 假设当前位置是 "C:/test",通过执行如下命令来运行A "java com.rain.A"
* 1. 在程序里可以使用绝对路径,Windows下的绝对路径以盘号开始,Unix下以"/"开始
* 2. 也可以使用相对路径,相对路径前面没有"/"
* 因为我们在 "C:/test" 目录下执行程序,程序入口点是"C:/test",相对路径就
* 是 "com/rain/config/sys.properties"
* (例子中,当前程序的ClassLoader是App ClassLoader,system ClassLoader = 当前的
* 程序的ClassLoader,入口点是"C:/test")
* 对于ClassLoader树,如果文件在jdk lib下,如果文件在jdk lib/ext下,如果文件在环境变量里,
* 都可以通过相对路径"sys.properties"找到,lib下的文件最先被找到
*/
File f = new File("C:/test/com/rain/config/sys.properties"); // 使用绝对路径
//File f = new File("com/rain/config/sys.properties"); // 使用相对路径
InputStream is = new FileInputStream(f);
如果是配置文件,可以通过java.util.Properties.load(is)将内容读到Properties里,Properties默认认为is的编码是ISO-8859-1,如果配置文件是非英文的,可能出现乱码问题。
2. 使用ClassLoader
/**
* 因为有3种方法得到ClassLoader,对应有如下3种方法读取文件
* 使用的路径是相对于这个ClassLoader的那个点的相对路径,此处只能使用相对路径
*/
InputStream is = null;
is = this.getClass().getClassLoader().getResourceAsStream(
"com/rain/config/sys.properties"); //方法1
//is = Thread.currentThread().getContextClassLoader().getResourceAsStream(
"com/rain/config/sys.properties"); //方法2
//is = ClassLoader.getSystemResourceAsStream("com/rain/config/sys.properties"); //方法3
如果是配置文件,可以通过java.util.Properties.load(is)将内容读到Properties里,这里要注意编码问题。
3. 使用ResourceBundle
ResourceBundle bundle = ResourceBundle.getBoundle("com.rain.config.sys");
这种用法通常用来载入用户的配置文件,关于ResourceBunlde更详细的用法请参考其他文档
总结:有如下3种途径来载入文件
1. 绝对路径 ---> IO
2. 相对路径 ---> IO
---> ClassLoader
3. 资源文件 ---> ResourceBundle
2.3 如何在web应用里载入资源在web应用里当然也可以使用ClassLoader来载入资源,但更常用的情况是使用ServletContext,如下是web目录结构
ContextRoot
|- JSP、HTML、Image等各种文件
|- [WEB-INF]
|- web.xml
|- [lib] Web用到的JAR文件
|- [classes] 类文件
用户程序通常在classes目录下,如果想读取classes目录里的文件,可以使用ClassLoader,如果想读取其他的文件,一般使用ServletContext.getResource()
如果使用ServletContext.getResource(path)方法,路径必须以"/"开始,路径被解释成相对于ContextRoot的路径,此处载入文件的方法和ClassLoader不同,举例"/WEB-INF/web.xml","/download/WebExAgent.rar"
分享到:
相关推荐
- **如何转换:** JSP 转换为 Servlet 的过程及原理。 #### 6. Java中的数据库操作(Statement vs. PreparedStatement) **知识点概述:** - **Statement:** 执行 SQL 语句的基本方式,不支持参数化查询,可能...
获取`Class`实例还有其他用途,比如: - **创建对象**:`Class`对象提供了`newInstance()`方法,用于创建类的新实例(前提是没有参数的构造函数)。 - **获取类的成员信息**:可以获取类的字段(`getFields()`)、...
### Java 文件加载机制详解 #### 一、概述 在Java应用开发中,特别是Web应用程序的构建过程中,资源文件(如配置文件、属性文件等)的加载是必不可少的一部分。本文将重点探讨Java环境下不同方式下的文件加载方法...
### Java反射机制详解 #### 一、Java反射机制概述 Java反射机制是Java语言提供的一种在运行时动态访问和操作类、对象、方法和属性的能力。这种能力使得开发者可以在程序运行过程中获取到类的信息,并且能够动态地...
### Java API 使用详解 #### 一、Java 面向对象基本概念 Java是一种完全的面向对象编程语言,除了原始数据类型(如int、double等),几乎所有事物都被视为对象。对象是类的实例,拥有状态(属性)和行为(方法)。...
- **反射机制**:说明反射的用途及其实现方式,探讨其性能影响及应用场合。 - **自定义注解**:讲解自定义注解的使用场景及其实现过程。 - **List与Map区别**:比较List和Map的使用场景,以及ArrayList与LinkedList...
### Java中的四个核心技术详解 #### 一、Java虚拟机(JVM) Java虚拟机(JVM)是Java平台的核心组成部分之一,它为Java程序提供了一个独立于硬件环境的运行时环境。JVM的主要职责包括:加载类文件(.class),执行字节...
### JVM基础知识及类加载机制详解 #### Java程序的运行流程 Java程序的执行流程主要包含以下几个步骤: 1. **编译**: 将`.java`源文件编译为`.class`字节码文件。 2. **打包**: 将多个`.class`字节码文件打包为一...
- **示例**: 类加载器(ClassLoader)机制可以防止恶意代码的注入。 **6. 体系结构中立性** - **特点**: Java程序编译后的字节码可以在任何支持Java虚拟机(JVM)的平台上运行。 - **示例**: 不同操作系统上的JVM使得...
### 学习Java的六大步骤详解 #### 一、安装JDK与理解其核心概念 在开始学习Java之前,第一步是正确安装Java开发工具包(JDK),并编写第一个简单的程序——“HelloWorld”。虽然安装JDK看似简单,但实际上涉及到了...
### 方立勋JavaWeb知识点详解 #### Eclipse 使用与程序调试 **1.1 Eclipse 的使用** Eclipse 是一款流行的开源集成开发环境(IDE),广泛应用于Java等语言的开发。方立勋在这部分主要介绍了如何安装配置Eclipse...
- **Request对象**:Request对象的用途及如何从中获取用户提交的数据。 ##### 9.15 文件下载 - **实现方法**:如何使用Servlet实现文件的下载功能。 #### 总结 本篇笔记涵盖了从开发工具的使用到Java Web开发中...
【Android代码-Hook技术详解】 Hook技术在Android开发中是一种非常重要的逆向工程和插桩技术,它允许开发者在不修改原始代码的情况下,通过拦截和替换原有方法的执行流程,来实现对系统或应用功能的扩展和调试。在...
【J2EE面试知识点详解】 1. **可继承性**:在Java中,不是所有类都可以被继承。例如,`java.lang.Math`、`java.lang.Void`和`java.lang.Class`不能被继承,而`java.lang.Thread`和`java.lang.ClassLoader`可以。 2...
Java虚拟机在执行Java程序时会将其管理的内存划分为多个不同的区域,每个区域都有其特定的用途及生命周期: - **程序计数器(Program Counter Register)**:用于记录当前线程执行的字节码指令的位置。每当执行引擎...
4. 使用`Proxy.newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)`方法创建代理对象。 #### 五、CGLIB动态代理 CGLIB是一个高性能的代码生成库,可以用于实现动态代理。CGLIB采用...
- **volatile变量**:标记为volatile的变量具有可见性和有序性保证,即对volatile变量的写操作对其他线程是立即可见的。 - **volatile与atomic变量的区别**:volatile仅提供可见性和有序性,而atomic变量还提供了...
### Java代理模式和动态代理详解 #### 一、概述 在软件工程中,代理模式是一种常用的软件设计模式,主要用于在不直接暴露目标对象的情况下提供一个替代接口。这种模式可以简化客户端与目标对象之间的交互,同时还...
#### 三、Java集合框架详解 - **Collection 接口:** Collection 接口是 Java 集合框架的核心接口之一,它定义了集合的基本操作,如添加元素、删除元素等。 - **Collections 工具类:** Collections 类提供了许多...