1.新建一个项目HelloworldBundle
1.启动Eclipse,依次点 File --> New --> Project。
2.选择Plug-in Project
,next。
3.输入Project Name项目名称,比如HelloWorldBundle
,Target Platform
(目标平台)里的an OSGI framework
,选择standard
。
4.next,保持默认。
5.然后选择Hello OSGI Bundle作为模版
6. 之后点击next,最后点击finish
1.1 查看自动生成的文件
Eclipse会飞快的为你创建Hello world
bundle的模版代码。主要包含三个文件:Activator.java
和MANIFEST.MF
,还有一个是build.properties
文件。
1.1.1 Activator.java解释
Activator.java的代码如下所示:
package helloworldbundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class Activator implements BundleActivator {
public void start(BundleContext context) throws Exception {
System.out.println("Hello World!!");
}
public void stop(BundleContext context) throws Exception {
System.out.println("Goodbye World!!");
}
}
如果你的bundle在启动和关闭的时候需要被通知,你可以考虑实现BundleActivator
接口。以下是定义Activator的一些注意点:
-
你的Activator类需要一个公有的无参数构造函数。OSGI框架会通过类反射的方式来实例化一个Activator类。
-
容器
启动
bundle过程中负责调用你的Activator类的start
方法。bundle可以在此初始化资源比如说初始化数据库连接。start方法需要一个参数,BundleContext对象。这个对象允许bundles以取得OSGI容器相关信息的方式和框架交互。如果某一个bundle有异常抛出,容器将对该bundle标记为stopped并不将其纳入service列表。我们在start方法中加入了打印语句,这样在启动时,可以验证;后面的章节中,有启动的步骤
-
容器
关闭
的时候会调用你的Activator类方法stop()
,你可以利用这个机会做一些清理的操作。我们在stop方法中加入了打印语句,这样在关闭时,可以验证
1.1.2 MANIFEST.MF解释
这个文件是你的bundle的部署描述文件。格式和Jar里的MANIFEST.MF是一样的。包含的不少名值对,就像如下:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloWorldBundle
Bundle-SymbolicName: HelloWorldBundle
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: helloworldbundle.Activator
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.osgi.framework;version="1.3.0"
分别来看下:
-
Bundle-ManifestVersion
数值为2意味着本bundle支持OSGI规范第四版;如果是1那就是支持OSGI规范第三版。
-
Bundle-Name
给bundle定义一个短名,方便人员阅读
-
Bundle-SymbolicName
给bundle定义一个唯一的非局部名。方便分辨。
-
Bundle-Activator
声明在start和stop事件发生时会被通知的监听类的名字。
-
Import-Package
定义bundle的导入包。
1.2 执行bundle
-
1.点击
Run --> Run Configuration
-
2.在左边的
OSGI Framework
选项里右键 new ,创建一个新的OSGI Run Configuration -
3.名字随便取好了,也可以用默认值。
-
4.你会注意到中间的窗口里Workspace项目里有一子项
HelloWorldBundle
,将其勾选上,并配置依赖的5个Target Platform:
-
5.点击Run按钮。在控制台你应该可以看见点东西了。那是叫做OSGI控制台的东东。与子相伴,还有一个"Hello world"。
提示信息可以参考:OSGI HelloWorld调试过程中碰到的问题
一切ok:
至此,我们正确的运行了程序,并且在启动时出发了start方法,因为输出了“Hello World!!”
如果没有找到控制台或丢失焦点,可以参考下图:
1.2.1常用的OSGi命令
下面是几个经常使用的OSGi命令,您可以使用这些命令与OSGi容器进行交互:
ss: 该命令显示所有已安装的Bundles及它们的状态,它将显示Bundle ID,Bundle的简短名称及Bundle状态;
start< bundleid>: 该命令将启动一个Bundle;
stop< bundleid>: 该命令将停止一个Bundle;
update< bundleid>: 该命令使用新的JAR文件更新一个Bundle;
install< bundleid>: 该命令将一个新的Bundle安装到OSGi容器;
uninstall< bundleid>: 从OSGi容器中卸载一个已安装的Bundle。
2. 依赖管理
OSGI规范允许你把你的应用分解成多个模块然后管理各个模块间的依赖关系。
这需要通过bundle scope来完成。默认情况下,一个bundle内的class对其他bundle来说是不可见的。那么,如果要让一个bundle访问另一个bundle里的class要怎么做?解决的方案就是从源bundle
导出包,然后在目标bundle
里导入。
接下来我们演示跨bundle访问,我们将新建一个bundle工程,名称为HelloServiceBundle,在里面声明一个接口并提供一个实现类,通过第一个bundle项目HelloworldBundle来访问这个接口和实现类。
2.1 导出包
2.1.1 新建bundle项目HelloServiceBundle
创建步骤和前面一样,只是项目名称为HelloServiceBundle。
2.1.2 创建接口和实现类
新建接口类,HelloService.java :
package helloservicebundle;
public interface HelloService {
public String sayHello();
}
新建实现HelloService 接口的实现类HelloServiceImpl .java:
package helloservicebundle.impl;
import helloservicebundle.HelloService;
public class HelloServiceImpl implements HelloService {
public String sayHello() {
System.out.println("Inside HelloServiceImple.sayHello()");
return "Say Hello";
}
}
2.1.3 导出bundle项目helloservicebundle包
打开MANIFEST.MF
,选择Runtime
标签项,在Exported Packages
选项栏,点击Add
并且选择这个包,效果图:
然后MANIFEST.MF的代码应该如下:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloServiceBundle
Bundle-SymbolicName: HelloServiceBundle
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: helloservicebundle.Activator
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.osgi.framework;version="1.3.0"
`Export-Package: helloservicebundle //新增的 `
你可以看到,MANIFEST.MF
文件和刚才的HelloWorld
项目的那份很类似。唯一的区别就是这个多了Export-Pachelloservicebundlekage
这个标记,对应的值就是我们刚才选择的helloservicebundle包
Export-Package标记告诉OSGI容器在helloservicebundle包
内的classes可以被外部访问。
注意,我们仅仅暴露了HelloService接口,而不是直接暴露HelloServiceImpl实现。
2.2 导入包
接下来我们要更新原来的HelloWorldBundle并导入helloservice包
,步骤如下
2.2.1 在配置文件中增加导入包的配置
进入HelloWorldBundle,打开MANIFEST.MF
,进入Dependencies
标签页,在Imported Packages
里添加helloservicebundle。MANIFEST.MF
文件应该如下所示:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: HelloWorldBundle
Bundle-SymbolicName: HelloWorldBundle
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: helloworldbundle.Activator
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: `helloservicebundle,`
org.osgi.framework;version="1.3.0"
Import-package
标记的值增加一条“helloservice”,并通过逗号分隔。
从上面的代码可以看出,Import-Package属性头的值是一个由逗号分隔的字符串,这是您想导入包的列表。在HelloWorldBundle示例代码中,我们引入了两个包,即com.javaworld.sample.service
和org.osgi.framework
。
org.osgi.framework包
中包含有OSGi框架类,比如,在HelloWorldBundle中的Activator.java中用到的BundleContext和BundleActivator类都属于这个包。
2.2.2 引入接口或类
打开HelloWorldBundle项目下的Activator.java
文件,这时候你会发现可以使用HelloService
这个接口了。但还是不能使用HelloServiceImpl
实现类:
package helloworldbundle;
import helloservicebundle.HelloService;
import helloservicebundle.impl.HelloServiceImpl;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class Activator implements BundleActivator {
public void start(BundleContext context) throws Exception {
HelloService s;
HelloServiceImpl si;
System.out.println("Hello World!!");
}
public void stop(BundleContext context) throws Exception {
System.out.println("Goodbye World!!");
}
}
但是接口编译正常,但是实现类编译错误:
Eclipse会告诉你:Access restriction(立入禁止):
Access restriction: The type 'HelloServiceImpl' is not API (restriction on required project 'HelloService')
2.2.3 现象解释
为什么OSGI容器可以做到让jar包中的一些classes可见而另一些又不可见呢。
我们知道类加载器是需要路径的,比如双亲委派模型下的几个类加载器,均有自己的加载路径范围,超过这个范围,加载器就会加载失败。同样的,OSGI容器自定义了java class loader来有选择的加载类。OSGI容器为每一个bundle都创建了不同的class loader,一个bundle可以访问的classes路径包括:
- Boot classpath:所有的java基础类。
比如我们调用的System.out.println语句不会有编译错误,但是又没有配置Import-Package
- Framework classpath:OSGI框架级别的classloader加载的类
- Bundle classpath:Bundle本身引用的关系紧密的JAR的路径
- Imported packages:就是在
MANIFEST.MF
里声明的导入包,一旦声明,在bundle内就可见了。
比如我们导入了helloservicebundle包,就可以访问该包下的HelloService.class
bundle级别的可见域允许你可以随时放心的更改HelloServiceImpl实现类而不需要去担心依赖关系会被破坏。
3. OSGi服务
OSGi架构非常适合我们实现面向服务的应用(SOA)。它可以让Bundles导出服务,而其它的Bundles可以在不必了解源Bundles任何信息的情况下消费这些导出的服务。由于OSGi具有隐藏真实的服务实现类的能力,所以它为面向服务的应用提供了良好的类与接口的组合。
在OSGi框架中,源Bundle在OSGi容器中注册POJO对象,该对象不必实现任何接口,也不用继承任何超类,但它可以注册在一个或多个接口下,并对外提供服务。目标Bundle可以向OSGi容器请求注册在某一接口下的服务,一旦它发现该服务,目标Bundle就会将该服务绑定到这个接口,并能调用该接口中的方法。下面我们举个例子,以便我们能更好理解与OSGi相关的这些概念。
这个例子基本是扩展HelloService接口,我们目前无法访问其实现类HelloServiceImpl,那么通过如下的操作,我们就能实现调用HelloServiceImpl类。
3.1 导出services
在本小节中,我们将更新HelloServiceBundle
,以便它能把HelloServiceImpl
类的对象导出为服务,具体步骤如下:
3.1.1 修改HelloServiceBundle
中的MANIFEST.MF
文件
让它导入org.osgi.framework
包(译者注,这一步我们已经符合条件,是自动生成的);
3.1.2 新建HelloServiceActivator类
helloservicebundle.impl.HelloServiceActivator.java
其源代码如清单7所示;
源代码清单7. HelloServiceActivator.java
package helloservicebundle.impl;
import helloservicebundle.HelloService;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
public class HelloServiceActivator implements BundleActivator {
ServiceRegistration helloServiceRegistration;
@Override
public void start(BundleContext context) throws Exception {
HelloService helloService = new HelloServiceImpl(); //创建一个实例
helloServiceRegistration = context.registerService(HelloService.class.getName(),
helloService, null); //把实例注入到上下文中,并定义key为HelloService,将来通过key访问这个实例
}
@Override
public void stop(BundleContext context) throws Exception {
helloServiceRegistration.unregister();
}
}
OK,我们就是用BundleContext
的registerService
方法注册service的。这个方法需要三个参数。
请注意,在源Bundle中,我们应使用BundleContext.registerService()
方法导出服务,这个方法带三个参数:
-
a) 该方法第一个参数为您要注册的服务的
接口名称
。
如果您想把您的服务注册到多个接口下,您需要新建一个String数组存放这些接口名,然后把这个数组作为第一个参数传给registerService()方法。在示例代码中,我们想把我们的服务导出到HelloServer接口名下; -
b) 第二个参数是您要注册的服务的
实际Java对象
。
在示例代码中,我们导出HelloServiceImpl类的对象,并将其作为服务; -
c) 第三个参数为服务的属性,它是一个Dictionary对象。
如果多个Bundle导出服务的接口名相同,目标Bundle就可以使用这些属性对源Bundle进行过滤,找到它感兴趣的服务。
3.1.3 修改Bundle-Activator属性
修改HelloServiceBundle中的MANIFEST.MF
文件,将Bundle-Activator
属性头的值改为helloservicebundle.impl.HelloServiceActivator
。
现在HelloService Bundle就可以导出HelloServiceImpl对象了。当OSGi容器启动HelloServiceBundle时,它会将控制权交给HelloServiceActivator.java类,HelloServiceActivator
将HelloServiceImpl
对象注册为服务。下面,我们开始创建该服务的消费者。
我们知道Bundle-Activator属性指定的类(假设为类A),会在该Bundle启动或停止时会触发类A的start()或stop()方法.。我们正是利用这个原理,在start()内注册接口服务的。
3.2 导入服务
在本小节中,我们将修改上面开发的HelloWorldBundle
,以便让它成为HelloService服务的消费者
。您主要需要修改HelloWorldBundle中的Activator.java代码,修改后的代码如源代码清单8所示:
源代码清单8. HelloWorld Bundle中的Activator.java
package helloworldbundle;
import helloservicebundle.HelloService;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
public class Activator implements BundleActivator {
ServiceReference helloServiceReference;
public void start(BundleContext context) throws Exception {
System.out.println("Hello World!!");
helloServiceReference = context.getServiceReference(HelloService.class.getName()); //通过接口名称作为key,获取其实例
HelloService helloService = (HelloService) context.getService(helloServiceReference);
System.out.println(helloService.sayHello());
}
public void stop(BundleContext context) throws Exception {
System.out.println("Goodbye World!!");
context.ungetService(helloServiceReference);
}
}
在上面的代码中,BundleContext.getServiceReference()
方法将为注册在HelloService接口下的服务返回一个ServiceReference
对象。如果存在多个HelloService服务,该方法会返回排行最高的服务(服务的排行是通过Constants.SERVICE_RANKING属性指定的)。您一旦获得ServiceReference对象,您就可以调用其BundleContext.getService()方法获取真实的服务对象。
3.3 验证
您可以参照运行Bundle的方法运行上面的示例应用,请点击“Run…”菜单,并确保HelloWorld
和HelloService
这两个Bundle被选中。当您启动HelloServiceBundle时,您会在控制台上看到“InsideHelloServiceImple.sayHello()”,这个消息是由HelloServiceImpl.sayHello()方法打印出来的。
Hello World!!
Inside HelloServiceImple.sayHello()
Say Hello
osgi> ss
"Framework is launched."
id State Bundle
0 ACTIVE org.eclipse.osgi_3.10.2.v20150203-1939
2 ACTIVE org.apache.felix.gogo.command_0.10.0.v201209301215
3 ACTIVE org.eclipse.equinox.console_1.1.0.v20140131-1639
4 ACTIVE org.apache.felix.gogo.runtime_0.10.0.v201209301036
5 ACTIVE org.apache.felix.gogo.shell_0.10.0.v201212101605
7 ACTIVE HelloWorldBundle_1.0.0.qualifier
8 ACTIVE HelloServiceBundle_1.0.0.qualifier
osgi>
3.3.1 验证过程中出现的问题
参见:《OSGI HelloWorld启动报org.osgi.framework.BundleException》
参考OSGI