OSGi教程 – 08 – OSGi框架类加载机制

8.1 Bundle生命周期

在OSGi传说的第0章我们简要介绍过Bundle的生命周期,下面我们来简要回顾一下,下图是其状态转换图:

20160925_01

在上图中,人工转换箭头线上的install、update、resolve、start、stop是通过在控制台输入对应命令来触发。Bundle生命周期过程之中的6种状分别为:

1) UNINSTALLED(未安装):状态值为整数1。此时Bundle中的资源是不可用的。

2) INSTALLED(已安装):状态值为整数2。此时Bundle已经通过了OSGi框架的有效性校验并分配了Bundle ID,本地资源已加载,但尚未对其依赖关系进行解析处理。

3) RESOLVED(已解析):状态值为整数4。此时Bundle已经完成了依赖关系解析并已经找到所有依赖包,而且自身导出的Package已可以被其它Bundle导入使用。在此种状态的Bundle要么是已经准备好运行,要么就是被停止了。

4) STARTING(启动中):状态值为整数8。此时Bundle的BundleActivator的start()方法已经被调用但是尚未返回。如果start()方法正常执行结束,Bundle将自动转换到ACTIVE状态; 否则如果start()方法抛出了异常,Bundle将退回到RESOLVED状态。

5) STOPPING(停止中):状态值为整数16。此时Bundle的BundleActivator的stop()方法已经被调用但是尚未返回。无论stop()是正常结束还是抛出了异常,在这个方法退出之后,Bundle的状态都将转为RESOLVED。

6) ACTIVE(已激活):状态值为整数32。Bundle处于激活状态,说明BundleActivator的start()方法已经执行完毕,如果没有其他动作,Bundle将继续维持ACTIVE状态。

下图中使用install命令安装了位于C盘根目录的Bundle(注意表达其位置的URL格式),在安装完毕后控制台返回了对应的Bundle信息,包括ID、状态、版本等元数据信息。

20161211_001

8.2 Bundle解析

Class Loader在Bundle被正确解析(状态变成Resolved)之后创建, 而只有Bundle 中的所有包约束条件都满足后,对应的Bundle才能被正确解析完毕。

Bundle的解析由定义在MANIFEST.MF文件中的四个配置项定义:

1) Import-Package:定义当前Bundle需要导入的其它包(Package),一般位于其它Bundle之中并且被定义为Export-Package。

2) Export-Package:定义当前Bundle要导出的包。被导出的包中的类资源可以被其它Bundle导入,可以被定义到Import-Package中。

3) Require-Bundle:定义当前Bundle需要依赖的Bundle。

4) DynamicImport-Package:定义需要动态导入的包。这里定义的包在Bundle解析过程中不会被使用到,而是会在运行时被动态解析并加载。

在 Bundle 得到正确解析后,OSGi 框架将会生成对应Bundle的依赖关系表。使用bundle命令可以得到对应的依赖关系说明,如下图所示:

20161211_002

8.3 Bundle-ClassPath配置

在MANIFEST.MF文件中定义的Bundle-Classpath会描述Classpath范围,告诉Classloader去哪里查找类。

为了理解Classpath的配置,需要首先理解容器(Container)、条目(Entry)、条目路径(Entry Path)、Bundle类路径(Bundle Classpath)的概念:

1) 容器(Container):Jar包、Zip包、文件夹等,一般指Bundle的Jar包;

2) 条目(Entry):Container内包含的资源,这些资源的组织是按层级进行的。在运行时,一个Bundle中的Entry可能会来自多个容器,因为一个Bundle可能会具有多个Fragment Bundle。

3) 条目路径(Entry Path):在Bundle中查找Entry的顺序如下:

● 首先查找Bundle的容器(如果有Fragment则先查找宿主Bundle)

● 然后按id的升序查找Fragment的容器(如果有Fragment的话)

这个查找资源的顺序被称为Entry Path。Entry Path标识了资源在其容器中的路径(位置),但其加载到OSGi环境中的顺序并不是完全按这个,而是按 Bundle Classpath。

