ImageVerifierCode 换一换
格式:DOCX , 页数:16 ,大小:62.85KB ,
资源ID:24440576      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/24440576.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(深入探讨Java类加载器.docx)为本站会员(b****2)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

深入探讨Java类加载器.docx

1、深入探讨Java类加载器深入探讨 Java 类加载器成富 IBM 中国软件开发中心成富任职于 IBM 中国软件开发中心,目前在 Lotus 部门从事 IBM Mashup Center 的开发工作。他毕业于北京大学信息科学技术学院,获得计算机软件与理论专业硕士学位。他的个人网站是 http:/www.cheng-。简介:类加载器(class loader)是 Java 中的一个很重要的概念。类加载器负责加载 Java 类的字节代码到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模式、加载类的具体过程和线程上下文类加载器等,接着介绍如何开发自己的类加载器,最后介

2、绍了类加载器在 Web 容器和 OSGi 中的应用。类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java Applet 需要从远程下载 Java 类文件到浏览器中并执行。现在类加载器在 Web 容器和 OSGi 中得到了广泛的使用。一般来说,Java 应用的开发人员不需要直接同类加载器进行交互。Java 虚拟机默认的行为就已经足够满足大多数情况的需求了。不过如果遇到了需要与类加载器进行交互的情况,而对

3、类加载器的机制又不是很了解的话,就很容易花大量的时间去调试 ClassNotFoundException 和 NoClassDefFoundError 等异常。本文将详细介绍 Java 的类加载器,帮助读者深刻理解 Java 语言中的这个重要概念。下面首先介绍一些相关的基本概念。类加载器基本概念顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代

4、码,并转换成 java.lang.Class 类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。基本上所有的类加载器都是 java.lang.ClassLoader 类的一个实例。下面详细介绍这个 Java 类。java.lang.ClassLoader 类介绍java.lang.ClassLoader 类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即

5、 java.lang.Class 类的一个实例。除此之外,ClassLoader 还负责加载 Java 应用所需的资源,如图像文件和配置文件等。不过本文只讨论其加载类的功能。为了完成加载类的这个职责,ClassLoader 提供了一系列的方法,比较重要的方法如 表 1 所示。关于这些方法的细节会在下面进行介绍。表 1. ClassLoader 中与加载类相关的方法方法说明getParent() 返回该类加载器的父类加载器。loadClass(String name) 加载名称为 name 的类,返回的结果是 java.lang.Class 类的实例。findClass(String name)

6、 查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。findLoadedClass(String name) 查找名称为 name 的已经被加载过的类,返回的结果是 java.lang.Class 类的实例。defineClass(String name, byte b, int off, int len) 把字节数组 b 中的内容转换成 Java 类,返回的结果是 java.lang.Class 类的实例。这个方法被声明为 final 的。resolveClass(Class c) 链接指定的 Java 类。对于 表 1 中给出的方法,表示类名称的 name

7、 参数的值是类的二进制名称。需要注意的是内部类的表示,如 com.example.Sample$1 和 com.example.Sample$Inner 等表示方式。这些方法会在下面介绍类加载器的工作机制时,做进一步的说明。下面介绍类加载器的树状组织结构。类加载器的树状组织结构Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个: 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。 扩展类加

8、载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader() 来获取它。除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader 类的方式实现自己的类加载器,以满足一些特殊的需求。除了

9、引导类加载器之外,所有的类加载器都有一个父类加载器。通过 表 1 中给出的 getParent() 方法可以得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根节点就是引导类加载器。图 1 中给出了一个典型的类加载器树状组织结构示意图,其中的箭头指向的是父类

