OSGi类加载流程

思路

OSGi每个模块都有自己独立的classpath。如何实现这一点呢?是因为OSGi采取了不同的类加载机制:

  • OSGi为每个bundle提供一个类加载器,该加载器能够看到bundle Jar文件内部的类和资源;
  • 为了让bundle能互相协作,可以基于依赖关系,从一个bundle类加载器委托到另一个bundle类加载器。
OSGi类加载流程
 

Java和J2EE的类加载模型都是层次化的,只能委托给上一层类加载器;

而OSGi类加载模型则是网络图状的,可以在bundle间互相委托。——这样更合理,因为bundle间的依赖关系并不是层次化的。

例如bundleA、B都依赖于bundleC,当他们访问bundleC中的类时,就会委托给bundleC的类加载器,由它来查找类;如果它发现还要依赖bundleE中的类,就会再委托给bundleE的类加载器。

优点

  • 找不到类时的错误提示更友好。假如bundleE不存在,则bundleC就不会被解析成功,会有错误消息提示为何未能解析;而不是报错ClassNotFoundException或NoClassDefFoundError。
  • 效率更高。在标准Java类加载模型中,总是会在classpath那一长串列表中进行查找;而OSGi类加载器能立即知道去哪里找类。

类加载步骤

 
OSGi类加载流程
 
 

Step 1: 检查是否java.*,或者在bootdelegation中定义

当bundle类加载器需要加载一个类时,首先检查包名是否以java.*开头,或者是否在一个特定的配置文件(org.osgi.framework.bootdelegation)中定义。如果是,则bundle类加载器立即委托给父类加载器(通常是Application类加载器)。

这么做有两个原因:

  • 唯一能够定义java.*包的类加载器是bootstrap类加载器,这个规则是JVM要求的。如果OSGI bundle类加载器试图加载这种类,则会抛Security Exception。
  • 一些JVM错误地假设父加载器委托永远会发生,内部VM类就能够通过任何类加载器找到特定的其他内部类。所以OSGi提供了org.osgi.framework.bootdelegation属性,允许对特定的包(即那些内部VM类)使用父加载器委托

Step 2: 检查是否在Import-Package中声明

检查是否在Import-Package中声明。如果是,则找到导出包的bundle,将类加载请求委托给该bundle的类加载器。如此往复。

Step 3: 检查是否在Require-Bundle中声明

检查是否在Require-Bundle中声明。如果是,则将类加载请求委托给required bundle的类加载器。

Step 4: 检查是否bundle内部类

检查是否是该bundle内部的类,即当前JAR文件中的类。

Step5:  检查fragment

搜索可能附加在当前bundle上的fragment中的内部类。

什么是fragment?

Fragment bundle是OSGi 4引入的概念,它是一种不完整的bundle,必须要附加到一个host bundle上才能工作;fragment能够为host bundle添加类或资源,在运行时,fragment中的类会合并到host bundle的内部classpath中。

fragment有什么作用?

【场景1】bundle中有针对特定平台的代码

假设bundle对不同平台的实现方式稍有不同,Windows和Linux下代码有不同之处,即bundle中有针对特定平台的代码。

我们应该为每个平台提供不同的bundle吗?——显然不能,因为那会造成代码重复。

或者将共同代码放到bundle A中,Windows特定的那部分代码放到bundle Pwin中,Linux特定的那部分代码放到bundle Plinux中。——有问题:Pwin肯定要依赖A中某些包,我们就必须在A中导出这些包,如果只有Pwin用到这些包 岂不破坏封装性。

最好的解决方法是把Pwin作为fragment,附加到A中。这样Pwin就能看到A中的所有包,A也能看到Pwin的所有包。

【场景2】针对不同国家用户提供不同的i18n

GUI程序通常会通过properties文件定义i18n信息,可以将不同的i18n存到不同的fragment中。运行时用户只需要下载host bundle以及特定的i18n fragment即可,不需要把其他国家的i18n也下载下来。

Step6: 动态类加载

//TODO

完整的类加载流程图如下:

OSGi类加载流程
The class search algorithm as described always attempts to load classes from another bundle in preference to classes that may be on the classpath of the present bundle. This may seem counterintuitive at first, but in fact it makes a lot of sense, and it fits perfectly with the conventional Java approach.
A traditional Java class loader always first delegates to its parent before attempting to define a class itself. This helps to ensure that classes are loaded as few times as possible, by favouring already-defined copies of a class over redefining it in a lower-level class loader. The opposite approach would result in many copies of the same class, which would be considered incompatible because they were defined by different class loaders.
Back in OSGi, where the class loaders are arranged in a graph rather than a tree, the same principle of minimising class loading translates to making every effort to find a class from a bundle’s imports rather than loading it from the internal bundle classpath.
 
上一篇:c++描述将一个2进制数转化成10进制数(用到初始化栈,进栈,入栈)


下一篇:微信小程序体验(2):驴妈妈景区门票即买即游