Hello OSGi - starts with Felix

Hello OSGi - starts with Felix

Hello world 系列的OSGi部分。

本文分为三部分,第一部分(I)给出了一个最简单的bundle示例;第二部分(II)用三个bundle分别作为接口、实现和消费三种角色,来示例一下 Declarative Services;第三部分(III)给出了本文的一些参考资料(链接)。之前看过别人写的hello,觉得太hello了,一般只到本文的第一部分就结束了,为了让兄弟们在hello阶段了解的更多一点,这里特地加了第二部分。

注意本文为了简单不涉及unit test。

I

先整个maven工程出来。
执行命令

mvn archetype:generate -DgroupId=osteching.osgi.steps.hello -DartifactId=osgi.steps.hello

"choose a number"的时候就用默认的15,要最简单的工程。version随便整,喜欢什么都成,我这里用0.0.1。可以输入想要的package,我这里就用osteching.osgi.steps.hello了。OK,maven工程搞定,再顺手生成Eclipse工程吧,这年头,有IDE不用用 Emacs/Vi的人纯粹没事找事。先进入刚才生成的maven工程目录(这里是osgi.steps.hello),执行命令 mvn eclipse:eclipse 。在Eclipse中用import,导入工程。

修改pom,要用Felix嘛,添加Felix的dependency:

<dependency>
    <groupId>org.apache.felix</groupId>
    <artifactId>org.osgi.core</artifactId>
    <version>1.2.0</version>
</dependency>

这部分我们只要个最简单的bundle,所以其实直接jar 就行了。但是为了说明Felix确实加载了这个bundle,我们写一个Activator,这个Activator在Felix加载它的时候,打印Hello world!出来。下面是代码示例:

public class Activator implements BundleActivator {
    @Override
    public void start(BundleContext context) throws Exception {
        System.out.println("->-- bundle starts ---");
    }
    @Override
    public void stop(BundleContext context) throws Exception {
        System.out.println("->-- bundle stops ---");
    }
}

在bundle被加载的时候(start bundleId)start将会被调用,stop bundleId 的时候stop方法会被调用。

可以用bnd来帮助管理bundle的MANIFEST.MF中的各个配置项。因为我这里用了maven来管理工程,所以这里使用Felix提供的 Maven Bnd的plugin来打包Bundle。这个plugin的使用说明可以在第三部分(III)中找到链接。在打包之前我们先看看OSGi的bundle 需要什么。OSGi需要一个在{bundle.jar}/META-INF/MANIFEST.MF的文件来对bundle做些配置。我们这里的 bundle很简单,只需要引入osgi的package,给出bundle的name,说明bundle的version,指定Activator就可以了。所以下面四条就能满足基本的要求:

Bundle-Name: osgi.steps.hello
Bundle-Version: 0.0.1
Import-Package: org.osgi.framework;version="1.4"
Bundle-Activator: osteching.osgi.steps.hello.Activator

下面是对应上面四项的配置:

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <version>2.0.0</version>
    <extensions>true</extensions>
    <configuration>
        <manifestLocation>META-INF</manifestLocation>
        <instructions>
            <Bundle-Activator>osteching.osgi.steps.hello.Activator</Bundle-Activator>
            <Bundle-Version>${pom.version}</Bundle-Version>
            <Import-Package>org.osgi.framework</Import-Package>
        </instructions>
    </configuration>
</plugin>

注意这里没有配置Bundle-Name,这个工具会自动的把pom里的name作为bundle的name。

运行下面命令生成OSGi需要的MANIFEST.MF文件:

mvn org.apache.felix:maven-bundle-plugin:manifest

这里的manifestLocation会在工程目录下生成一个META-INF目录,MANIFEST.MF文件就在这个目录下。如果好奇心比较强,打开MANIFEST.MF文件看看就会发现,不光我们设置的这三项配置,还有其他一些杂七杂八的配置项,暂且不用管他们,对我们这个简单的hello来说,把其他配置项都删除也不会有任何影响。

执行下面命令完成打包:

mvn org.apache.felix:maven-bundle-plugin:bundle

如果一切顺利,在target目录下的jar文件,就是一个可用的bundle了。

好了,现在来试试打包好的bundle。为了方便,我这里把这个bundle文件放到了Felix安装目录(解压缩就完成了安装)下。下面是测试过程,这个测试包括了几个常用的命令:

F:\open_source\OSGi\felix-1.6.0>java  -jar bin\felix.jar

Welcome to Felix.
=================