10、加载器。图 1. 类加载器树状组织结构示意图代码清单 1 演示了类加载器的树状组织结构。清单 1. 演示类加载器的树状组织结构public class ClassLoaderTree public static void main(String args) ClassLoader loader = ClassLoaderTree.class.getClassLoader(); while (loader != null) System.out.println(loader.toString(); loader = loader.getParent(); 每个 Java 类都维护着一个指向定义它

11、的类加载器的引用,通过 getClassLoader() 方法就可以获取到此引用。代码清单 1 中通过递归调用 getParent() 方法来输出全部的父类加载器。代码清单 1 的运行结果如 代码清单 2 所示。清单 2. 演示类加载器的树状组织结构的运行结果sun.misc.Launcher$AppClassLoader9304b1 sun.misc.Launcher$ExtClassLoader190d11如 代码清单 2 所示,第一个输出的是 ClassLoaderTree 类的类加载器,即系统类加载器。它是 sun.misc.Launcher$AppClassLoader 类的实例;第

12、二个输出的是扩展类加载器,是 sun.misc.Launcher$ExtClassLoader 类的实例。需要注意的是这里并没有输出引导类加载器,这是由于有些 JDK 的实现对于父类加载器是引导类加载器的情况,getParent() 方法返回 null。在了解了类加载器的树状组织结构之后,下面介绍类加载器的代理模式。类加载器的代理模式类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。在介绍代理模式背后的动机之前,首先需要说明一下 Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还

13、要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。比如一个 Java 类 com.example.Sample,编译之后生成了字节代码文件 Sample.class。两个不同的类加载器 ClassLoaderA 和 ClassLoaderB 分别读取了这个 Sample.class 文件,并定义出两个 java.lang.Class 类的实例来表示这个类。这两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常 ClassCastExcep

14、tion。下面通过示例来具体说明。代码清单 3 中给出了 Java 类 com.example.Sample。清单 3. com.example.Sample 类package com.example; public class Sample private Sample instance; public void setSample(Object instance) this.instance = (Sample) instance; 如 代码清单 3 所示,com.example.Sample 类的方法 setSample 接受一个 java.lang.Object 类型的参数,并且会把该

15、参数强制转换成 com.example.Sample 类型。测试 Java 类是否相同的代码如 代码清单 4 所示。清单 4. 测试 Java 类是否相同public void testClassIdentity() String classDataRootPath = C:workspaceClassloaderclassData; FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath); FileSystemClassLoader fscl2 = new FileSystemClassLoader

16、(classDataRootPath); String className = com.example.Sample; try Class class1 = fscl1.loadClass(className); Object obj1 = class1.newInstance(); Class class2 = fscl2.loadClass(className); Object obj2 = class2.newInstance(); Method setSampleMethod = class1.getMethod(setSample, java.lang.Object.class);

17、setSampleMethod.invoke(obj1, obj2); catch (Exception e) e.printStackTrace(); 代码清单 4 中使用了类 FileSystemClassLoader 的两个不同实例来分别加载类 com.example.Sample,得到了两个不同的 java.lang.Class 的实例,接着通过 newInstance() 方法分别生成了两个类的对象 obj1 和 obj2,最后通过 Java 的反射 API 在对象 obj1 上调用方法 setSample,试图把对象 obj2 赋值给 obj1 内部的 instance 对象。代码

18、清单 4 的运行结果如 代码清单 5 所示。清单 5. 测试 Java 类是否相同的运行结果java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMetho

19、dAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at classloader.ClassIdentity.testClassIdentity(ClassIdentity.java:26) at classloader.ClassIdentity.main(ClassIdentity.java:9) Caused by: java.lang.ClassCastException: com.example.Sample cannot be cast to com.example.Sample at

20、 com.example.Sample.setSample(Sample.java:7) . 6 more 从 代码清单 5 给出的运行结果可以看到,运行时抛出了 java.lang.ClassCastException 异常。虽然两个对象 obj1 和 obj2 的类的名字相同,但是这两个类是由不同的类加载器实例来加载的,因此不被 Java 虚拟机认为是相同的。了解了这一点之后,就可以理解代理模式的设计动机了。代理模式是为了保证 Java 核心库的类型安全。所有 Java 应用都至少需要引用 java.lang.Object 类,也就是说在运行的时候,java.lang.Object 这个类

