OSGi简易教程
开篇我先解释一下两个容易搞混的术语:
- OSGi: The Open Services Gateway Initiative(开放服务网关协议) ,是用来模块化开发和部署Java应用程序和库(library)的一种架构,已经成为Java模块化事实上的标准了。详细解释请参考OSGi简介
- WSGI: Web Server Gateway Interface(Web服务器网关接口) ,具体的来说,WSGI是一个Python专用规范,定义了Web服务器如何与Python应用程序进行交互,使得使用Python写的Web应用程序可以和Web服务器对接起来。最新版本在Python的PEP-3333定义的。详细解释请参考WSGI简介
所以,弄清楚之后才发现只有一个字母的差别,但是这两个东东一毛钱的关系都没有,^_^
跟Java的Servlet协议和EJB协议类似,OSGi协议定义了两件事:一组OSGi容器必须实现的服务,以及容器和你的应用程序之间的接口标准。 也许你会搞不懂为什么我们已经有了Servelt容器来构建Web应用程序,有了EJB容器来构建企业级事务应用程序,那我们还要这个OSGi容器干嘛? 简单来讲就是OSGi容器是特地用来开发非常复杂的大型Java应用程序的,你需要将系统分解成很多个模块。
使用Equinox构建一个小应用
OSGi容器的开源实现现在主要有三种:Knopflerfish, Equinox 和 Apache Felix。
接下来我通过例子来演示如何使用Eclipse OSGi容器Equinox来构建一个helloworld应用。同时会介绍到OSGi中的ServiceFactory和ServiceTracker类。
在OSGi里面,各个模块都是以bundle的形式存在。每个bundle包含各自的java类和其他资源,并可以为其他的bundle提供服务。
创建bundle
打开eclipse,通过下面的步骤来创建一个Hello World的bundle
- 菜单选择File –> New –> Project,打开创建工程对话框。
- 选择
Plug-in Project
点下一步 - 输入如下的内容
- Project Name: com.javaworld.sample.HelloWorld
- Target Platform: OSGi framework –> Standard
- 其他默认点下一步进入Plug-in Context对话框,还是默认点下一步
- 这一步Template对话框里面选择Hello OSGi Bundle,然后完成
Activator.java
打开Activator.java这个类,应该类似下面这样:
1 | package com.javaworld.sample.helloworld; |
MANIFEST.MF
这个文件比较核心,它定义了你怎样去部署这个bundle。应该会类似下面这样。注:#开头的是我自己加的注释
1 | # 这个是MF版本 |
现在一切准备就绪了,你可以直接运行这个bundle。点击”Run Configuration”,新加一个OSGi Framework,把需要的bundle勾上,Run
注意,请在Target Platform里面把下面这些依赖添加进去
运行之后会在控制台打印hello world,那么说明执行成功了。
OSGi控制台
OSGi控制台是OSGi容器的命令行接口,它可以让你启动、停止、安装、更新和卸载bundle。 在Eclipse下面的Open Console标签点击”Host OSGi Console”
下面是常见的几个命令:
- ss 显示所有已经安装了的bundle列表,包括bundle ID、简短名、状态
- start
启动一个bundle - stop
停止一个bundle - update
使用一个新的jar来更新bundle - install
在OSGi容器中安装一个新的bundle - uninstall
从OSGi容器中卸载一个bundle - exit 退出osgi环境
- refresh
刷新对应id的Bundle - ls 显示所有services 如果没有发布service会显示找不到该命令
- ls –c
显示该bundle中所有发布service的详细信息,对与unsatisfied的component查错很有用
这些都是标准的OSGi命令,所以你可以适用于任何OSGi容器
依赖管理
OSGi协议允许你将应用切分为多个子模块,然后各自管理自己的依赖,默认情况下bundle里的东西对外是不可见的。要想让别的bundle可以调用到内部服务需要导出相应的包,同时其他想要使用的bundle还要导入此包。
导出一个包
我们再创建一个com.javaworld.sample.HelloService
的bundle,然后将其导出。这个bundle构建过程跟前面步骤一样,这里不再重复。
然后创建好后我们在HelloService这个bundle里面新建一个com.javaworld.sample.service.HelloService.java
接口
1 | public interface HelloService { |
再创建一个实现了这个接口的类com.javaworld.sample.service.impl.HelloServiceImpl.java
1 | public class HelloServiceImpl implements HelloService{ |
打开HelloService中的MANIFEST.MF
文件,添加Export-Package选项
1 | Export-Package: com.javaworld.sample.service |
导入一个包
在HelloWorld bundle里面,打开它的MANIFEST.MF
文件,添加Import-Package
1 | Import-Package: com.javaworld.sample.service, |
打开com.javaworld.sample.helloworld.Activator.java
源文件,现在你是可以访问HelloServiceImpl类了,
可编译通过但是直接调用会出错。原因是OSGi特有的类加载机制
OSGi服务
OSGi很适合用来开发面向服务的应用程序,因为它会把接口和实现分开,调用者不需要知道任何服务实现细节, 而服务提供者可以任意修改服务实现而不用告知被调用者,只要保证接口不变即可。
导出服务
在服务bundle里面新建一个com.javaworld.sample.service.impl.HelloServiceActivator.java
1 | public class HelloServiceActivator implements BundleActivator { |
通过使用BundleContext.registerService()
来导出服务,三个参数含义
- 第一个是服务的名字
- 第二个是服务的具体实现
- 第三个是服务的属性,是一个
Dictionary
对象
然后你要更改服务bundle的MANIFEST.MF
文件,指定HelloServiceActivator
为它的activator类
1 | Bundle-Activator: com.javaworld.sample.service.impl.HelloServiceActivator |
导入服务
这一步我们修改HelloWorld的程序,开始导入我们刚刚创建的服务并实际使用它。修改Activator
类
1 | public class Activator implements BundleActivator { |
BundleContext.getServiceReference()
方法返回一个ServiceReference
对象,
然后你可以调用BundleContext.getService()
传入这个对象后获取真正的服务对象了。
再次运行这个HelloWorld看看,打印出Inside HelloServiceImple.sayHello()
,说明已经成功调用了。
创建一个服务工厂
上一节我们在服务bundle里面创建了一个HelloServiceImpl
对象,任何其他bundle导入的时候都会返回这个同样的对象。要是我们想对每个bundle返回不同对象要在呢么做呢?
解决方法是创建一个实现了ServiceFactory
接口的类,然后注册这个工厂类的对象,而不是实际的服务对象。
1 | public class HelloServiceFactory implements ServiceFactory{ |
修改HelloServiceActivator.java
中的start()
方法
1 | public class HelloServiceActivator implements BundleActivator { |
再次运行也是同样的结果,不过这次效果是每个bundle请求会获取yield新的service对象了。
跟踪服务
有时候你需要跟踪某个服务什么时候被注册,被卸载之类的。这时候你可以使用ServiceTracker
类,我们在HelloWorld里面新建一个类
1 | public class HelloServiceTracker extends ServiceTracker { |
同时我们要修改 MANIFEST.MF
文件,导入包org.osgi.util.tracker
1 | Import-Package: com.javaworld.sample.service, |
然后修改Activator.java
,让它使用这个HelloServiceTracker
来获取真正的Service类
1 | public class Activator implements BundleActivator { |
再次执行时,你会发现当你启动、停止这个HelloService bundle的时候,相应的跟踪器里面的方法也会执行。
总结
我们通过一个小的例子演示了一个使用OSGi框架开发模块化程序的最基础的东西,你已经知道怎样导入/导出包和服务了。如果想更深入学习,可以去OSGi中文社区 ,那里有很多好的资源。