-> help
bundlelevel <level> <id> ... | <id> - set or get bundle start level.
cd [<base-URL>]                     - change or display base URL.
exports <id> ...                    - list exported packages.
headers [<id> ...]                  - display bundle header properties.
help                                - display impl commands.
imports <id> ...                    - list imported packages.
install <URL> [<URL> ...]           - install bundle(s).
obr help                            - OSGi bundle repository.
ps [-l | -s | -u]                   - list installed bundles.
refresh [<id> ...]                  - refresh packages.
requirers <id> ...                  - list requiring bundles.
requires <id> ...                   - list required bundles.
resolve [<id> ...]                  - attempt to resolve the specified bundles.
scr help                            - Declarative Services Runtime
services [-u] [-a] [<id> ...]       - list registered or used services.
shutdown                            - shutdown framework.
start [-t] <id> [<id> <URL> ...]    - start bundle(s).
startlevel [<level>]                - get or set framework start level.
stop [-t] <id> [<id> ...]           - stop bundle(s).
uninstall <id> [<id> ...]           - uninstall bundle(s).
update <id> [<URL>]                 - update bundle.
version                             - display version of framework.
-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.6.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.2.0)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.2.0)
[   3] [Active     ] [    1] Apache Felix Bundle Repository (1.4.0)
[   4] [Active     ] [    1] Apache Felix Declarative Services (1.0.8)
-> install file:osgi.steps.hello-0.0.1.jar
Bundle ID: 5
-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.6.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.2.0)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.2.0)
[   3] [Active     ] [    1] Apache Felix Bundle Repository (1.4.0)
[   4] [Active     ] [    1] Apache Felix Declarative Services (1.0.8)
[   5] [Installed  ] [    1] osgi.steps.hello (0.0.1)
-> start 5
->-- bundle starts ---
-> stop 5
->-- bundle stops ---
-> shutdown
->
F:\open_source\OSGi\felix-1.6.0>

State 一列是bundle所处的状态,可以在install、start、stop等各个命令执行之后通过ps 命令来查看bundle对应的状态。注意,和Felix不同的是,Equinox通过ss 命令来查看bundle的状态。通过上面一段日志,可以清楚的看到,bundle的start和stop会调用BundleActivator对应的 start和stop方法。

到这里这个最简单的HelloWorld就完成了。在完成你自己hello bundle的过程中,一定有很多“为什么”这里都没用解答。对这些进一步的内容,希望后续的B文能帮助你。或者你可以给我留言,说不定就你的话题我会出一篇B文了-_-。

II

OSGi R4的规范中Declarative Services是很吸引人的地方之一。在这部分我们来一起搞搞Declarative Services的HelloWorld。向开头所说的那样,我们要把接口、实现和消费三者分开来做。在OSGi中,也就是要分开三个bundle。这里建三个工程,分别是osgi.steps.hello对应接口,osgi.steps.hello.impl对应实现,osgi.steps.hello.client对应消费者。下面一个个说明这三个bundle工程的特别之处。

对接口bundle来说,它需要暴露出服务接口,在OSGi规范中,通过MANIFEST.MF中的Export-Package配置项来暴露。需要在pom.xml的maven-bundle-plugin部分添加一个相关的配置,我这里的配置如下:

<Export-Package>osteching.osgi.steps.hello.biz;version=0.0.1</Export-Package>

实现的bundle需要引入上面暴露的包,所以要在maven-bundle-plugin部分的Import-Package 中添加osteching.osgi.steps.hello.biz,注意这里直接省略的version,嗯,就一个版本的接口,无所谓了:) 注意因为我们用DS的方式对外提供服务,所以还需要加上Service-Component 的配置,如下:

<configuration>
    ...
    <instructions>
        ...
        <Import-Package>osteching.osgi.steps.hello.biz</Import-Package>
        <Service-Component>OSGI-INF/components.xml</Service-Component>
    </instructions>
</configuration>

会在MANIFEST.MF中添加对应的Service-Component项,用来给出DS配置文件的位置。这里的文件路径对用到src/main/resources下面,maven嘛,走习惯路线:)

下面来看看components.xml 的内容:

<?xml version="1.0" encoding="UTF-8"?>
<component name="HelloImpl">
    <implementation class="osteching.osgi.steps.hello.impl.HelloImpl"/>
    <service>
        <provide interface="osteching.osgi.steps.hello.biz.HelloI"/>
    </service>
</component>

一眼就看明白了吧,服务的接口、实现类,全在那了。至于具体实现则并没有什么特殊之处。

最后还剩一个client,为了简化,所以这里client简单的用Activator来代替。Client要使用HelloI接口,所以同样也要 import 服务接口所在的包。Client要通过OSGi 的DS 把服务注入,所以也需要指定components 的配置。这样下面三个配置项就是必须的了:

    <Bundle-Activator>osteching.osgi.steps.hello.client.Activator</Bundle-Activator>
    <Import-Package>org.osgi.framework;osteching.osgi.steps.hello.biz</Import-Package>
    <Service-Component>OSGI-INF/components.xml</Service-Component>