21、需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object 类,而且这些类之间是不兼容的。通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。不同的类加载器为相同名称的类创建了额外的名称空间。相同名称的类可以并存在 Java 虚拟机中,只需要用不同的类加载器来加载它们即可。不同类加载器加载的类之间是不兼容的,这就相当于在 Java 虚拟机内部创建了一个个相互隔离的 Java 类空间。这种技术在许多

22、框架中都被用到,后面会详细介绍。下面具体介绍类加载器加载类的详细过程。加载类的过程在前面介绍类加载器的代理模式的时候,提到过类加载器会首先代理给其它类加载器来尝试加载某个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用 defineClass 来实现的;而启动类的加载过程是通过调用 loadClass 来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器

23、启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。如类 com.example.Outer 引用了类 com.example.Inner,则由类 com.example.Outer 的定义加载器负责启动类 com.example.Inner 的加载过程。方法 loadClass() 抛出的是 java.lang.ClassNotFoundException 异常;方法 defineClass() 抛出的是 java.lang.NoClassDefFoundError 异常。类加载器在成功加载某个类之后,会把得到

24、的 java.lang.Class 类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass 方法不会被重复调用。下面讨论另外一种类加载器:线程上下文类加载器。线程上下文类加载器线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread 中的方法 getContextClassLoader() 和 setContextClassLoader(ClassLoader cl) 用来获取和设置线程的上下文

25、类加载器。如果没有通过 setContextClassLoader(ClassLoader cl) 方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。前面提到的类加载器的代理模式并不能解决 Java 应用开发中会遇到的类加载器的全部问题。Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来

26、提供,如 JAXP 的 SPI 接口定义包含在 javax.xml.parsers 包中。这些 SPI 的实现代码很可能是作为 Java 应用所依赖的 jar 包被包含进来,可以通过类路径(CLASSPATH)来找到,如实现了 JAXP SPI 的 Apache Xerces 所包含的 jar 包。SPI 接口中的代码经常需要加载具体的实现类。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory 类中的 newInstance() 方法用来生成一个新的 DocumentBuilderFactory 的实例。这里的实例的真正的类是继承自 javax

27、.xml.parsers.DocumentBuilderFactory,由 SPI 的实现所提供的。如在 Apache Xerces 中,实现的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题。线程上下文类加载器正好

28、解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。下面介绍另外一种加载类的方法:Class.forName。Class.forNameClass.forName 是一个静态方法,同样可以用来加载类。该方法有两种形式:Class.forName(String name, boolean initialize, ClassLoader loader) 和 Class.forName(String classNam

29、e)。第一种形式的参数 name 表示的是类的全名;initialize 表示是否初始化类;loader 表示加载时使用的类加载器。第二种形式则相当于设置了参数 initialize 的值为 true,loader 的值为当前类的类加载器。Class.forName 的一个很常见的用法是在加载数据库驱动的时候。如 Class.forName(org.apache.derby.jdbc.EmbeddedDriver).newInstance() 用来加载 Apache Derby 数据库的驱动。在介绍完类加载器相关的基本概念之后,下面介绍如何开发自己的类加载器。开发自己的类加载器虽然在绝大多数情

30、况下,系统默认提供的类加载器实现已经可以满足需求。但是在某些情况下,您还是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输 Java 类的字节代码,为了保证安全性,这些字节代码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类来。下面将通过两个具体的实例来说明类加载器的开发。文件系统类加载器第一个类加载器用来加载存储在文件系统上的 Java 字节代码。完整的实现如 代码清单 6 所示。清单 6. 文件系统类加载器public class FileSystemClassLoader ext

31、ends ClassLoader private String rootDir; public FileSystemClassLoader(String rootDir) this.rootDir = rootDir; protected Class findClass(String name) throws ClassNotFoundException byte classData = getClassData(name); if (classData = null) throw new ClassNotFoundException(); else return defineClass(name, classData, 0, classData.length); private byte getClassData(String className) String path = classNameToPath(className); try InputStream ins = new FileInputStream(path); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int bufferSi

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1