在我们搭建运行环境时,有时候会碰到各种各样的问题,比较常见的是ClassNotFoundException和NoClassDefFoundException,这样的问题还是比较简单的,一般我们只要查看一下缺少的类,将类包放进加载目录就可以的。有的问题就比较奇怪了,譬如说,在启动的时候,一点错误也没有,但是起来之后却提示404,无法访问;再就是,启动的时候每条日志都会重复打印,而且起来之后怎么也登录不进去等等。其实只要了解了java和Web 应用服务器的类加载机制之后,套用神探狄仁杰的一句话说“这些看似不可思议的问题背后,其实都有一个合理的原因”。那么接下来,我来介绍一下我所了解的java及Web 应用服务器的类加载机制。
Java 中的系统提供的类加载器主要有下面三个:
引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader() 来获取它。
除了引导类加载器之外,所有的类加载器都有一个父类加载器。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java 类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根节点就是引导类加载器。图 1 中给出了一个典型的类加载器树状组织结构示意图,其中的箭头指向的是父类加载器。
类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。那么Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。
对于运行在 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-INF/lib下或者全部放到共享目录下。听起来好像也没有什么问题,反正都是能够被系统加载的,只要放全了,放哪不行啊。那么真的可行吗?
其实对于单个web应用,确实放哪都可以,因为对于单个web应用是没有共享和私有的区分的,只要把该放的类放全了,那么类就可以被正确的加载和使用。但是当一个web服务下存在多个应用时,问题就来啦。首先假如把类全部放在共享的classPath下,那么就会出现我们开头所提到的“在启动的时候,一点错误也没有,但是起来之后却提示404”的问题,为什么?因为我们的web服务在加载的时候只加载了一份webwork相关的类,这导致了这些类在初始化的时候,只初始化了一个web应用的action配置,当我们要访问web服务中的另外一个应用时,系统只好告诉你“Could not execute action”了,因为他根本不知道这个世界上还有另外一个应用存在。那么反过来,我们把全部的类都放到WEB-INF/lib下总万事大吉了吧,我用我的全部类,你用你的全部类,无非是容器累一点,多加载一份类而已。OK,真的能够如愿以偿吗。不是的,举个简单的例子,在我们zjport一期的单点登录中使用了globalSession,这是保存在web容器的单个共享实例中的,假如我们人为的将这些共享类放到私有的加载路径下,这就会导致“启动的时候每条日志都会重复打印,而且起来之后怎么也登录不进去”,因为globalSession不共享了,所以当我们登录到zjport之后,由于我们的菜单配置有个默认显示页面,这个页面可能不是zjport本身这个应用的,这时候单点登录就会校验不通过,马上就跳到起始的登录页面。
本文的理论部分参考:http://www.ibm.com/developerworks/cn/java/j-lo-classloader/?ca=drs-tp4608#download
分享到:
相关推荐
浅谈SpringBoot2.4 配置文件加载机制大变化 Spring Boot 2.4 中的配置文件加载机制发生了重大变化,这些变化将影响到应用程序的配置文件加载方式。下面是相关知识点的详细介绍: 一、为什么要进行这些更改 随着...
- **应用程序类加载器(Application ClassLoader)**:也称为系统类加载器,负责加载环境变量`CLASSPATH`或`-cp`指定的类。 - **自定义类加载器**:开发者可以继承`java.lang.ClassLoader`,实现自己的类加载逻辑。...
在本资料"浅谈为什么java命令运行class文件出现异常共7页.pdf"中,可能详细讨论了这些常见问题及其解决方法。由于提供的压缩包中仅有一个名为"赚钱项目"的文件,这似乎与Java编程无关,因此可能不是解决上述问题的...
"浅谈 JVM 原理" JVM(Java Virtual Machine)是一种虚拟机,它可以模拟完整的硬件系统功能,运行在一个完全隔离的环境中,提供了一个完整的计算机系统。JVM 可以分为三类:VMWare、Visual Box 和 JVM。其中,...
Android中的ClassLoader机制是Java应用程序运行的核心组成部分,它负责查找并加载类文件到Java虚拟机(JVM)中,使得程序能够执行相应的代码。在Android系统中,ClassLoader的使用与Java环境有所不同,主要体现在...
"浅谈JAVA虚拟机JVM及工作原理" Java虚拟机(JVM)是Java语言的 runtime 环境,它提供了一个平台独立的环境,使得Java程序可以跨平台运行。JVM 的主要组件包括虚拟机栈、堆、方法区、程序计数器、本地方法栈等。 1...
在Java中,类加载是一项非常重要的任务,因为它负责将Java源代码编译后的字节码文件(.class文件)加载到内存中,并准备运行时环境。类加载机制的主要功能包括: 1. **加载**:将字节码文件读入内存,并转化为二...
共享函数库中的函数是在一个可执行程序在启动的时候被加载,如果一个共享函数库正常安装,所有的程序在重新运行的时候都可以自动加载最新的函数库中的函数,如果想覆盖某个库中的某些函数,用自己的函数替换它们,...
本文主要讨论了在WEBSHELL环境下如何在不同脚本语言中运行系统命令,特别是针对Windows服务器的情况。以下是对每个章节的详细解释: 0×00 前言: 这部分阐述了随着互联网的发展,B/S架构的广泛应用带来的便利性...
- **创建模板**:加载XML文件后,可以通过“插入字段”功能将数据字段添加到Word文档中。也可以通过插入表格或图表的方式更加灵活地展示数据。 - **预览**:创建的模板可以通过PDF预览功能查看最终的效果。 ##### 3...
一个好的低频减载方案需要满足以下几点标准:首先,可适用于过负荷或者其他各种运行环境下,并能够在以上环境下实现系统频率迅速恢复到额定范围之内。其次,整个系统的动态性能,因此在监测的过程中,因根据其特性,...
Java虚拟机(JVM)是Java编程语言的核心组成部分,它为Java程序提供了跨平台的运行环境。Java的“一次编写,到处运行”特性主要得益于JVM的存在。在JVM内部,程序被编译成字节码,这是一种平台无关的中间表示,可以...
**标题:“浅谈Datafactory”** Datafactory是一个强大的数据处理工具,它允许用户构建、部署和管理数据集成工作流。这个工具的核心理念是提供一个灵活的平台,以便于在不同的数据源之间移动、转换和清洗数据,从而...
Bootloader是操作系统运行之前执行的一小段程序,用来初始化硬件设备,建立一个系统空间映射图和一个适当的系统软硬件环境。最终Bootloader把操作系统内核映像加载到RAM中。并将系统控制权交给内核。BootLoader...
在Linux系统中,环境变量是控制程序运行的关键因素,它们定义了系统如何查找并执行命令、定位文件以及如何与其他程序进行交互。环境变量分为系统级和用户级,这两种类型的环境变量有着不同的作用范围和配置文件。 ...
《浅谈开关电源的电路保护》一文详细探讨了开关电源在现代电子设备中的广泛应用及其重要性,尤其强调了电路保护对于确保电源安全性和可靠性的重要性。文章从开关电源的基本概念出发,介绍了其工作原理和成本优势,...