1、摘要
前述文档我向大家展示了Equinox OSGi环境及其搭建配置。从本文开始,我们将详细讨论Bundle的开发及OSGi应用构建。
2、OSGi相关概念
在正式进入Bundle的设计与开发之前,我们先来熟悉一下OSGi框架中的一些概念。用户在设计Bundle时必须要深入理解这些实体概念。
实体概念 | 实体概念说明 |
Bundle
|
- 安装到OSGi框架中的一个Bundle组件
|
Bundle Context
|
- 在OSGi框架运行环境中,一个Bundle的运行上下文。如果一个Bundle定义了一个BundleActivator接口的实现类并在Bundle 清单【Bundle-Activator】中指明了该Activator,则OSGi框架启动和停止该Bundle时会通过该 BundleActivator传入该Bundle的运行上下文
|
• Bundle Activator
|
- 一个org.osgi.framework.BundleActivator接口的实现类,OSGi框架在启动和停止该Bundle时会调用该类的start和stop方法.
|
• Bundle Event
|
- 描述一个Bundle生命周期操作的事件。当一个Bundle的生命周期状态出现变更时(如Bundle启动),所有的Bundle生命周期事件会被同步发的到所有的生命周期事件监听者.
|
• Framework Event•
|
- 描述OSGi框架状态变更的事件。所有注册的OSGi框架事件监听都会接收到该事件.
|
Bundle Listener
|
- 监听所有Bundle事件(安装,启动,停止,卸载等)的BundleListener接口实现.
|
• Synchronous Bundle Listener
|
- Bundle事件监听,该监听器监听同步发生的Bundle事件.
|
• Framework Listener
|
- 监听所有OSGi框架事件的监听器,该监听器实现FrameworkListener接口.
|
• Bundle Exception
|
- 当OSGi框架操作失败是抛出的异常.
|
• System Bundle
|
- OSGi核心框架功能的实现,该实现被看作是一个特殊的Bundle,其ID永远为0.
|
• Service
|
- 注册到服务注册表中的一个Java对象。该对象实现一个或多个接口,通过这些接口以及绑定到接口的属性列表,该对象提供接口定义的服务,该对象可以通过接口名称及属性被其他Bundle发现并使用.
|
• Service Registry
|
- 服务注册表,维护所有Bundle注册的服务.
|
• Service Reference
|
- 服务的引用。该引用只用于访问所引用的服务的属性而不是真正的服务对象。实际的服务对象必须通过Bundle的Bundle上下文获取.
|
• Service Registration
|
- 当一个服务被注册后,OSGi框架返回该对象;该对象可以用于更新服务属性及注销注册的服务.
|
• Service Listener
|
- 服务监听器,用于监听所有的服务事件(如服务的注册,变更等).
|
• Service Event
|
- 服务事件,用于描述一个对象服务的注册,修改或注销.
|
• Filter
|
- 过滤器,用于根据属性进行过滤.
|
• Start Level Service
|
- 启动等级服务,该服务用于控制Bundle的启动和停止顺序.
|
3、Bundle描述清单
每 一个Bundle都具有一组描述自身信息的元数据清单,该清单以名称-值对的头信息形式描述Bundle的各个方面,如Bundle的名称(Bundle -Name:acme),Bundle的版本(Bundle-Version:1.1)等,该清单存处于Bundle内部的META- INF/MANIFEST.MF文件中。OSGi定义了一系列的标准清单列表如下:
名称 | 说明 |
Bundle-ActivationPolicy: lazy | 该信息指明OSGi框架在启动时如何激活该Bundle。 |
Bundle-Activator: com.acme.fw.Activator | 该头信息指明OSGi框架在启动和停止该Bundle时调用的BundleActivator接口的类实现。 |
Bundle-Category: osgi, test, nursery | 该头信息指明Bundle的分类,多种分类以逗号分隔。 |
Bundle-Classpath: /jar/http.jar,. | Bundle的内部类路径。 |
Bundle-ContactAddress: 2400 Oswego Road, Austin, TX 74563 | 该Bundle实现厂商的联系地址 |
Bundle-Copyright: OSGi (c) 2002 | 该Bundle的版权信息 |
Bundle-Description: Network Firewall | 该Bundle的简短的描述信息 |
Bundle-DocURL: http:/www.acme.com/Firewall/doc | 通过URL指明该Bundle的文档信息所在的位置 |
Bundle-UpdateLocation: http://www.acme.com/Firewall/bundle.jar | Bundle的更新地址 |
Bundle-Vendor: OSGi Alliance | Bundle的实现厂商 |
Bundle-Version: 1.1 | Bundle的版本号 |
DynamicImport-Package: com.acme.plugin.* | 该Bundle动态引用的其他Bundle发布的包,多个包以逗号分隔。 |
Export-Package: org.osgi.util.tracker;version=1.3 | 该头信息可以将Bundle内部的类包发布出去 |
Fragment-Host: org.eclipse.swt; bundle-version="[3.0.0,4.0.0)" | 如果该Bundle是Fragment类型的,则使用该头信息描述该Bundle的Host Bundle。 |
Import-Package: org.osgi.util.tracker,org.osgi.service.io;version=1.4 | 该Bundle引用的其他Bundle发布的包,多个引用以逗号分隔。 |
Require-Bundle: com.acme.chess | 该Bundle依赖的其他Bundle |
说明:关于Bundle元数据清单的详细信息请参考OSGi R4.1核心规范。
4、Bundle的概念
在OSGi环境中,Bundle可以认为存在两个层面的含义,分别指静态的Bundle和运行时的Bundle。
4.1、静态的Bundle
理 论上静态的Bundle可以由任何形式存在,在Equinox OSGi实现中,静态的Bundle由一组资源文件构成,表现为文件系统目录或打包的JAR文件。如下图所示,用户为Bundle资源创建一个文件目录, 在此目录下添加META-INF目录并生成MANIFEST.MF文件。
在MANIFEST.MF文件中,我们可以定义Bundle的元数据头信息。如Bundle的名称(Bundle-Name),Bundle的唯一标识 (Bundle-SymbolicName),Bundle所依赖的其他Bundle构件(Required-Bundle)等上表所列的头信息。用户也 可以定义自己的头信息,如Acme-ID:1213等,用户自己定义的头信息,OSGi框架在解析元数据清单时会被忽略,用户需要自己解析处理。
用 户可以向Bundle目录中添加该Bundle的资源,如在该Bundle中定义类包(com.acme.*)以及这些类所引用的其他的类资源(xml- api.jar,junit.jar);用户也可以将此Bundle中的类包打包成JAR文件,如runtime.jar。
一个 Bundle构件并不是必须要发布Java类资源。如果一个Bundle构件并没有发布任何Java类资源,则该Bundle可以看作是一个资源 Bundle,该Bundle中的资源可以供其他Bundle使用。举例来说,在以OSGi实现JBI(JSR-208)ESB系统时,含有Java类资 源定义Bundle可以实现JBI框架及内部的服务引擎(SE)和绑定组件(BC);不含有Java类定义的Bundle构件可以用来描述为SE和BC定 义的配置即SU(Service Unit)或SA(Service Assembly)。
每一个Bundle构件都有自己的类空间,对于Bundle内部自己的类空间,可以使用元数据头Bundle-Classpath来描述,如Bundle-Classpath:.,runtime.jar,lib/xml-api.jar,lib/junit.jar
从上述讲述可以看出,只要准备好必要的资源及定义好Bundle元数据清单(MANIFEST.MF)文件就可以完成Bundle的构建。那么,这些Bundle构件在实际系统中会起到什么作用呢?
首 先让我们回顾一下通常的应用开发模式:简单说来,在获取用户需求之后,我们分析用户需求确定系统的功能,为系统的功能抽象对象模型,用Java类描述对象 模型实现这些系统功能。在这样的系统中,我们的系统实现代码是紧密结合在一起的,当系统的某项需求发生变更时,我们必须重新定义相关的类,并重新编译整个 系统。如果系统实现过程中,模块化程度定义的比较好,只需要重新编译和发布某一模块。即使如此,维护这样的系统也是非常复杂的,特别是随着系统功能的不断 扩充,系统变得越来越庞大,维护的代价也越来越高。
Bundle构件的开发模式在解决上述问题时带给我们的好处是显然的。我们仍然需要从系 统需求分析入手,确定系统的功能,划分系统的功能模块,对于每一个功能模块,我们构造面向对象的模型并使用Java类实现这些模型。但是,在实现过程中, 我们的视角发生了一定程度的转变。对于每一个功能模块,我们需要清晰的定义该模块向外部系统或内部系统其他模块发布什么样操作接口;然后,我们只需要将这 些接口定义及操作这些接口的功能实现的入口通过Bundle的发布(Export-Package)机制公布出去,同时屏蔽这些接口操作的内部实现;其他 模块只需引用(Import-Package)这些接口定义和功能实现入口就可以使用此模块的操作从而与该模块交互。当一个功能模块的需求发生变化时,如 果接口操作不变,则仅改变内部实现而不会对整个系统产生任何影响,如果接口操作发生改变,我们也可以发布该模块的高版本实现,不同版本可以共处于同一个运 行环境中。通过这种发布、引用、屏蔽和版本控制机制,我们可以实现高度松耦合的系统,系统实现一定程度上可以无限制的扩展。
4.2 动态的Bundle
OSGi 框架启动后会加载Bundle的静态资源,构建相应的运行时Bundle实例。OSGi框架为每一个运行时Bundle分配一个Bundle上下文 (BundleContext),每个Bundle都可以使用该上下文获取OSGi框架提供的各种信息和功能,如框架内部发布的事件,其他Bundle发 布的服务等。Bundle上下文只有当一个Bundle在元数据清单(MANIFEST.MF)中定义了Bundle-Activator头信息, OSGi框架启动和停止该Bundle时才通过Bundle-Activator头信息指定的实现了BundleActivator接口的类传入该 Bundle上下文。Bundle-Activator定义的BundleActivator举例如下:
public class MyBundleActivator implements org.osgi.framework.BundleActivator {
public void start(BundleContext context) throws Exception {
}
public void stop(BundleContext context) throws Exception {
}
}
如果用户构建的Bundle需要获取OSGi的上下文,则用户必须为该Bundle定义BundleActivator的实现,并在Bundle-Activator中指明。通常,Bundle可以分为三类:
5、构建Bundle的步骤
在熟悉了Bundle的上述含义后,我们可以根据Bundle的功能采用不同的开发步骤。
6、Bundle构建举例
在此示例中,我们定义一个简单的应用系统,该系统监控某个指定的目录,当目录下存在符合设定的文件过滤规则的文件时,获取这些文件的内容并以事件的形式发布出去。该应用系统在运行过程中需要记录运行日志,当系统运行出现异常时便于维护人员确定异常的原因。
在确定上述需求后,我们通过分析可以得出,除了核心的业务功能之外,我们需要为系统开发日志记录功能及事件发布功能。为了举例清晰,我们定义以下Bundle:
6.1 日志服务Bundle
首先,我们需要定义日志记录服务的ILogService接口:
package com.example.log;
public interface ILogService {
void debug(String message);
void debug(String message, Throwable t);
void info(String message);
void info(String message, Throwable t);
void error(String message);
void error(String message, Throwable t);
}
然后,我们定义ILogService接口的实现,该实现将日志输出到控制台:
package com.example.log.internal;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.text.DateFormat;
import java.util.Date;
import com.example.log.ILogService;
public class LogServieImpl implements ILogService {
public void debug(String message) {
log("[DEBUG]", message);
}
public void debug(String message, Throwable t) {
log("[DEBUG]", message, t);
}
public void error(String message) {
log("[ERROR]", message);
}
public void error(String message, Throwable t) {
log("[ERROR]", message, t);
}
public void info(String message) {
log("[INFO]", message);
}
public void info(String message, Throwable t) {
log("[INFO]", message, t);
}
private void log(String level, String message) {
log(level, message, null);
}
private void log(String level, String message, Throwable t) {
StringBuffer logEntry = new StringBuffer();
logEntry.append(DateFormat.getDateTimeInstance().format(new Date()));
logEntry.append(" ");
logEntry.append(level);
logEntry.append(" ");
logEntry.append(message);
logEntry.append(" ");
if (t != null) {
logEntry.append(getStackTraceAsString(t));
}
if(t==null)
System.out.println(logEntry);
else
System.err.println(logEntry);
}
private String getStackTraceAsString(Throwable t) {
String retString = null;
if (t == null) {
return retString;
}
try {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(outStream);
if (t.getStackTrace() != null) {
t.printStackTrace(printStream);
retString = outStream.toString();
printStream.close();
} else {
return t.getMessage();
}
outStream.close();
} catch (Exception e) {
}
return retString;
}
}
在 定义了日志记录服务接口及实现后,我们需要将日志记录服务发布出去供其他Bundle引用。为此,我们需要定义BundleActivator接口的实 现,获取BundleContext向OSGi环境中注册该日志服务。BundleActivator接口的实现类如下:
package com.example.log.internal;
import java.util.Hashtable;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import com.example.log.ILogService;
public class Activator implements BundleActivator {
public void start(BundleContext context) throws Exception {
//生成日志记录服务的实例
LogServieImpl logImpl = new LogServieImpl();
//设定日志记录服务的属性
Hashtable
//注册日志记录服务
context.registerService(ILogService.class.getName(),
logImpl, properties);
}
public void stop(BundleContext context) throws Exception {
}
}
在 完成日志记录服务的所有类定义后,我们需要为该Bundle编写元数据清单。在该Bundle中,我们只需要向其他Bundle发布日志服务接口包即可, 其他Bundle不必了解日志记录服务是如何实现的,因此,我们在Export-Package头信息中只输出:com.example.log包。该 Bundle的元数据清单如下:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Log Plug-in
Bundle-SymbolicName: com.example.log
Bundle-Version: 1.0.0
Bundle-Activator: com.example.log.internal.Activator
Bundle-Vendor: EXAMPLE
Import-Package: org.osgi.framework;version="1.3.0"
Export-Package: com.example.log
至此,日志记录服务Bundle已经开发完成。
6.2 事件管理服务
对于一个事件管理服务来说,用户使用该服务必须能够定义自己的事件,定义事件的监听处理。在本示例中,事件定义我们直接使用java.util.EventObject表示,用户可以继承此类定义自己的事件。
首先,我们定义事件监听处理IEventHandler接口:
package com.example.event;
import java.util.EventObject;
public interface IEventHandler {
void handlEvent(EventObject event);
}
然后,我们定义事件发布管理IEventManager接口:
package com.example.event;
import java.util.EventObject;
public interface IEventManager {
void fireEvent(EventObject event);
public void registEventHandler(IEventHandler handler);
}
现在我们来定义事件发布管理接口的实现,与日志记录服务相似,我们不需要将该实现暴露给其他Bundle,因此我们使用internal类包:
package com.example.event.internal;
import java.util.ArrayList;
import java.util.EventObject;
import com.example.event.IEventHandler;
import com.example.event.IEventManager;
public class EventManagerImpl implements IEventManager {
public ArrayList handlerList = new ArrayList (
7);
public void fireEvent(EventObject event) {
for (IEventHandler handler : handlerList) {
handler.handlEvent(event);
}
}
public void registEventHandler(IEventHandler handler) {
handlerList.add(handler);
}
}
现在,我们定义该Bundle的BundleActivator实现,注册事件管理服务:
package com.example.event.internal;
import java.util.Hashtable;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import com.example.event.IEventManager;
public class Activator implements BundleActivator {
public void start(BundleContext context) throws Exception {
// 生成事件管理服务实例
EventManagerImpl em = new EventManagerImpl();
// 设定事件管理服务的属性
Hashtable
// 注册事件管理服务
context.registerService(IEventManager.class.getName(), em, properties);
}
public void stop(BundleContext context) throws Exception {
}
}
最后,我们定义事件服务Bundle的元数据清单:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Event Plug-in
Bundle-SymbolicName: com.example.event
Bundle-Version: 1.0.0
Bundle-Activator: com.example.event.internal.Activator
Bundle-Vendor: EXAMPLE
Import-Package: org.osgi.framework;version="1.3.0"
Export-Package: com.example.event
至此,事件管理服务开发完毕。
6.3 文件目录监控Bundle
在 此示例中,该Bundle启动时获取日志记录服务和事件管理服务,查找系统属性"file.monitor.dir"和 "file.monitor.filter",确定监控目录及监控规则,并开始监控。当监控目录下存在符合过滤条件的文件时,读取该文件的内容发布 FileMonitorEvent事件。
我们首先定义FileMonintorEvent类,该类继承java.util.EventObject类,用户可以从该类中获取监控到的文件。该类定义如下:
package com.example.file.monitor;
import java.io.File;
import java.util.EventObject;
public class FileMonitorEvent extends EventObject {
private File m_file;
public FileMonitorEvent(File file) {
super(file);
m_file = file;
}
public File getFile() {
return m_file;
}
}
下面,我们定义FileMonitorService类,该类使用ILogService和IEventManager服务,开启一个新的线程监控指定的目录。如果用户未设定file.monitor.dir属性,默认监控目录为C:/osgi_test;如果用户未设定file.monitor.filter属性,默认监控文件名为数字,后缀为txt的文件,如1212.txt。该类的实现如下:
package com.example.file.monitor.internal;
import java.io.File;
import java.io.FilenameFilter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.example.event.IEventManager;
import com.example.file.monitor.FileMonitorEvent;
import com.example.log.ILogService;
public class FileMonitorService {
private ILogService log;
private IEventManager em;
private String dir;
private String filter;
private File monitorDir;
protected boolean doMonitor = true;
public FileMonitorService(ILogService logService, IEventManager eventManager) {
log = logService;
em = eventManager;
}
public void start() {
String m_dir = System.getProperty("file.monitor.dir");
if (m_dir != null && m_dir.length() > 0) {
dir = m_dir;
} else {
dir = "c://osgi_test";
File f = new File(dir);
if (!f.exists()) {
if (f.mkdir())
log.info("Can not create the default monitor dir [c://osgi_test]");
} else
log.info("Property [file.monitor.dir] is not assigned, use the default value [c://osgi_test]");
}
String m_filter = System.getProperty("file.monitor.filter");
if (m_filter != null && m_filter.length() > 0) {
filter = m_filter;
} else {
filter = "//d+.txt";
}
log.info("File monitor filter rule is [" + filter + "]");
final Matcher matcher = Pattern.compile(filter).matcher("");
final FilenameFilter filenameFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return matcher.reset(name).matches();
}
};
monitorDir = new File(dir);
if (!monitorDir.exists()) {
log.info("The monitor dir [" + dir + "] is not exist!");
return;
}
new Thread("FileMonitor") {
public void run() {
while (doMonitor ) {
File[] files = monitorDir.listFiles(filenameFilter);
for (File file : files) {
log.debug("File monitor service find file ["
+ file.getName() + "]");
FileMonitorEvent event = new FileMonitorEvent(file);
em.fireEvent(event);
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//
}
}
}
}.start();
}
public void stop(){
doMonitor = false;
}
}
然后,我们定义该Bundle的Activator:
package com.example.file.monitor.internal;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import com.example.event.IEventManager;
import com.example.log.ILogService;
public class Activator implements BundleActivator {
private ServiceTracker logTracker;
private ServiceTracker emTracker;
private static ILogService log;
private static IEventManager em;
private FileMonitorService fms;
public void start(BundleContext context) throws Exception {
logTracker = new ServiceTracker(context, ILogService.class.getName(),
null);
logTracker.open();
log = (ILogService) logTracker.getService();
emTracker = new ServiceTracker(context, IEventManager.class.getName(),
null);
emTracker.open();
em = (IEventManager) emTracker.getService();
fms = new FileMonitorService(log, em);
fms.start();
}
public void stop(BundleContext context) throws Exception {
if (fms != null)
fms.stop();
fms = null;
if (logTracker != null)
logTracker.close();
logTracker = null;
log = null;
if (emTracker != null)
emTracker.close();
emTracker = null;
em = null;
}
public static ILogService getLogService() {
return log;
}
public static IEventManager getEventManager() {
return em;
}
}
最后,我们定义文件监控服务Bundle的元数据清单:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Monitor Plug-in
Bundle-SymbolicName: com.example.file.monitor
Bundle-Version: 1.0.0
Bundle-Activator: com.example.file.monitor.internal.Activator
Bundle-Vendor: EXAMPLE
Import-Package: com.example.event,
com.example.log,
org.osgi.framework;version="1.3.0",
org.osgi.util.tracker;version="1.3.3"
Export-Package: com.example.file.monitor
至此,文件监控目录服务完成实现。
6.4 文件监控事件处理Bundle
此Bundle监听文件目录监控服务Bundle发布的文件监控事件,并处理该事件,为了简化,我们将Bundle的Activator实现作为文件监控事件的处理器,类定义如下:
package com.exammple.file.monitor.handler;
import java.util.EventObject;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;
import com.example.event.IEventHandler;
import com.example.event.IEventManager;
import com.example.file.monitor.FileMonitorEvent;
import com.example.log.ILogService;
public class Activator implements BundleActivator, IEventHandler {
private ServiceTracker logTracker;
private ServiceTracker emTracker;
private static ILogService log;
private static IEventManager em;
public void start(BundleContext context) throws Exception {
logTracker = new ServiceTracker(context, ILogService.class.getName(),
null);
logTracker.open();
log = (ILogService) logTracker.getService();
emTracker = new ServiceTracker(context, IEventManager.class.getName(),
null);
emTracker.open();
em = (IEventManager) emTracker.getService();
em.registEventHandler(this);
}
public void stop(BundleContext context) throws Exception {
if (logTracker != null)
logTracker.close();
logTracker = null;
log = null;
if (emTracker != null)
emTracker.close();
emTracker = null;
em = null;
}
public void handlEvent(EventObject event) {
if (event instanceof FileMonitorEvent) {
FileMonitorEvent fm = (FileMonitorEvent) event;
if (log != null)
log.info("Receive File Monitor Event, File Name is ["
+ fm.getFile().getName() + "]");
}
}
}
该Bundle的元数据清单如下:
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Handler Plug-in
Bundle-SymbolicName: com.example.file.monitor.handler
Bundle-Version: 1.0.0
Bundle-Activator: com.exammple.file.monitor.handler.Activator
Bundle-Vendor: EXAMMPLE
Import-Package: com.example.event,
com.example.log,
org.osgi.framework;version="1.3.0",
org.osgi.util.tracker;version="1.3.3"
Require-Bundle: com.example.file.monitor
至此,文件监控事件处理Bundle定义完成。
6.5小结
上述示例以最简单的方式实现,仅用于展示OSGi Bundle在创建高度松散耦合的应用系统时的优越性,关于实现的细节及细微控制,在此我们并没有详细涉及。
上述示例在Eclipse中的项目实现如下图所示。点击此处下载。
用户可以在Eclipse的OSGi开发环境中直接运行上述示例,也可以打包发布到独立的OSGi运行环境。关于OSGi运行环境参考前述文档。