就像Java Web应用程序需要运行在Tomcat、Weblogic这样的容器中一样。程序员开发的OSGI程序包也需要运行在OSGI容器中。目前主流的OSGI容器包括:Apache Felix以及Eclipse Equinox。OSGI程序包在OSGI中称作Bundle
。
Bundle
的整个生命周期都交与OSGI容器进行管理。可以在不停止服务的情况下,对Bundle
进行加载和卸载,实现热部署。
Bundle
对于外部程序来说就是一个黑盒。他只是向OSGI容器中注册了供外部调用的服务接口,至于实现则对外部不可见。不同的Bundle
之间的调用,也需要通过OSGI容器来实现。
刚才说到Bundle
是一个黑盒,他所有实现都包装到了自己这个“盒子”中。在开发Bundle
时,避免不了引用一些比如Spring、Apache commons等开源包。在为Bundle
打包时,可以将当前Bundle
依赖jar与Bundle
的源码都打包成一个包(all-in-one)。这种打包结果就是打出的包过大,经常要几兆或者十几兆,这样当然我们是不可接受的。下面就介绍一种更优的做法。
Bundle
可以在MANIFEST.MF
配置文件中声明他要想运行起来所要的包以及这些包的版本 !!!而OSGI容器在加载Bundle
时会为Bundle
提供Bundle
所需要的包 !!!在启动OSGI容器时,需要在OSGI配置文件中定义org.osgi.framework.system.packages.extra
,属性。这个属性定义了 OSGI容器能提供的包以及包的版本。OSGI在加载Bundle
时,会将他自己能提供的包以及版本与Bundle所需要的包以及版本列表进行匹配。如果匹配不成功则直接抛出异常:
也可能加载Bundle
通过,但是运行Bundle
时报ClassNotFoundException
。这些异常都由于配置文件没配置造成的。理解了配置文件的配置方法,就能解决60%的异常。
在Bundle
的Import-Package
属性中通过以下格式配置:
语法与Impirt-Package
基本一致,只是org.osgi.framework.system.packages.extra
不支持通配符。
在我们平时开发中有些情况下加载一个Class会使用this.getClassLoader().loadClass
。但是通过这种方法加载Bundle
中所书写的类的class
会失败,会报ClassNotFoundException
。在Bundle
需要使用下面的方式来替换classLoader.loadClass
方法
由于Bundle
加载Class
的特性,会导致在加载Spring配置文件时报错。所以需要将Spring启动所需要的ClassLoader进行更改,使其调用BundleContext.loadClass
来加载Class。
这里选用了Apache Felix
来开发,主要是因为Apache Felix
是Apache的顶级项目。社区活跃,对OSGI功能支持比较完备,并且文档例子比较全面。
其实OSGI支持两种方式来部署Bundle
。
从项目的整体考虑,我们选用了第二种方案。
开发Bundle
时,首先需要开发一个BundleActivator
。OSGI在加载Bundle
时,首先调用BundleActivator
的start
方法,对Bundle
进行初始化。在卸载Bundle
时,会调用stop
方法来对资源进行释放。
在start
方法中调用context.registerService
来完成对外服务的注册。
通过上面的配置,只是将OSGI容器加载到了Web应用中。还需要修改Web应用程序路由的代码。
在Apache Felix
例子中提供的ProvisionActivator
,只会在系统启动时加载/WEB-INF/bundles/
目录下的Bundle
。当文件夹下的Bundle
文件有更新时,并不会自动更新OSGI容器中的Bundle
。所以Bundle
自动加载的逻辑,需要我们自己增加。下面提供实现的思路:
最后一个问题,通过上面的方式,可以实现Bundle
的自动加载。但是刚才我们介绍了,在路由程序中,我们会缓存OSGI容器中所有的Bundle
所对应的ServiceReference
以及所有Bundle
所对应的servlet-pattern
。所以Bundle
自动更新后,我们还需要将路由程序中的缓存同步的进行更新。
可以通过向bundleContext
中注册BundleListener
,当OSGI容器中的Bundle
状态更新后,会调用BundleListener
的bundleChanged
回调方法。然后我们可以在bundleChanged
回调方法中书写更新路由缓存的逻辑