前言
本文档以 TongWeb8.0.9.10 版本为基础进行说明。
关于JVM类加载的详细解析,可参阅如下资源:
- 文章: 深入分析Java ClassLoader原理-CSDN博客
- 视频:尚硅谷JVM全套教程(详解java虚拟机)_哔哩哔哩_bilibili(讲解风趣幽默、通俗易懂,适合JVM初学者)
一、JVM类加载基础说明
JVM中类加载器加载class文件时,采用委托模式(即双亲委派机制),具体流程如下:
- 类加载器首先判断该class是否已加载成功;
- 若未加载,不会直接自身加载,而是先委托给父加载器,递归向上直至BootStrap ClassLoader;
- 若BootStrap ClassLoader找到该class,则直接返回;若未找到,则逐级向下返回,最终由自身查找加载。
核心流程:委托过程从下向上,实际查找过程从上至下。
二、TongWeb类加载结构说明
TongWeb打破了JVM默认的双亲委派模式,会优先从自身类路径下查找类。控制台的类加载结构树界面,可清晰展示各父子类加载器及其对应的加载路径。
TongWeb中的类加载器分为两大类:JVM自带类加载器和TongWeb自定义类加载器,具体如下:
2.1 JVM自带类加载器
- BootstrapClassLoader:加载${JAVA_HOME}/jre/目录下的核心库;
- sun.misc.Launcher$ExtClassLoader(扩展类加载器):加载${JAVA_HOME}/jre/lib/ext/目录下的扩展包;
- sun.misc.Launcher$AppClassLoader(应用/系统类加载器):加载CLASSPATH路径下的包,以及boot/tongweb-bootstrap.jar。
2.2 TongWeb自定义类加载器
- com.tongweb.server.startup.ClassLoaderFactory
- 职责:负责加载TongWeb的核心库;
- 加载路径:
- ${tongweb.base}/lib/
- ${tongweb.home}/lib/
- ${tongweb.home}/version8.0.9.10/
2. java.net.URLClassLoader
- 职责:加载应用公共路径;
- 加载路径:
- ${tongweb.base}/lib(仅对当前实例有效)
- ${tongweb.home}/lib(对所有实例有效)
3. com.tongweb.ee.server.TongWebWebappClassLoader
- 职责:负责加载Web应用WEB-INF/classes/和WEB-INF/lib/*.jar下的类,同时负责加载应用自定义的公共类库;
- 特性:为每个应用单独创建实例,应用卸载后,对应的类加载器会被销毁。
三、TongWeb类加载模式
3.1 类加载顺序(父优先 vs 子优先)
TongWeb部署应用时,可选择两种类加载优先级,核心区别如下:
- 父优先:优先加载TongWeb自带的类;
- 子优先:优先加载应用自带的类(TongWeb默认模式)。
测试验证(以A.jar中的com.test.main.niubi类为例):
- 将A.jar分别放入${tongweb.home}/lib/app和应用WEB-INF/lib路径下;
- 默认子优先模式:访问应用,该类由TongWebWebappClassLoader加载(优先从应用自身类路径查找)
- 切换父优先模式:访问应用,该类由URLClassLoader加载(优先从TongWeb自带路径查找);
- 删除应用WEB-INF/lib/A.jar中的目标类,默认子优先模式下,该类由URLClassLoader加载(向上查找父加载器)。
结论:TongWeb的类加载顺序与JVM默认双亲委派机制相反,通过打破该机制,实现应用间的类隔离。
打破双亲委派的核心原因(解决版本冲突问题):
假设TongWeb部署两个应用,存在版本不兼容的依赖需求:
- 应用A:需要log4j-1.2.17.jar;
- 应用B:需要log4j-2.17.1.jar(与1.x版本不兼容)。
若采用JVM双亲委派机制,会出现如下问题:
- 应用A请求加载log4j类,委派给URLClassLoader,加载log4j-1.2.17.jar;
- 应用B请求加载log4j类,URLClassLoader发现该类已加载,直接返回log4j-1.2.17.jar;
- 应用B因版本不匹配报错。
TongWeb的子优先模式可避免该问题,实现不同应用加载各自所需的依赖版本。
3.2 强制加载规则
- 强制从应用加载的类
- 作用:在默认开启子优先模式时,一些JEE规范实现的类,仍然会从服务器类路径查找,为了规避这个问题,可以配置强制从应用路径下加载类;
- 应用场景:解决应用使用高版本规范迁移到TongWeb后与容器自带的兼容性问题(如应用使用的hibernate高版本带的jakarta.persistence高于TongWeb8带jakarta.persistence)
Caused by: java.lang.NoSuchFieldError: UUID at org.hibernate.id.factory.internal.StandardIdentifierGeneratorFactory.registerJpaGenerators(StandardIdentifierGeneratorFactory.java:111) at org.hibernate.id.factory.internal.StandardIdentifierGeneratorFactory.<init>(StandardIdentifierGeneratorFactory.java:86) at org.hibernate.id.factory.internal.StandardIdentifierGeneratorFactory.<init>(StandardIdentifierGeneratorFactory.java:77) at org.hibernate.id.factory.internal.StandardIdentifierGeneratorFactoryInitiator.initiateService(StandardIdentifierGeneratorFactoryInitiator.java:25) at org.hibernate.id.factory.internal.StandardIdentifierGeneratorFactoryInitiator.initiateService(StandardIdentifierGeneratorFactoryInitiator.java:15) at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:130) at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263) at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:238) at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215) at org.hibernate.boot.internal.MetadataBuilderImpl$MetadataBuildingOptionsImpl.<init>(MetadataBuilderImpl.java:621) at org.hibernate.boot.internal.MetadataBuilderImpl.<init>(MetadataBuilderImpl.java:139) at org.hibernate.boot.MetadataSources.getMetadataBuilder(MetadataSources.java:164)- 强制从TongWeb加载的类
- 作用:默认子优先模式时,也可指定某些类由TongWeb类加载器去加载;
- 优先级:高于“强制从应用加载的类”。
3.3 公共类库的使用
公共类库用于提供给不同应用可共享的外部公共资源,配置公共类库后,通过控制台类加载结构树可发现:公共类库同样由TongWebWebappClassLoader负责加载。
3.4 应用jar包加载顺序
- 默认规则:TongWeb默认按文件名排序加载(如a.jar优先于b.jar)
- 应用场景: 若应用jar中有相同类,如a.jar,b.jar中有一个同名类
- 自定义顺序:可通过配置控制jar包加载顺序
通常POC会用到,实际应用场景很少见
3.5 TongWeb自定义类加载器实现步骤
- 编写自定义类加载器demo,继承com.tongweb.ee.server.TongWebWebappClassLoader;
- 导入TongWeb相关依赖(可通过mvn install方式将TongWeb依赖导入maven仓库);
- 将自定义类加载器打成jar包,放入${tongweb.base}/lib目录下,重启TongWeb;
- 在控制台部署应用时,选择自定义的类加载器;
- 部署成功后,通过控制台类加载结构树,可查看自定义类加载器的名称;
- 访问应用并查看日志,确认自定义类加载器(如CustomWebappClassLoader)已加载相关资源;日志中返回null,说明该类由BootstrapClassLoader加载(BootstrapClassLoader基于C++编写,在Java中返回null
3.6 TongWeb部署应用类加载过程(实际案例)
以Spring6应用从Tomcat10迁移到TongWeb8为例,部署时报错排查过程如下:
- 报错现象:应用部署失败,排查发现应用jasperreports-6.21.4.jar中,BaseHttpServlet.class引入了javax.servlet.http.HttpServlet;
- 解决方案:过滤该jar包,让TongWeb不扫描它
- 补充说明:该jar包实际无业务作用,过滤后应用部署成功,功能测试正常;
- 核心结论:TongWeb部署应用时,会扫描加载应用WEB-INF/lib目录下的jar和classes目录下的class文件;若配置过滤jar包,则仅加载classes目录下的类;class文件中引入的jar相关类,会在实际使用时才触发加载。
四、补充说明与部署建议
- 其他资源加载说明:控制台和官方手册中已详细说明,若遇到资源冲突问题,可根据错误日志开启对应开关进行测试;
部署建议:
- 应用部署时,推荐使用“默认”配置;
- 若出现类加载异常,需根据日志排查问题;
- Web应用建议开启“Web兼容模式”;ejb-jar、ear应用不可开启“Web兼容模式”,可配置“加载应用实现”;
结语
- 类加载机制的核心:每层类加载器有明确的职责范围,仅加载自身目录下的资源;
- TongWeb类加载核心特性:默认子优先模式,为每个应用单独创建 TongWebWebappClassLoader实例,部署时会扫描并加载应用下的第三方lib和class文件;
- 掌握TongWeb类加载机制及资源加载逻辑,可有效解决实际工作中的类冲突等复杂问题;