`
footman265
  • 浏览: 118931 次
  • 性别: Icon_minigender_1
  • 来自: 宁波
社区版块
存档分类
最新评论

深入探讨 Java 类加载器2

    博客分类:
  • j2SE
阅读更多

开发自己的类加载器

  我的天,爱死这作者了!!(转载者泪流满面ing)

虽然在绝大多数情况下,系统默认提供的类加载器实现已经可以满足需求。但是在某些情况下,您还是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输 Java 类的字节代码,为了保证安全性,这些字节代码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类来。下面将通过两个具体的实例来说明类加载器的开发。

文件系统类加载器

第一个类加载器用来加载存储在文件系统上的 Java 字节代码。完整的实现如 代码清单 6 所示。


清单 6. 文件系统类加载器

public class FileSystemClassLoader extends ClassLoader { 

    private String rootDir; 

    public FileSystemClassLoader(String rootDir) { 
        this.rootDir = rootDir; 
    } 
    //覆盖ClassLoader 的方法
    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); //产生Class
        } 
    } 

    private byte[] getClassData(String className) { 
        //得到类的系统路径
        String path = classNameToPath(className); 
        try { 
            InputStream ins = new FileInputStream(path); 
            ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
            int bufferSize = 4096; 
            byte[] buffer = new byte[bufferSize]; 
            int bytesNumRead = 0; 
            while ((bytesNumRead = ins.read(buffer)) != -1) { 
                baos.write(buffer, 0, bytesNumRead); //将从文件里读到的字节码写入StringBuff里
            } 
            return baos.toByteArray(); //返回字节数组
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
        return null; 
    } 

    private String classNameToPath(String className) { 
        return rootDir + File.separatorChar 
                + className.replace('.', File.separatorChar) + ".class"; 
    } 
}


如 代码清单 6 所示,类 FileSystemClassLoader 继承自类 java.lang.ClassLoader。在 表 1 中列出的 java.lang.ClassLoader 类的常用方法中,一般来说,自己开发的类加载器只需要覆写 findClass(String name) 方法即可。java.lang.ClassLoader 类的方法 loadClass() 封装了前面提到的代理模式的实现该方法会首先调用 findLoadedClass() 方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的 loadClass() 方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用 findClass() 方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写 loadClass() 方法,而是覆写 findClass() 方法

类 FileSystemClassLoader 的 findClass() 方法首先根据类的全名在硬盘上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过 defineClass() 方法来把这些字节代码转换成 java.lang.Class 类的实例。

网络类加载器

   下面将通过一个网络类加载器来说明如何通过类加载器来实现组件的动态更新。即基本的场景是:Java 字节代码(.class)文件存放在服务器上,客户端通过网络的方式获取字节代码并执行。当有版本更新的时候,只需要替换掉服务器上保存的文件即可。通过类加载器可以比较简单的实现这种需求。

类 NetworkClassLoader 负责通过网络下载 Java 类字节代码并定义出 Java 类。它的实现与 FileSystemClassLoader 类似。在通过 NetworkClassLoader 加载了某个版本的类之后,一般有两种做法来使用它。第一种做法是使用 Java 反射 API。另外一种做法是使用接口。需要注意的是,并不能直接在客户端代码中引用从服务器上下载的类,因为客户端代码的类加载器找不到这些类。使用 Java 反射 API 可以直接调用 Java 类的方法。而使用接口的做法则是把接口的类放在客户端中,从服务器上加载实现此接口的不同版本的类。在客户端通过相同的接口来使用这些实现类。网络类加载器的具体代码见 下载

在介绍完如何开发自己的类加载器之后,下面说明类加载器和 Web 容器的关系。

类加载器与 Web 容器

对于运行在 Java EE™ 容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。

绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:

  • 每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes 和 WEB-INF/lib 目录下面。
  • 多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。
  • 当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确


在介绍完类加载器与 Web 容器的关系之后,下面介绍它与 OSGi 的关系。

 

类加载器与 OSGi

OSGi™ 是 Java 上的动态模块系统。它为开发人员提供了面向服务和基于组件的运行环境,并提供标准的方式用来管理软件的生命周期。OSGi 已经被实现和部署在很多产品上,在开源社区也得到了广泛的支持。Eclipse 就是基于 OSGi 技术来构建的。

OSGi 中的每个模块(bundle)都包含 Java 包和类。模块可以声明它所依赖的需要导入(import)的其它模块的 Java 包和类(通过 Import-Package),也可以声明导出(export)自己的包和类,供其它模块使用(通过 Export-Package)。也就是说需要能够隐藏和共享一个模块中的某些 Java 包和类。这是通过 OSGi 特有的类加载器机制来实现的。OSGi 中的每个模块都有对应的一个类加载器。它负责加载模块自己包含的 Java 包和类。当它需要加载 Java 核心库的类时(以 java 开头的包和类),它会代理给父类加载器(通常是启动类加载器)来完成。当它需要加载所导入的 Java 类时,它会代理给导出此 Java 类的模块来完成加载。模块也可以显式的声明某些 Java 包和类,必须由父类加载器来加载。只需要设置系统属性 org.osgi.framework.bootdelegation 的值即可。

假设有两个模块 bundleA 和 bundleB,它们都有自己对应的类加载器 classLoaderA 和 classLoaderB。在 bundleA 中包含类com.bundleA.Sample,并且该类被声明为导出的,也就是说可以被其它模块所使用的。bundleB 声明了导入 bundleA 提供的类 com.bundleA.Sample,并包含一个类 com.bundleB.NewSample 继承自 com.bundleA.Sample。在 bundleB 启动的时候,其类加载器 classLoaderB 需要加载类 com.bundleB.NewSample,进而需要加载类 com.bundleA.Sample。由于 bundleB 声明了类com.bundleA.Sample 是导入的,classLoaderB 把加载类 com.bundleA.Sample 的工作代理给导出该类的 bundleA 的类加载器 classLoaderA。classLoaderA 在其模块内部查找类 com.bundleA.Sample 并定义它,所得到的类 com.bundleA.Sample 实例就可以被所有声明导入了此类的模块使用。对于以 java 开头的类,都是由父类加载器来加载的。如果声明了系统属性org.osgi.framework.bootdelegation=com.example.core.*,那么对于包 com.example.core 中的类,都是由父类加载器来完成的。

OSGi 模块的这种类加载器结构,使得一个类的不同版本可以共存在 Java 虚拟机中,带来了很大的灵活性。不过它的这种不同,也会给开发人员带来一些麻烦,尤其当模块需要使用第三方提供的库的时候。下面提供几条比较好的建议:

  • 如果一个类库只有一个模块使用,把该类库的 jar 包放在模块中,在 Bundle-ClassPath 中指明即可。
  • 如果一个类库被多个模块共用,可以为这个类库单独的创建一个模块,把其它模块需要用到的 Java 包声明为导出的。其它模块声明导入这些类。
  • 如果类库提供了 SPI 接口,并且利用线程上下文类加载器来加载 SPI 实现的 Java 类,有可能会找不到 Java 类。如果出现了 NoClassDefFoundError 异常,首先检查当前线程的上下文类加载器是否正确。通过Thread.currentThread().getContextClassLoader() 就可以得到该类加载器。该类加载器应该是该模块对应的类加载器。如果不是的话,可以首先通过 class.getClassLoader() 来得到模块对应的类加载器,再通过Thread.currentThread().setContextClassLoader() 来设置当前线程的上下文类加载器。

 

总结

类加载器是 Java 语言的一个创新。它使得动态安装和更新软件组件成为可能。本文详细介绍了类加载器的相关话题,包括基本概念、代理模式、线程上下文类加载器、与 Web 容器和 OSGi 的关系等。开发人员在遇到 ClassNotFoundException和 NoClassDefFoundError 等异常的时候,应该检查抛出异常的类的类加载器和当前线程的上下文类加载器,从中可以发现问题的所在。在开发自己的类加载器的时候,需要注意与已有的类加载器组织结构的协调。

 

分享到:
评论

相关推荐

    深入探讨 Java 类加载器.wps

    深入探讨 Java 类加载器.wps深入探讨 Java 类加载器.wps深入探讨 Java 类加载器.wps

    深入探讨 Java 类加载器

    【深入探讨 Java 类加载器】 Java 类加载器是Java虚拟机(JVM)的核心组成部分,它的主要任务是将Java类的字节码加载到JVM中以便执行。类加载器的概念始于JDK 1.0,最初是为了解决Java Applet在浏览器中的运行需求...

    深入探讨 Java 类加载器.pdf

    ### 深入探讨Java类加载器 #### 类加载器基本概念 类加载器(Class Loader)是Java语言的关键组成部分之一,它负责将Java类的字节码加载到Java虚拟机(JVM)中,从而使得Java应用程序能够运行起来。自Java诞生以来...

    深入解析Java类加载器及其工作机制

    本文深入探讨了 Java 类加载器的概念、类型、工作机制以及如何实现自定义类加载器。通过理解类加载器,开发者可以更好地控制类的加载过程,提高应用的性能和安全性。希望本文能帮助你更深入地理解 Java 类加载器及其...

    java类加载器

    本文将深入探讨类加载器的工作原理,主要类加载器类型,并通过示例展示如何自定义类加载器。 首先,理解类与数据的区别是重要的。类是代码的蓝图,包含方法和字段声明,而数据则是这些类实例的运行时状态。每个类都...

    java类加载器-tomcat中的类加载器

    下面我们将深入探讨Java类加载器以及Tomcat中的类加载器。 在Java中,类加载器主要分为三个层次:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。Bootstrap ClassLoader负责加载JDK的核心库,如rt...

    Java的类加载器

    下面将详细探讨类加载器的工作原理、层次结构以及自定义类加载器的应用。 1. **类加载的生命周期** 类加载的过程包括加载、验证、准备、解析和初始化五个阶段。加载是将类的二进制数据读取到内存中;验证确保类的...

    深入Java虚拟机_002_深入详解JVM之类加载器深度剖析、根、扩展及系统类加载器

    本部分我们将深入探讨JVM中的类加载器,特别是根类加载器、扩展类加载器和系统类加载器。 首先,让我们了解类加载的基本过程。当JVM启动时,会触发类加载。这个过程分为三个阶段:加载、链接和初始化。加载阶段,类...

    Java类加载内幕详细讲解

    本文旨在深入探讨Java类加载的过程、原理及其在实际开发中的应用。 #### 二、类与数据的概念 在Java中,一个类定义了对象的行为与状态。类定义了可执行的代码,而数据则代表了对象的状态。状态是可以变化的,但...

    深入理解Java类加载.docx

    本文将深入探讨Java类加载的各个方面。 首先,类的生命周期包括加载、验证、准备、解析和初始化五个阶段。加载阶段,JVM通过全限定类名获取二进制字节流,将其转换为方法区的运行时数据结构,并创建一个代表该类的...

    Java类加载器:静态变量初始化.docx

    这篇文章将深入探讨 Java 类加载器中的静态变量初始化机制,了解其背后的工作原理和载入过程。 静态变量初始化机制 ------------------ 在 Java 中,静态变量是指在类加载时初始化的变量。静态变量的初始化是由类...

    springboot+java类热加载

    本文将深入探讨"springboot+java类热加载"这一主题,包括其核心概念、实现方式以及相关技术。 **热加载(Hot Loading)**是开发过程中的一个重要功能,它允许开发者在不重启应用的情况下更新代码,提高开发效率。在...

    java 静态代码块通过类加载器获取资源文件例子

    在Java编程中,静态代码块(Static Block)和类加载器(Class Loader)是两个重要的概念,它们在软件开发中有着广泛的应用。本案例聚焦于如何利用静态代码块结合类加载器来高效地获取资源文件,尤其是属性配置文件。...

    Java类加载说明

    本文将深入探讨Java类加载的原理、过程以及相关知识点。 首先,我们需要理解类加载的三个基本阶段:加载、验证、准备、解析和初始化。在加载阶段,类加载器寻找指定的类文件,并将其内容读入内存。验证阶段确保字节...

    Java类加载机制

    本文将深入探讨 Java 类加载机制的核心概念和技术细节,帮助开发者更好地理解 Java 类加载的过程,以及如何利用这一机制解决实际问题。 #### 二、类和数据的关系 在 Java 中,类和数据有着密切的关系。类定义了...

    自定义类加载器实现自定义加载

    本篇文章将深入探讨如何实现自定义类加载器以及其背后的原理。 1. **类加载器的工作原理** - 类加载过程分为三个阶段:加载、验证、准备、解析和初始化。在加载阶段,类加载器寻找并加载类的二进制数据。 - Java...

    自定义类加载器

    本文将深入探讨自定义类加载器的基本概念、工作原理以及如何创建一个简单的基于磁盘的自定义类加载器。 ### 类加载器的层次结构 Java中的类加载器通常形成一个树状的层次结构,其中Bootstrap ClassLoader是最顶层...

    Java 动态加载jar文件示例

    下面我们将深入探讨Java动态加载jar文件的原理和实践方法。 首先,我们需要理解Java的类加载机制。Java中的类是由类加载器(ClassLoader)负责加载的。默认情况下,Java虚拟机(JVM)提供了三个内置的类加载器:...

    JAVA类加载器分析--热部署的缺陷(有代码示例,及分析)

    总之,“JAVA类加载器分析--热部署的缺陷”这篇博文深入探讨了Java中的类加载机制,特别是热部署过程中可能出现的问题,以及如何通过理解这些机制来优化和解决问题。了解这些知识对于开发和维护大型Java应用至关重要...

    Java之——类热加载

    本篇文章将深入探讨Java中的类热加载机制以及如何实现。 1. 类加载过程 Java类的加载分为三个阶段:加载、验证、准备、解析和初始化。当类被加载时,类加载器会查找并读取类的字节码文件(.class),将其转换为...

Global site tag (gtag.js) - Google Analytics