4) Bundle类路径(Bundle Classpath):指的是Bundle中资源在OSGi环境中加载的顺序,它基于Entry Path来构建。下图是一个简单的样例:

20161211_003

从上图中能看出,在MANIFEST.MF中,Bundle-ClassPath由以逗号“,”分隔的各个Entry Path组成。

除了两个jar包之外,还有一个特殊的Entry Path——小数点“.”。小数点Entry Path“.”也可以使用反斜杠“/”替代,两者都是表示容器的根路径。如果没有指定任何Bundle-Classpath,容器的根路径就是默认值。

8.4 Java类加载机制

在Java中通过类加载器(Class Loader)把一个类装入Java虚拟机中,每一个Java的Class对象都有一个指向其Class Loader的引用。类加载过程主要经过三个步骤:

1) 装载: 查找和导入类或接口的二进制数据;

2) 链接:又可以分成校验、准备和解析三步,除了解析是可选的之外,其它步骤是严格按照顺序完成的:

● 校验:检查导入类或接口的二进制数据的正确性;

● 准备:给类的静态变量分配并初始化存储空间;

● 解析:将符号引用转成直接引用;

3) 初始化:激活类的静态变量的初始化Java代码和静态Java代码块。

JVM规范定义了两种类型的类装载器:启动Class Loader(Bootstrap Class Loader)和用户自定义Class Loader(User-defined Class Loader)。

Java提供了抽象类ClassLoader,它是所有用户自定义Class Loader的父类。 System Class Loader是由JVM的实现者提供的一个特殊的用户自定义Class Loader,它可以通过ClassLoader.getSystemClassLoader() 方法得到,如果编程者不特别指定装载器则默认使用它来装载用户类。

JVM在运行时会产生三个ClassLoader:

1) BootClassLoader:用来装载核心类库,如lang.*等,确保Java基本框架的可用。它用C++编写而成,是JVM自带的类加载器,在Java中不可见,表现为null。。

2) ExtClassLoader:用来加载扩展类。ExtClassLoader的Parent为Bootstrap ClassLoader。

3) AppClassLoader: 用来加载用户自定义类。AppClassLoader的Parent是ExtClassLoader。

每个Class Loader有自己的命名空间,命名空间由被此Class Loader加载的类组成,不同命名空间的两个类是相互不可见的。不过如果能得到(例如通过反射)另一命名空间中的类所对应的Class对象的引用,则还是可以访问该类的成员变量。

由同一Class Loader装载的属于相同包的类组成了运行时包(Runtime Package),决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看Class Loader是否相同。只有属于同一运行时包的类才能互相访问包可见的类和成员。这样的限制避免了用户使用自己的代码冒充核心类库的类,进而访问核心类库包可见成员的情况。

8.5 OSGi类加载机制

Bundle的加载策略、如何导入和导出代码是在OSGi规范的模块(Modules )层实现的。OSGi框架对于每个实现了 BundleActivator 接口的 Bundle(非Fragment Bundle)都会创建一个单独的类加载器(Class Loader),不过对于Class Loader的创建可能会延迟到真正需要它时才会发生。

对应的Classloader为每个Bundle都提供了独立的命名空间来避免命名冲突,并且允许在不同的Bundle之间分享资源,这种机制使得在同一个Bundle中的资源都相互具有包(Package)级别的访问权限。

关于OSGi类加载机制的细节,可以参考OSGi规范中的“Runtime Class Loading”章节。

Bundle的Class Loader能加载的所有类的集合构成了Bundle的类空间(Class Space)。Bundle 所需要的类资源应该完全被其类空间所覆盖,否则将会在运行时环境中抛出类或资源未发现异常,从而导致 Bundle 无法正常工作。

类空间包含的类资源主要来自于以下几个方面:

1) 父Class Loader可加载的类集合;

2) Import-Package定义的依赖的包中的类集合;

3) Require-Bundle定义的依赖的Bundle中的类集合;

4) Bundle自身的类集合,通常在Bundle-Classpath中定义;

5) 隶属于Bundle的Fragment类集合。

