一、OSGi 规范
OSGi(Open Service Gateway Initiative) 技术是 Java 动态化模块化系统的一系列规范。OSGi 一方面指维护 OSGi 规范的 OSGi Alliance(OSGi 联盟),另一方面指的是该组织维护的基于 Java 语言的服务(业务)规范。简单来说,OSGi 可以认为是 Java 平台的模块层,为大型分布式系统以及嵌入式系统提供一种模块化架构减少了软件的复杂度。
1. 什么是 OSGi
OSGi 联盟(OSGi Alliance)于 1999 年开始着手制定 OSGi 规范,其主要目的就是要制定一套开放式标准,以便向局域网及其中的设备提供可管理的服务;其基本思路是,一旦您在网络设备(如服务器和嵌入式设备)上使用了 OSGi 服务平台,您就可以在网络上的任何地方管理这些设备上运行的软件组件的生命周期,可以在后台对这些组件进行安装、升级或卸载,但不需要打断该设备的正常运行。Eclipse 就是基于 OSGi 开发的。
2. OSGi 标准
OSGI R1 于 2000 年发布,现在最新的标准版本是 R5,到现在为止应用最广泛的当属是 2005 年发布的 R4。
其中主要定义了 OSGi 框架。OSGi 框架提供了一个通用安全可管理的 java 框架,能够支持可扩展可下载的应用(即 bundles)的部署。OSGi 框架是 OSGi 技术最基础也是最核心的部分。
这里你需要理解 OSGi 框架的三个最重要部分:模块层、生命周期层、服务层。
-
模块层 :关注打包和代码共享。
OSGi 是严格要求模块化的,模块有个专有名词 bundle。每个模块都是一个 bundle,一个 Business Logic 由多个 bundle 来实现。
注:后面全部使用 bundle 代替模块来表述。
-
生命周期层 :关注提供执行模块管理和对底层 OSGi 框架的访问。
bundle 是需要 OSGi 进行解析的,每个 bundle 在变得可用之前,都需要完整经历该生命周期。
-
服务层 :关注模块,特别是模块内的组件的交互和通讯。
OSGi 技术全面贯彻了 SOA,每个 bundle 都是其他 bundle 提供服务,夸张一点说,不提供服务的 bundle 就没有存在的价值。
2.1 OSGi 三层架构-模块层
模块化其实就是计算机科学中常见的一个概念:将一个大型系统分解为多个较小的互相协作的逻单元,通过强制设定模块之间的逻辑边界来改善系统的维护性和封装性。
模块层定义了 OSGi 中的模块 bundle:
-
bundle 是以 jar 包形式存在的个模块化物理单元,里面包含了代码,资源文件和元数据(metadata),井且 jar 包的物理边界也同时是运行时逻辑模块的封装边界。
-
bundle 是开发、部署 OSGi 应用的基本单元。
-
bundle 的核心是 META-NF 目录下的 MANIFEST.MF 文件。
-
bundle 定义了其所包含的包的可见性、可以认为是在 public/private/protected 的基础上的一个扩展。
-
bundle 的 java 包共享、屏蔽的规则。通过 Export-Package、Import-Package 方式进行交互。
-
每个 bundle 都有单独的类加加载器。
来看看在 MANIFEST.MF 中可以定义哪些 Bundle 的的元数据信息:
属性 | 属性描述 |
---|---|
Bundle-Activator | Bundle 的 Activator 类名 |
Bundle-Category | Bundle 的分类属性描述 |
Bundle-Classpath | Bundle 的 Classpath |
Bundle-Copyright | Bundle 的版权 |
Bundle-Description | Bundle 的描述信息 |
Bundle-DocURL | Bundle 的文档 URL 地址 |
Bundle-Localization | Bundle 的国际化文件 |
Bundle-ManifestVersion | 定义 Bundle 所遵循的规范的版本, OSGI R3 对应的值为1, OSGI R4 对应的值为2 |
Bundle-Name | Bundle的有意义的名称 |
Bundle-NativeCode | Bundle 所引用的 Native Code 的地址 |
bundle-RequiredExecutionEnvironment | Bundle 运行所需要的环境,如可指定为需要 OSGI R3、Java1.4、Java1.3 等 |
Bundle-SymbolicName | Bundle 的唯一标识名,可采用类似 java package 名的机制来保证唯一性 |
Bundle-Version | Bundle 的版本 |
Dynamiclmport-Package | Bundle 动态引用的 package |
Export-Package | Bundle对外暴露的 package |
Fragment-Host | Fragment 类型 Bundle 所属的 Bundle 名 |
Import-Package | Bundle 引用的 package |
Require-Bundle | Bundle 所需要引用的其他的 Bundle |
2.2 OSGi 三层架构-生命周期
状态是 Bundle 在运行期的一项动态属性,不同状态的 Bundle 具有不同的行为,生命周期层规范定义了 Bundle 生命周期过程之中的 6 种状态。
OSGi 生命周期层有两种不同的作用:
-
在应用程序外部,定义了对 bundle 生命周期的相关操作。OSGi 生命周期层允许在执行时,从外部安装、启动、更新、停止、卸载不同的 bundle 进而定制应用的配置。
-
在应用程序内部,定义了 bundle 访问其执行上下文的方式,为 bundle 提供了一种与 OSGi 框架交互的途径以及一些执行时的便利条件。
2.3 OSGi 三层架构-服务层
OSGi 的服务层除了面向服务的编程模型,还有一个区别于其他很多类似模型的特性。也就是说,当一个 bundle 发现并开始使用 OSGi 中的一个服务了以后,这个服务可能在任何的时候改变或者是消失。
OSGi 框架有一个中心化的注册表,这个注册表从 publish-find-bind 模型:
3. OSGi 纲要规范
除了上述 OSGi 核心规范(core specification)中的服务,OSGi 联盟也定义了一组非核心的(non-core)标准服务,称为 compendium 服务。Core 服务在任何运行的 OSGi 框架内都是可用的,这要求所有的 OSGi 框架都要实现核心服务。而 compendium 服务则不然。这些服务以分离的 bundle 的形式出现,由框架实施者或者第三方实现并提供,能在任何框架上运行。
- LOG Service(日志服务)
- HTTP Service(注册 servlet 和资源)
- Configuration Admin(配置管理)
- Event Admin(事件通知)
- Declarative Services(定义轻量级的面向服务的组件模型)
- Blueprint(一个类似 IOC 容器的实现)
4. OSGi 特点
从开发的角度:
- 复杂性的降低:基于 OSGi 的组件横型 bundle 能够隐藏内部实现,bundle 基于服务进行交互。
- 复用:很多第三方的组件可以以 bundle 的形式进行复用。
- 简单:核心的 API 总过包括不超过 30 个类和接口。
- 小巧: OSGi R4 和实现仅需要 300KB 的 JAR file 就足够。在系统中引入 OSGi 几乎有什么开销。
- 非侵入式:服务可以以 POJO 的形式实现,不需要关注特定的接口。
从可维护的角度:
- 切合真实运行环境:OSGi 框架是动态的,bundle 能够进行即时的更新,服务可以根据需要动态增加或者删除。
- 易于部署:OSGi 定义了组件是如何安装和管理的,标准化的管理 API 使得 OSGi。 能够和现有和将来的各种系统有机的集成。
- 动态更新:这是 OSGi 被最经常提起的一个特性,即所谓的 "热插拔" 特性,bundle 能够动态的安装、启动、停止、更新和卸载,而整个系统无需重启。
- 适配性:这主要得益于 OSGi 提供的服务机制、组件可以动态的注册、获取和监听服务,使得系统能够在 OSGi 环境调整自己的功能。
- 版本化:bundle 可以版本化,多版本能够共存而不会影响系统功能。
- 懒加载:OSGi 技术采用了很多懒加载机制。比如服务可以被注册,但是直到被使用时才创建。
5. OSGi VS 传统 java 模块化
"OSGi" 相比 "传统 java 模块化" 有以下优势:
- 基于接口编程,完全隐藏底层代码实现
- 动态性(对扩展开放,即使是运行时的)
- 版本控制
部分支持 OSGi 的项目:
Apache cxf、Apache thrift、Apache activemq、Apache curator Activiti、drools、mysql-connector-java、postgresql(java client)、rabbitmq(java client)、hazelcast
OSGi 的缺点:
- 目前只支持Java
- 入门高、技术更加复杂
- 相对的重量级
- 资料匮乏
- lib 支持的不好,很多 lib 无法在 OSGi 环境下运行
6. OSGi 开源框架介绍
-
Equinox:OSGi R4 core framework 的一个实现,一组实现各种可选的 OSGi bundle 和一些开发基于 OSGi 技术的系统所需要的基础构件。 Eclipse 是基于 Equinox 项目开发的一个典型例子。具体内容可以从 http://www.eclipse.org/equinox/ 下载。
比较适合不需要集成太多外部技术的应用,如桌面应用开发,当需要进行集成时,会遇到相当多的兼容性问题;
-
Apache Felix:实现 OSGi R4 规范(包括 OSGi 框架,Standard Service 和其它 OSGi 相关技术)的另一个开源项目。具体内容可以从 http://felix.apache.org/ 下载。
与 Equinox 非常相似,都属于基础环境。但也有一个最大的不同,其兼容性、可扩展性都比较强,能够很方便的集成 Web Container、DataSource 管理等许多实际开发中必须具备的组件。但是这里有个很大的隐患:所有的集成都需要手工完成,质量、测试都无法保证,作为系统最重要的基础运行环境,其稳定性、可靠性是至关重要的。
-
Apache Karaf:一个基于 OSGi 的运行环境,它提供了一个轻量级的 OSGi 容器,可以用于部署各种组件和应用程序。它提供了很多的组件和功能用于帮助开发人员更加灵活的部署应用,更适合作为商业化产品的开发、运行平台。具体内容可以从 http://karaf.apache.org/ 下载。
结论:使用 karaf 作为接下来的 OSGi 基础平台,进行下一步的 JavaEE 与 OSGi 整合工作。
二、 深入理解OSGi类加载机制
OSGi之所以能够实现模块热插拔和模块内部可见性的精准控制都归结于其特殊的类加载机制.加载器之间的关系不再是双亲委派模型的树状结构,而是发展成复杂的网状结构。
根据OSGi规范说明:
对于类或资源加载,框架必须遵循以下规则。 当请求bundle的类加载器加载类或资源时,必须按以下顺序执行搜索:
- 如果类或资源在java.*包中,则将请求委托给父类加载器; 否则,继续下一步搜索。 如果请求被委托给父类加载器还找不到类或资源,则搜索终止并且失败。
- 如果类或资源来自引导委派列表(系统变量org.osgi.framework.bootdelegation)中包含的包,则将请求委托给父类加载器。如果在那里找不到类或资源,继续下一步搜索。
- 如果类或资源属于声明在Import-Package导入的包中,或者是在先前的加载中动态导入的,那么请求将委托给声明Export-Package这个包的bundle的类加载器;否则继续下一步搜索。如果请求被委托给导出类加载器但找不到类或资源,则失败。
- 如果类或资源位于在多个Require-Bundle包中导入的包中,则请求将按照清单中指定的顺序委派给其他包的类加载器。这个过程中使用深度优先策略;如果未找到类或资源,则继续下一步搜索。
- 搜索bundle的内嵌jar的类路径(Bundle Class Path)。如果找不到类或资源,继续下一步。
- 查找Bundle的Fragment Bundle中导入的包, 如果没找到继续下一步
- 如果类或资源位于自己导出的包中,则搜索结束并失败。
- 否则,如果类或资源位于DynamicImport-Package导入的包中,则尝试动态导入包。
- 如果动态导入包成功,则将请求委托给导出这个包的类加载器。如果请求被委托给导出类加载器并且找不到类或资源,则搜索终止且失败。
总体图:
但这种模式也会产生许多隐患,比如循环依赖问题,如果BundleA依赖BundleB , BundleB依赖BundleC, BundleC又依赖BundleA, 这可能在加载Bundle的时候导致死锁问题。为了避免这种情况,根据OSGi规范说明,在这种情况下,框架必须在第一次访问Bundle的时候做标记,不去访问已经访问过的Bundle.
另外,在OSGi中Bundle都有自己独有的ClassLoader
, Fragment Bundle不同于普通Bundle, 其和其附着的Host Bundle共享一个ClassLoader.
三、Java默认类加载器机制和OSGI类加载器机制
Java默认类加载机制介绍
类加载器
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
类加载器可以说是Java语言的一项创新,也是Java语言流行的重要原因之一,它最初是为了满足Java Applet的需求而开发出来的。虽然目前Java Applet技术基本上已经“死掉”,但类加载器却在类层次划分、OSGi、热部署、代码加密等领域大放异彩,成为了Java技术体系中一块重要的基石,可谓是失之桑榆,收之东隅。
类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
双亲委派模型
从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。
从Java开发人员的角度来看, 类加载器还可以划分得更细致一些, 绝大部分Java程序都会使用到以下3种系统提供的类加载器。
1)启动类加载器(Bootstrap ClassLoader):前面已经介绍过,这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。
2)扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher.ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
3)应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher.AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(Class Path)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
我们的应用程序都是由这3种类加载器互相配合进行加载的,如果有必要,还可以加入自己定义的类加载器。这些类加载器之间的关系一般如下图所示。
图中展示的类加载器之间的这种层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。类加载器的双亲委派模型在JDK 1.2期间被引入并被广泛应用于之后几乎所有的Java程序中,但它并不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载器实现方式。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的Class Path中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。如果读者有兴趣的话,可以尝试去编写一个与rt.jar类库中已有类重名的Java类,将会发现可以正常编译,但永远无法被加载运行。
双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现却非常简单,实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中,如以下代码所示,逻辑清晰易懂:
[java] view plain copy
- protected synchronized Class<?> loadClass(String name, boolean resolve)
- throws ClassNotFoundException
- {
- //首先, 检查请求的类是否已经被加载过了
- Class c=findLoadedClass(name);
- if( c== null ){
- try{
- if( parent != null ){
- c = parent.loadClass(name,false);
- } else {
- c = findBootstrapClassOrNull(name);
- }
- } catch (ClassNotFoundException e) {
- //如果父类加载器抛出ClassNotFoundException
- //说明父类加载器无法完成加载请求
- }
- if( c == null ) {
- //在父类加载器无法加载的时候
- //再调用本身的findClass方法来进行类加载
- c = findClass(name);
- }
- }
- if(resolve){
- resolveClass(c);
- }
- return c;
- }
先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。双亲委派的具体逻辑就实现在这个loadClass()方法之中,JDK 1.2之后已不提倡用户再去覆盖loadClass()方法,而应当把自己的类加载逻辑写到findClass()方法中,在loadClass()方法的逻辑里如果父类加载失败,则会调用自己的findClass()方法来完成加载,这样就可以保证新写出来的类加载器是符合双亲委派规则的。
打破双亲委派模型
上文提到过双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的类加载器实现方式。在Java的世界中大部分的类加载器都遵循这个模型,但也有例外。
双亲委派模型的一次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载),基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完美,如果基础类又要调用回用户的代码,那该怎么办?这并非是不可能的事情,一个典型的例子便是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK 1.3时放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的Class Path下的JNDI接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能“认识”这些代码,因为启动类加载器的搜索范围中找不到用户应用程序类,那该怎么办?为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器(Application ClassLoader)。
有了线程上下文类加载器,就可以做一些“舞弊”的事情了,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。
双亲委派模型的另一次“被破坏”是由于用户对程序动态性的追求而导致的,这里所说的“动态性”指的是当前一些非常“热门”的名词:代码热替换(HotSwap)、模块热部署(HotDeployment)等,说白了就是希望应用程序能像我们的计算机外设那样,接上鼠标、U盘,不用重启机器就能立即使用,鼠标有问题或要升级就换个鼠标,不用停机也不用重启。对于个人计算机来说,重启一次其实没有什么大不了的,但对于一些生产系统来说,关机重启一次可能就要被列为生产事故,这种情况下热部署就对软件开发者,尤其是企业级软件开发者具有很大的吸引力。Sun公司所提出的JSR-294、JSR-277规范在与JCP组织的模块化规范之争中落败给JSR-291(即OSGi R4.2),虽然Sun不甘失去Java模块化的主导权,独立在发展Jigsaw项目,但目前OSGi已经成为了业界“事实上”的Java模块化标准,而OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(OSGi中称为Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。
在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当收到类加载请求时,OSGi将按照下面的顺序进行类搜索:
1)将以java.*开头的类委派给父类加载器加载。
2)否则,将委派列表名单内的类委派给父类加载器加载。
3)否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载。
4)否则,查找当前Bundle的Class Path,使用自己的类加载器加载。
5)否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载。
6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。
7)否则,类查找失败。
上面的查找顺序中只有开头两点仍然符合双亲委派规则,其余的类查找都是在平级的类加载器中进行的。
只要有足够意义和理由,突破已有的原则就可认为是一种创新。正如OSGi中的类加载器并不符合传统的双亲委派的类加载器,并且业界对其为了实现热部署而带来的额外的高复杂度还存在不少争议,但在Java程序员中基本有一个共识:OSGi中对类加载器的使用是很值得学习的,弄懂了OSGi的实现,就可以算是掌握了类加载器的精髓。
OSGi:灵活的类加载器架构
既然说到OSGI,就要来解释一下OSGi是什么,以及它的作用
OSGi(Open Service Gateway Initiative):是OSGi联盟指定的一个基于Java语言的动态模块化规范,这个规范最初是由Sun、IBM、爱立信等公司联合发起,目的是使服务提供商通过住宅网管为各种家用智能设备提供各种服务,后来这个规范在Java的其他技术领域也有不错的发展,现在已经成为Java世界中的“事实上”的模块化标准,并且已经有了Equinox、Felix等成熟的实现。OSGi在Java程序员中最著名的应用案例就是Eclipse IDE
OSGi中的每一个模块(称为Bundle)与普通的Java类库区别并不大,两者一般都以JAR格式进行封装,并且内部存储的都是Java Package和Class。但是一个Bundle可以声明它所依赖的Java Package(通过Import-Package描述),也可以声明他允许导出发布的Java Package(通过Export-Package描述)。在OSGi里面,Bundle之间的依赖关系从传统的上层模块依赖底层模块转变为平级模块之间的依赖(至少外观上如此),而且类库的可见性能得到精确的控制,一个模块里只有被Export过的Package才可能由外界访问,其他的Package和Class将会隐藏起来。除了更精确的模块划分和可见性控制外,引入OSGi的另外一个重要理由是,基于OSGi的程序很可能可以实现模块级的热插拔功能,当程序升级更新或调试除错时,可以只停用、重新安装然后启动程序的其中一部分,这对企业级程序开发来说是一个非常有诱惑性的特性
OSGi之所以能有上述“诱人”的特点,要归功于它灵活的类加载器架构。OSGi的Bundle类加载器之间只有规则,没有固定的委派关系。例如,某个Bundle声明了一个它依赖的Package,如果有其他的Bundle声明发布了这个Package,那么所有对这个Package的类加载动作都会为派给发布他的Bundle类加载器去完成。不涉及某个具体的Package时,各个Bundle加载器是平级关系,只有具体使用某个Package和Class的时候,才会根据Package导入导出定义来构造Bundle间的委派和依赖
另外,一个Bundle类加载器为其他Bundle提供服务时,会根据Export-Package列表严格控制访问范围。如果一个类存在于Bundle的类库中但是没有被Export,那么这个Bundle的类加载器能找到这个类,但不会提供给其他Bundle使用,而且OSGi平台也不会把其他Bundle的类加载请求分配给这个Bundle来处理
一个例子:假设存在BundleA、BundleB、BundleC三个模块,并且这三个Bundle定义的依赖关系如下:
BundleA:声明发布了packageA,依赖了java.*的包
BundleB:声明依赖了packageA和packageC,同时也依赖了Java.*的包
BundleC:声明发布了packageC,依赖了packageA
那么,这三个Bundle之间的类加载器及父类加载器之间的关系如下图:
由于没有涉及到具体的OSGi实现,所以上图中的类加载器没有指明具体的加载器实现,只是一个体现了加载器之间关系的概念模型,并且只是体现了OSGi中最简单的加载器委派关系。一般来说,在OSGi中,加载一个类可能发生的查找行为和委派关系会比上图中显示的复杂,类加载时的查找规则如下:
1)以java.*开头的类,委派给父类加载器加载
2)否则,委派列表名单内的类,委派给父类加载器加载
3)否则,Import列表中的类,委派给Export这个类的Bundle的类加载器加载
4)否则,查找当前Bundle的ClassPath,使用自己的类加载器加载
5)否则,查找是否在自己的Fragment Bundle中,如果是,则委派给Fragment bundle的类加载器加载
6)否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载
7)否则,查找失败
从之前的图可以看出,在OSGi里面,加载器的关系不再是双亲委派模型的树形架构,而是已经进一步发展成了一种更复杂的、运行时才能确定的网状结构。