最好的入门材料:
这里还有用Webex产品录制的OSGi 的简单培训 OSGiInAction
当前的开源社区对OSGi 支持的基本情况是:
1. OSGi R4 core framework specification 2. Equinox latest Release3.2.2 3. Spring-OSGi 1.0 M2 4. The Felix 0.8.0-incubator release is now available in Apache. 5. Knopflerfish 2.0.1 released
OSGi 的特别适用于对于热插拨有强烈需求的系统,同时适合经常有系统模块的增加和删除的系统。如果你的系统没有上面的两种需求的一种,建议当前不要用OSGi 。当然这个不是定论,只是我的个人研究结果,原因如下:
在其Spec中有专门的章节讲述其ClassLoader的实现原理。但是当涉及到ClassLoader,我们必须面对一些我们不愿看到的事实, 实际上Java中ClassLoader的策略和管理上比较混乱。导致当前Java开源很多的Framwork和lib都有自己的 ClassLoader。如果我们的系统不用Spring,Log4J之类的框架,全部是自己的代码的话,是没有太多的问题。可以当你要引用第三方的库之 后,就要面对因为ClassLoader不同,而资源不能共享的问题。 Apache Commons Logging的Thread classloader的本来的目的是想在不同的线程能用不同的log4j配置文件,但是把Apache Commons Logging 引入 到OSGi 中就出现了问题,所以才有网上热烈讨论的SLF4j的解决方案。
仅管Spring-OSGi 已经提出了针对ClassLoader问题的解决方案,但是并没有纳入到OSGi 的标准中。就当前形势看OSGi 更倾向于实现自己的一套Bundles的解决方案,包括log, IoC,http, servlet。至今还没有考虑怎么兼容其它已经存在的类库的问题。
OSGi R4的提出了一个Declare Service的Bundle,类似于Spring IoC的机制,目的是为了减少OSGi 的 侵入性,因为在R3时,用户必须用代码的方式注册Service和引用Service。 R4只要编写一个component的xml文件将现有的类声明为Service。而且不需要强制性实现接口,这是一个很好的趋势。只是还没有做到更好。 因为没有做到象Spring IoC那么强大,DS所支持的properites只是基本类型,还不能象spring那样直接注入bean。 <property name=”domain” ref=”domainBean”/>。不过希望总是有的,Spring-OSGi 的提出就是为了解决这些问题的。只是一个正式项目用Spring-OSGi 的M2版,似乎有些不可控的风险。这些项目都在发展...
Eclipse本身尽管是用OSGi 作核心的,但是对OSGi 的开发的支持还是有一些小问题的。比如对封闭 jar包 的bundle,在IDE的工程中其它的bundle就不能识别。只能将jar包 解开为classes,然后再放到bundle中才行。
问题虽小,便是却很烦人。
但是上面的问题现在都已经有一定的解决方案,而且OSGi 在发展中,所以在OSGi R5中可能都会得到很好解决
因为当时是想在正式项目中应用OSGi ,所以不是写几个Test的那种尝试,愿能给大家一点帮助
我选择的环境比较大众化。
Eclipse3.3M6(使用最新的,是想看看对OSGi 的支持有没有更好一点,其实用Eclipse3.2.2也是一样的), Equinox OSGi R4
1) 先建立自己的我们的OSGi 运行环境。建立根目录,且定为D:\osgi \ 建立如下图的目录和文件。startup.jar是从Eclipse安装目录copy过来的。其实这个目录结构和Eclipse的安装目录结构是一样的。
Backup.bat是在windows下的启动脚本,其内容是: java -jar startup.jar –console
然后在Eclipse安装目录plugin子目录中copy下列OSGi 运行的基本jar包 到D:\osgi \plugins
org.eclipse.osgi _3.2.2.R32x_v20070118.jar
org.eclipse.osgi .services_3.1.100.v20060601.jar
再到http://download.eclipse.org/eclipse/equinox/drops/R-3.2.2-200702121330/index.php
去下载declare service bundle实现以及log实现。
org.eclipse.equinox.log_1.0.1.R32x_v20060717.jar
org.eclipse.equinox.ds_1.0.0.v20060601a.jar
同样copy到D:\osgi \plugins
接下来我们来配置文件。
配置文件为D:\osgi \configuration\config.ini
将其内容写为
#Configuration File #Mon Apr 23 09:17:19 CST 2007 osgi .clean=true osgi .console= osgi .noShutdown=true osgi .bundles=org.eclipse.osgi .services_3.1.100.v20060601.jar@start, \ org.eclipse.equinox.ds_1.0.0.v20060601a.jar@start, \ org.eclipse.equinox.log_1.0.1.R32x_v20060717.jar @start osgi .bundles.defaultStartLevel=4 osgi .framework=plugins/org.eclipse.osgi _3.2.2.R32x_v20070118.jar osgi .configuration.cascaded=false eclipse.ignoreApp=true eof=eof
针对这个配置文件需要说明几点:
1. 每个key的含义网上已经有资料说明 2. osgi .bundles中声明多个jar的时候,如果需要换行要加 “\”。这个网上一些资料没有说明,害我折腾了老半天。 3. osgi .console,一般情况下空着,表示启动后就进入console模式。 如果填入一个prot号,表示是telnet的port。这个时候OSGi 进入telnet服务模式,用户可以用telnet来进行访问。 网上有人说用Equinox不能进入后台模式,而改用了Knopflerfish, 可能是不知道这种配置的作用吧,我已经在Linux下做过测试,用nohup ./backup.sh & 可以正常运行。 4. 还有一点就是对orgi.bundles配置时,bundle的声明顺序要注意。在同一级别的起动情况下, 请将越基础的bundle越声明在前面。因为我们不可能将启动顺序完全依赖于startLevel。
OK,运行backup.bat你应该会进入OSGi 的控制台。我们的自己的OSGi 环境已经搭建好了。
2) 将OSGi 运行目录配置到Eclipse中 Eclipse默认的OSGi 工作目录是Eclipse安装目录,所以我们需要配置Eclipse使其指向我们自己的OSGi 目录(D:\osgi \) 菜单Window/Preferences
Eclipse会自动将D:\osgi \plugins下的bundles都加载过来。下次在plugins下的jar有增减的话,打开该页面按Relod即可。
3) 建立一个OSGi Project 不建议用Eclipse的向导来建立Project,我们的工程路径一般是cvs或是svn目录,而不是eclipse workspace。所以我摸索出的建立工程的方法如下:先用向导建一个空的bundle工程
将这个在Eclipse workspace目录下建立的工程目录copy到你工程目录下,以后作为模板目录。
且定我们的工程目录如下
D:\cvs\myproject\ \bundles\ \bundle_proejct_template\build.properties \src\ \test\ \bin\ \META-INF\MANIFEST.MF \OSGI -INF\
下次我们建立自己的项目时,第一步先copy一份bundle_proejct_template目录,
(如果有CVS目录,请将所有CVS目录清除),再将目录更名为你project的name
比如我们建了一个bundle工程起名为famework,
D:\cvs\myproject\bundles\framework\
这样一个Bundle工程就建立好了如果Eclipse没有将工程自动识别为OSGi bundle工程的话,可以在Eclipse package Explorer 中右键后如下图。
4) 我们的项目经常会用到log包,可是Apache common logging的ClassLoader机制和OSGi 的不兼容,所以在正式项目之前,我们必须解决这个难题。我用的网上是SLF4j的解决方案。在解决之前我们必须了解一些背景,首先我们打Log,经常会用到两个jar包 , 一个是commons-loggin包,另一个是log4j包。本质上我们是可以直接用log4j包来打log的,但是为了做到兼容其它的log,比如 Jdklog,simple log等等。其实个人觉得这个在具体的一个项目内都没什么大的意义。可是一个类库,比如spring,他就不能定死为log4j。所以spring内部的 logger都是用的commons-logging的LogFactory.getLog()。来作为log的接口,而没有直接用log4j。我们可以 在设置java环境变量 “org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger” 来指定commons logging 的实现为log4j. 而SLF4j呢,又更高一层地兼容commons logging(也是就JCL),同时兼容log4j,并且没有ClassLoader问题。
5) 建立slf4j-osgi bundle project 我们没有必要写代码,只是简单地将slf4j的jar包 做一层bundle的封装,再重新打包成jar包 ,并且作为bundle发布。先按上面介绍的方法建立slf4j-osgi bundle proejct 再download slf4j,将 slf4j-api-1.3.1.jar jcl104-over-slf4j-1.3.1.jar log4j-1.2.14.jar slf4j-log4j12-1.3.1.jar copy到 D:\cvs\myproject\bundles\slf4j-osgi \lib\ 下
D:\cvs\myproject\bundles\slf4j-osgi \META-INF\ MANIFEST.MF文件内容如下
Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: slflog4josgi Bundle-SymbolicName: slf.commons.logging.osgi Bundle-Version: 1.1.0 Bundle-Vendor: slf Bundle-Localization: plugin Bundle-ClassPath: bin/, lib/jcl104-over-slf4j-1.3.1.jar, lib/slf4j-api-1.3.1.jar, lib/log4j-1.2.14.jar, lib/slf4j-log4j12-1.3.1.jar Export-Package: org.apache.commons.logging, org.apache.log4j, org.apache.log4j.spi, org.apache.log4j.xml, org.slf4j Import-Package: org.osgi .framework;version="1.3.0"
D:\cvs\myproject\bundles\slf4j-osgi \build.properites文件内容如下
source.. = src/ bin.includes = bin/,\ META-INF/,\ lib/jcl104-over-slf4j-1.3.1.jar,\ lib/slf4j-api-1.3.1.jar,\ lib/log4j-1.2.14.jar,\ lib/slf4j-log4j12-1.3.1.jar
6) 将OSGi bundle工程导出为jar包 在Eclipse Project Exploer工作区,右键选择”Export…”
7) 建立一个new bundle project,以测试slf4j-osgi ,为了使这个例子有一定的意义,
我们让这个bundle可以为控制台输出一个简单命令hello。
工程目录且为
D:\cvs\myproject\bundles\test-slf4j\
package gsb.service .console ; import org.eclipse.osgi .framework.console.CommandInterpreter; import org.eclipse.osgi .framework.console.CommandProvider; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class SitesProviderConsole implements CommandProvider { private Log logger = LogFactory.getLog ( CommandProvider.class ) ; public String getHelp( ) { StringBuffer buffer = new StringBuffer ( ) ; buffer.append ( "\t sayHello – Just a test\n " ) ; return buffer.toString ( ) ; } public void _sayHello( CommandInterpreter ci) throws Exception { logger.warn ( "logger.class=" +logger.getClass ( ) .getName ( ) ) ; logger.warn ( "This is a debug log from commons log!" ) ; System .out .println ( "Hello, world!" ) ; } }
META-INF/MANIFEST.MF
… Service-Component: OSGI -INF/console.xml … Import-Package: org.apache.log4j, org.apache.commons.logging, … …
OSGI -INF/console.xml
<?xml version ="1.0" encoding ="UTF-8" ?> <component name ="SiteProviderConsoleActivator" > <implementation class ="gsb.service.console.SitesProviderConsole" /> <service> <provide interface ="org.eclipse. osgi .fram ework.console.CommandProvider" /> </service> </component>