bootdelegation vs. org.osgi.framework.system.packages.extra

osgi alliance support

osgi alliance support

在用osgi的过程中,如果需要osgi容器内外互通,则一般需要osgi容器内外共享类,否则会抛出各种ClassNotFoundException或者ClassCastException。常见的解决方法有两种,即把需要共享的类所在包名配置在这两个osgi启动参数上:org.osgi.framework.bootdelegation和org.osgi.framework.system.packages.extra。

下面分别介绍这两个参数的特点(以下解释都是基于osgi-core-4.3.0):

org.osgi.framework.bootdelegation

配置这个参数时往往需要一起配置另一个参数:org.osgi.framework.bundle.parent(详见osgi.core-4.3.0.pdf的9.1.15.47节),用来指定配置在org.osgi.framework.bootdelegation上的包中的类由什么类加载器加载,这个参数的值有如下几个:boot(默认)、ext、app、framework四个。

  1. boot:父加载器默认是用的这个,引导类加载器(bootstrap class loader),它用来加载 Java 的核心库,是用原生代码来实现的。
  2. ext:扩展类加载器(extensions class loader),它用来加载 Java 的扩展库。
  3. app:系统类加载器(system class loader,下文中出现的app类加载器均指的是系统类加载器),它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
  4. framework:osgi框架容器的类加载器。

由于通常我们的程序都是系统类加载器来加载的,而默认的osgi容器的父加载器是boot(引导类加载器),这两个加载器加载同一个类得到的实例和引用是不能相互赋值转换的,为了能让osgi容器中的bundle能共用容器外的类且由同一个加载器加载,就必须设置这些被共用的类由相同的类加载器来加载,所以一般设置为系统类加载器,即org.osgi.framework.bundle.parent=app,参考:Java类加载器的双亲委派模型

osgi.core-4.3.0.pdf的3.9.3节:
委托给父加载器加载的包清单格式规范如下:
org.osgi.framework.bootdelegation ::= boot-description ( ‘,’ boot-description )*
boot-description::= package-name | ( package-name ‘.*’ ) | ‘*’
比如:org.osgi.framework.bootdelegation=sun.*,com.sun.*

  • *是通配符,意味着com.acme.*可匹配所有com.acme.下的所有子包,但是不包括com.acme包。注意此时的通配符仅仅是对package的通配,不会对类进行统配,比如com.acme.*这个通配是不能匹配com.acme.AAA的,但是可以匹配com.acme.sav.BBB和com.acme.sav.ttr.CCC,因为com.acme.*的意思是用”com.acme.”对包名(不包括类目以及类目前面的点号)作为前缀匹配,只要符合前缀条件就匹配上了。
  • 如果不使用通配符,则表示精确匹配。
  • 以java.*为前缀的包会默认委托给父加载器来加载。

凡是配置在org.osgi.framework.bootdelegation参数的包中的类,都是由org.osgi.framework.bundle.parent对应的类加载器来加载。osgi中的bundle需要使用这些包,无需其他配置,也不用import这些包,classloader会在碰到这个类的时候优先委托给org.osgi.framework.bundle.parent对应的类加载器加载。

适用场景:某些包所在的jar不可能转成bundle时,只能通过org.osgi.framework.bootdelegation参数来达到osgi内外共享。比如:rt.jar中的类,rt.jar是jdk、jre中的jar包,这个jar不是bundle,你也不太可能去把这个包转换成bundle,osgi内部的bundle自然就无法和外部共享这些类了,而bootdelegation参数正好解决了这个问题。

org.osgi.framework.system.packages.extra

org.osgi.framework.system.packages.extra是对org.osgi.framework.system.packages的扩展,配置到这个参数上的包都将被osgi framework类加载器加载(通常就是AppClassloader加载),这些包可以来自bundle,也可以来自非bundle,往往osgi framework类加载器就是app类加载器(除非自己写了个类加载来加载启动osgi的类,比如:在web容器中使用osgi,那么osgi framework类加载器就是web容器加载war包的类加载器,不是app类加载器),所以这些类外部app和osgi容器内部是共用class的,可以相互赋值和互传。同时这个参数还可以设置包的版本。注意:org.osgi.framework.system.packages与该参数基本意义相同,但是不能随意给org.osgi.framework.system.packages设置值,因为他的默认值是框架所需的包,默认值为org.osgi.framework,如果设置其他值,则会覆盖原来的默认值,所以一般都是给org.osgi.framework.system.packages.extra设置值,用来把某些包中的类共享到osgi外。