Client的components配置和impl稍微有些区别:

<?xml version="1.0" encoding="UTF-8"?>
<component name="HelloClient">
    <implementation class="osteching.osgi.steps.hello.client.Activator" />
    <reference name="HelloImpl" interface="osteching.osgi.steps.hello.biz.HelloI" bind="setHello"
        unbind="removeHello" policy="dynamic" cardinality="1..1" />
</component>

可以看见有个reference元素指明了需要的服务。在Activator 中分别有setHello和removeHello对应到服务的bind和unbind。稍微解释下另两个属性的含义。policy属性说明了服务发生变化是的作用范围,dynamic说明只管bind和unbind,默认的static方式则要整个bundle更新。cardinality指明多少个服务会被注册到component中,有四中可选项分别是0..1、1..1、0..n、1..n。

Client要注意的地方是,组建实现类必须加上两个方法来绑定引用,嗯,很像getter/setter。然后就是在start方法中使用注入的hello服务了,这里简单的打印一行符号。

public class Activator implements BundleActivator {
    private HelloI hello = null;
    public void setHello(HelloI h) {
        hello = h;
    }
    public void removeHello(HelloI h) {
        if (hello == h) {
            hello = null;
        }
    }
    @Override
    public void start(BundleContext context) throws Exception {
        // get service directly
        HelloI hello = (HelloI)context.getService(context.getServiceReference(HelloI.class.getName()));
        System.out.println("->-- " + hello.sayHello("World!"));
        // use injected service
        System.out.println(hello.sayHello("+++++++++++++++"));
    }
    @Override
    public void stop(BundleContext context) throws Exception {
        // do what you are interested in
    }
}

生成MANIFEST.MF文件和打包bundle没有特殊之处,这里掠过。接下来就该测试这三个bundle了。和第一部分的测试方法类似,把 bundle扔到Felix的安装目录。运行Felix的OSGi环境,install、start bundle之后会发现,我们期待的+++++并没有出现,甚至还拿到个NullPointerException。这是为什么呢?这是因为Felix默认的下载是没有提供对Declarative Service 的支持,我们使用Felix的SCR来增加对Declarative Service的支持。在第三部分(III)有下载SCR的链接。把下载的SCR bundle放到Felix安装目录下的bundle文件夹里,然后修改conf/config.properties,在 felix.auto.start.1项里加上scr,这里用的是org.apache.felix.scr-1.0.8.jar。把这个加进osgi启动项,配置OSGI-INF/components.xml才会起作用。下面是这个配置:

felix.auto.start.1= \
    file:bundle/org.apache.felix.shell-1.2.0.jar \
    file:bundle/org.apache.felix.shell.tui-1.2.0.jar \
    file:bundle/org.apache.felix.bundlerepository-1.4.0.jar \
    file:bundle/org.apache.felix.scr-1.0.8.jar

OK,再次进行测试,应该就会看见可爱的++了。

III

下面是一些很有用的参考资料
0/ http://www.osgi.org
1/ http://felix.apache.org
2/ 可以在http://felix.apache.org/site/downloads.cgi 下载Felix提供的各种bundle。
3/ 可以在http://repo1.maven.org/maven2/org/apache/felix/ 查看Felix在Maven中可能用到的各种Dependency。
4/ Bnd使用说明,中文 http://www.aqute.biz/Code/BndCn;英文 http://www.aqute.biz/Code/Bnd
5/ Felix提供的Bnd的Maven插件使用说明 http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html
6/ 在Felix中使用Declarative Services的说明 http://felix.apache.org/site/apache-felix-service-component-runtime.html
7/ SCR提供的Maven plugin使用说明 http://felix.apache.org/site/apache-felix-maven-scr-plugin.html

AttachmentSize
Hello OSGi.7z4.48 KB

Comments

难道一见的好文章

看完楼主的帖子,我的心情竟是久久不能平复,正如老子所云:大音希声,大象希形。我现在终于明白我缺乏的是什么了,正是楼主那种对真理的执着追求和楼主那种对理想的艰苦实践所产生的厚重感。面对楼主的帖子,我震惊得几乎不能动弹了,楼主那种裂纸欲出的大手笔,竟使我忍不住一次次的翻开楼主的帖子,每看一次,赞赏之情就激长数分,我总在想,是否有神灵活在它灵秀的外表下,以至能使人三月不知肉味,使人有余音穿梁,三日不绝的感受。楼主,你写得实在是太好了

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
3 + 10 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.