【0040】你了解OSGI类加载器架构吗?
前言
OSGI是什么?又有什么特性?支持热插拔是吧?是的,这些问题都会在本篇文章中给出相应回答,OSGI的独特之处,在我看来是其灵活的类加载器架构,并不遵循传统的“双亲委派模型”,新奇的事物总有其吸引人的一面。
本文大纲:
1、 OSGI定义
2、 OSGI特性
3、 OSGI类加载器架构
4、 应用场景
5、 总结
一、OSGI定义
OSGI(Open Service GatewayInitiative)是OSGI联盟制定的一个基于Java语言的动态模块化规范。
二、OSGI特性
1、模块划分
OSGI中的每个模块称为Bundle,与普通的Java类库区别不大,都是以JAR格式进行封装,并且内部存储的都是Java Package和Class。但是一个Bundle可以声明它锁依赖的Java Package(通过Import-Package描述),也可以声明它允许导出发布的Java Package(通过Export-Package描述)。
2、精准的类库可见性控制
在OSGI里面,Bundle之间的依赖关系从传统的上层模块依赖底层模块转变为平级模块之间的依赖,而且类库的可见性能得到非常精准的控制,一个模块里只有被Export过的Package才可能由外界访问,其他的Package和Class将会隐藏起来。
3、模块级的热插拔
由于其具有精准的类库可见性控制能力,基于OSGI的程序很可能可以实现模块级的热插拔功能,当程序升级更新或调试除错时,可以只停用、重新安装然后启动程序的其中一部分,这是一个对企业级程序开发很好的特性。
三、OSGI类加载器架构
1、类加载原则
在阐述OSGI的类加载器之前,不妨先了解一下OSGI类加载的原则。JDK中的类加载遵循“双亲委派”的委派原则,但在OSGI中,没有使用这个委派模型,OSGI中的Bundle类加载器之间只有规则,没有固定的委派关系,这是两者最大的不同。
例如:某个Bundle声明了一个它依赖的Package,如果有其它的Bundle声明发布了这个Package,那么所有对这个Package的类加载动作都会委派给发布它的Bundle类加载器取完成。
不涉及某个具体的Package时,各个Bundle加载器都是平级的关系,只有具体使用某个Package和Class的时候,才会根据Package导入导出定义来构造Bundle间的委派和关系。
2、控制访问规则
既然OSGI不遵循“双亲委派模型”,所以其类加载的时候必定有一套自己的协定,通俗而言,就是有一些访问控制规则。
类加载时可能进行的查询规则如下:
-
以java.*开头的类,委派给父类加载器加载。
-
否则,委派列表名单内的类,委派给父类加载器加载。
-
否则,Import列表中的类,委派给Export这个类的Bundle的类加载器加载。
-
否则,查找当前的Bundle的Classpath,使用自己的类加载器加载。
-
否则,查找是否在自己的Fragment Bundle中,如果是,则委派给Fragment Bundle的类加载器加载。
-
否则,查找Dynamic Import列表的Bundle,委派给对应的Bundle的类加载器加载。
-
否则,类查找失败。
3、OSGI类加载器架构
我们通过一个简单的例子来引出OSGI的类加载器架构。
假设存在BundleA,BundleB,BundleC三个模块,并且这三个Bundle定义的依赖关系如下。
BundleA:声明发布了packageA,依赖了java.*的包。
BundleB:声明依赖了packageA和packageC,同时也依赖了java.*的包。
BundleC:声明发布了packageC,依赖了packageA。
那么这三个Bundle之间的类加载器及父类加载器之间的关系如下图所示:
OSGI的类加载器之间的关系不在是“双亲委派模型”的树形结构,而是已经进一步发展成了一种更为复杂的、运行时才能确定的网状结构。上图是一个概念模型,因为不同的依赖关系,其实际委派加载关系也是不同的。
四、应用场景
OSGI是动态模块化程序设计的规范,应用场景使用的是其热插拔特性,大致有下面场景:
1、想要升级某一个模块或子系统,但是又不能关闭其它服务。
2、需要动态添加一些服务(跟踪,调试),这些服务就是添加某个模块。
五、总结
本文通过先阐述OSGI的定义、特性,然后通过一个例子引出类加载器架构,最后也提出了其应用的场景。表面上看来OSGI似乎很牛,支持热插拔,但是任何技术都有两面性,OSGI也有缺点,比如应用程序模块数量庞大时,依赖关系错综复杂,容易导致线程死锁和内存泄露。但是,相信你已经对OSGI有一个整体的认识了。
参考文献
《深入理解Java虚拟机》 周志明
始发于微信公众号: Java框架源码分析