org.osgi.framework.system.packages.extra的配置规则如下:

  1. 可以配置多个package,每个package之间用逗号隔开
  2. package可以配置版本信息,如果没有配置版本,则默认其版本为0.0.0
  3. package不能写成类似的org.osgi.framework.bootdelegation通配符形式,因为extra相当于启动osgi容器的classloader export了package给osgi中其他的classloader,用到这些package的bundle需要显性的import这些package(import时若没写版本号,则默认import的版本号为0.0.0),且import的包的版本号不能大于org.osgi.framework.system.packages.extra里面配置的版本号。如果大于的话,会抛出类似这样的错误: org.osgi.framework.BundleException: Unresolved constraint in bundle hello.impl [8]: Unable to resolve 8.0: missing requirement [8.0] osgi.wiring.package; (&(osgi.wiring.package=ipojo.example.hello)(version>=1.5.0))

对osgi类加载顺序不了解的请看这篇:osgi类加载顺序源码解析

有以下的两种case需要注意:

  • 如果一个bundle X包被bundle Y require了(即在require-bundle中),如果这个required bundle X中的包packageA export出去了,然后在org.osgi.framework.system.packages.extra中设置了这个packageA,如果bundle Y中的某个类使用了这个packageA中的类A,但没有import packageA,那么类A不会由app classloader加载,因为类加载顺序是先bootdelegation,再import-package,再require-bundle,再local,(import里面以extra的包优先,require-bundle里面没有extra的逻辑),因为这个bundle没有import packageA,所以尽管配置了org.osgi.framework.system.packages.extra,也不会走进import的逻辑,而是进入了local的逻辑,从而由bundle自己的classloader加载packageA中的类。
  • 如果bundle Z中包含packageA,若想要让pacakgeA由app classloader加载,且让bundle Z中的其他类也能使用pacakgeA中的类,则需要bundle Z同时export和import这个packageA,并在extra中配置packageA,且外部调用的应用也要把这个package所在jar包作为第三方包依赖上(否则,app classloader在加载package中的类是会发现在app classloader的classpath中找不到这个类,因为这个类在bundle的classpath中。也就是说其实app classloader加载是外部应用依赖的jar包中的packageA,而不是bundle中的packageA,但是二者是一样的,app classloader一旦加载过,class实例就已经存在了,后面碰到相同的类时就不需要再加载了。也就是说在运行时bundle的jar中的packageA并没有作用,但是为了编译通过,bundle依赖了packageA而已,这都是为了让osgi容器内外互通的不得已。

一切都是按照类加载顺序逻辑来!!!!!

总结对比

为了简化,以下bootdelegation代表org.osgi.framework.bootdelegation,extra代表org.osgi.framework.system.packages.extra

  1. bootdelegation和extra都是为了解决osgi内外类共享的问题。
  2. 配置语法上各有优劣,bootdelegation支持通配符,但不支持包版本;extra不支持通配符,但支持包版本配置。
  3. bootdelegation可以指定4种类加载器,extra只能是app classloader。
  4. bootdelegation配置的包不需要在bundle的menifest.mf中import,整个osgi中甚至可以根本没有bootdelegation配置的包;如果其他bundle想用extra配置的包,那需要其他bundle import这个包的,只是这个包不再是由包所在的bundle的类加载器加载,而是由app classloader加载。如果extra配置的包是某个指定版本的,那么import了这个指定版本的bundle将使用framework类加载器加载,相同包的其他版本还是由bundle的classloader加载。
  5. 形象的说,bootdelegation相当于是把osgi外部的类共享给了osgi内部使用;extra相当于把osgi内部的类共享给了外部的使用,当然extra也可以用于把osgi外部的类共享给osgi内部使用,只是需要想用外部类的bundle import一下。

Reference

Post Footer automatically generated by wp-posturl plugin for wordpress.