Bundle的Class Loader搜索类资源的规则简要介绍如下:

1) 如类资源属于* 包,则将加载请求委托给父 Class Loader;

2) 如类资源定义在 OSGi框架的启动委托列表(osgi.framework.bootdelegation)中,则将加载请求委托给父Class Loader;例如:

● osgi.framework.bootdelegation=*

● osgi.framework.bootdelegation=sun.*,com.sun.*

3) 如类资源属于在 Import-Package 中定义的包,则框架会将加载请求委托给导出此包的Bundle的Class Loader;

4) 如类资源属于在Require-Bundle 中定义的 Bundle,则框架会将加载请求委托给此Bundle的Class Loader;

5) Bundle搜索自己的Bundle-Classpath中定义的类资源;

6) Bundle搜索属于该Bundle的Fragment的类资源;

7) 判断是否找到导出(Export-Package)了对应资源,如果仍未找到进入动态导入查找;

8) 若类在DynamicImport-Package中定义,则开始尝试在运行环境中寻找符合条件的 Bundle,框架会将加载请求委托给导出此包的Bundle的Class Loader;

● 注意DynamicImport-Package: *使用了星号来对所有资源进行通配,这也意味着对应的Bundle可以“看到”任何可能访问到的资源,这个选项应该尽量少地使用,除非别无它法。

9) 如果在经过上面一系列步骤后,仍然没有正确地加载到类资源,则OSGi框架会向外抛出类未发现异常。

20161211_004

8.6 Equinox框架专属的伙伴(Buddy )加载器

在Equinox框架中引入了伙伴(Buddy)Bundle的概念,在类查找流程的最后,还会追加查找Buddy Bundle中的类资源。

伙伴策略(Buddy Policy)用于定义选择伙伴类的策略,Equinox支持如下几种策略:

● dependent——查询所有直接或间接依赖当前Bundle的Bundle。在Bundle数量很多的情况下使用该策略可能会导致性能问题。

● registered ——查询所有依赖当前Bundle并且明确将当前Bundle注册为伙伴(Buddy)的Bundle。它与dependent策略很类似,但是缩小了搜索的范围。

● global ——查询所有可用的导出包(exported packages)。

● app——查询AppClassLoader。

● ext -——查询ExtClassLoader。

● boot -——查询BootClassLoader。

注意:在使用dependent和registered策略时,Bundle中的所有包对其Buddy来说都是可用的,即使没有被导出(export)也是如此。

对于需要导出资源的Bundle来说需要表态“我允许伙伴们来访问自己的资源”,具体的表示方法为在MENIFEST.MF文件中增加Eclipse-BuddyPolicy 定义,其语法如下:

例如:Eclipse-BuddyPolicy: registered表示允许其它Bundle注册为当前Bundle的Buddy。

而对于需要依赖对应Bundle资源的Buddy来说,则需要明确将自己声明为对应其它Bundle的Buddy,其语法如下:

例如:Eclipse-RegisterBuddy: org.hibernate 表示当前Bundle为名为org.hibernate的Bundle的Buddy。

注意:如果希望Bundle X注册成为Bundle Y的伙伴,则需要满足如下条件:

1) BundleY必须定义registered为Eclipse-BuddyPolicy;

2) Bundle X必须在Eclipse-RegisterBuddy中定义Bundle Y的名称(例如:Eclipse-RegisterBuddy: Y);

3) Bundle X所依赖的包必须在Bundle Y中被导出(可以通过Require-Bundle或Import-Package来定义依赖关系)

示意图如下:

20161211_005

Buddy类加载机制是Equinox框架独有的特性,与其它OSGi实现并不兼容,也不属于OSGi标准规范,它的一般应用场景是便于Bundle使用那些并非最初就设计为在OSGi环境中使用的包(例如一般Jar包)。使用此特性需要注意,你可能会因此破坏OSGi Bundle的一致性要求。

打赏一下
支付宝
微信
除非注明,博客文章均为原创,转载请标明文章地址
本文地址: http://www.javafxchina.net/blog/2016/12/osgi-08/
百度已收录