1、背景
笔者刚毕业去有长面试时被问到:看你简历上写学校做过几个web项目,要不聊聊你对web容器类加载的了解。时隔多年,最近做项目遇到一个问题:web项目在jetty下运行没有问题,但是当项目迁移到jboss,就出现项目无法启动的问题,查看console日志可以发现出现了一堆 没有指向类路径的引用“缺失的依赖关系”,导致启动jboss失败。在不同容器下跑出现这个问题,极大可能是jboss可能加载了某些jar包和应用程序有了冲突。想想之前的面试经历,在此写些jboss的类加载机制,希望可以对读者有所帮助。
2、JBoss的类加载
J2EE规范在这块有所提及:web容器应该可让应用自由选择所需的依赖及其版本,也就是说应用服务器应该屏蔽加载依赖库可能存在的冲突。
jboss的类加载机制经过了几次改进。
2.1 统一类加载器(UnifiedClassloader)
4.X版本的jboss采用的统一类加载方式,其主要思想就是通过共享相同的类库或者副本来降低应用的通信成本。这种机制明显的问题就是,如果两个应用A,B。A需要使用B应用的某个库。当B升级重新部署时,A应该也要重新部署,否则A实际上用的是过时的依赖。这在大型项目开发,依赖比较复杂的场景下去完成这些依赖分析,然后根据依赖逐一更新,成本不小,尤其是依赖旧版本导致的问题不容易发现,给容易给生产环境埋雷。
2.2 虚拟文件系统(VFS)
jboss的类加载器是基于vfs(虚拟文件系统),vfs的作用如下:
(1)vfs用于定位jar和类文件
(2)类加载器向后兼容
(3)vfs还会用于一些文件处理工作,类加载语法和之前类似。
[图片]
如上图所示,其中每个UCL是NoAnnotationClassLoader的实例,每个NoAnnotationClasLoader是继承自URLClassLoader。当进程启动的时候,这个类加载器就会被创建,用来加载$JBOSS_HOME/lib的的类资源。它的上一级类加载器是 System CLass Loader
JBOSS类加载会覆盖父亲委托机制,首先由UCL去加载repository cache的jar,如果没有找到,那么UCL会从repository中加载jar,这样设计就可以让应用加载自己的所需版本的依赖。如果依然没有找到,那么转到父亲委托机制去加载jar。
jboss中不同war间的共享类空间
一个jboss实例可以部署多个应用,为了应用之间的资源共享需要如何处理:
(1) 将类和资源放在jboss/server/default/lib下面
(2)采用jboss的WebLoader来加载jar(WEB-INF/classed, WEB-INF/lib),它将这些类加载到缺省的类共享加载器仓库中,实现资源间的共享。
但存在一个问题,如果修改了共享类库中的资源,会影响对其依赖的所有项目,因此一般不会选择在应用级别进行共享。因为两个应用通过值传递方式进行共享,会涉及到序列化过程。 也就是,JBoss5.x引入了VFS,但依然没有解决依赖问题。
2.3 模块化机制(module project)
AS7之后,类加载是基于模块的,每个应用被部署成一个模块。那么该机制是如何解决依赖问题的咧?
首先需要说明的是,应用和应用之间是隔离的,或者说模块和模块之间是隔离的。其主要的实现机制就是将依赖加到module以及应用服务器中。
为了实现公用类共享以及应用依赖的隔离,该机制引入了隐式依赖和显示依赖概念,从而解决了共享和隔离的问题。
2.3.1 隐式依赖
有些依赖在企业级应用中经常用到,因此它们会被自动添加到应用服务器中,还有些是根据配置文件或者标注(模块中)来加载这些jar。例如 beans.xml来连接各个自动下载的依赖
[图片]
上图就是jboss会隐式加入的依赖,应用如需要可直接引用。
2.3.2 显式依赖
一些和应用相关的或者特定场景需要的依赖就需要显示指定,比如在META-INF/MANIFEST.MF指定或者在jboss的配置文件也可以指定,实现应用层面依赖的隔离。
主要注意的是!!!在依赖隔离情况下,由于每个项目在部署时可以拿到类加载器,这样便可以加载到自己想要的版本,不需要重启JVM来重新加载类。但是,如果是共享类,涉及的应用在依赖新类时必须重启。
参考:
http://www.mastertheboss.com/jboss-server/jboss-as-7/jboss-as-7-classloading
https://developer.jboss.org/wiki/JBossClassLoadingUseCases
网友评论