用 Eclipse 构建轻量级的 OSGi 应用程序

OSGi 一直是在 Java™ 领域及诸多其他领域中构建动态模块系统的实际行业标准。本文通过一系列相关示例展示在 Eclipse 中开发 OSGi 应用程序的过程、场景、解决方案和实践。深入阅读本文以系统了解 OSGi 框架与核心服务。

Yuan Tao Sun, 软件性能分析师, IBM

Photo of Sun, Yuan TaoSun Yuan Tao 是 IBM 中国软件开发实验室上海 Lotus 性能团队的一名软件性能分析师,他主要从事包括 Lotus Connections 和 Lotus Live iNotes 在内的 Lotus 社交产品的性能评测和改进。在加入该性能团队之前,他是一名软件开发人员,擅长软件设计模式、Eclipse RCP、Java 2 Platform 和 Enterprise Edition 应用程序开发。



2012 年 1 月 16 日

简介

构建模块化系统的需求非常多。在 Java 领域及诸多其他领域中,开放服务网关协议 (Open Services Gateway initiative, OSGi) 一直都被视为一种成熟的模块化系统框架,它一般包括桌面应用程序、Web 应用程序、移动应用程序,以及中间件。OSGi 提供了一种底层的基础设施,可用来开发具有模块化、动态性的面向服务应用程序。其大多数特性和功能均通过 OSGi 规范和实现中的服务所定义和提供的。在了解几个核心 OSGi 服务的概念和用法后,我们就能够利用这些服务以及 Eclipse IDE 构建轻量级模块化应用程序来满足复杂的需求。


一个样例应用程序

该样例应用程序是一个数据收集器,可从不同的数据源检索数据并将这些数据解析成统一的预定义的数据格式以供进一步处理。有多个系统具有不同的数据格式定义和检索方法。一般情况下,应用程序生产者或拥有者常常都期待第三方供应商基于此应用程序发布的 API 为特定的数据源实现业务逻辑。理想情况下,数据收集器客户端应集成第三方代码,并且可以直接运行而无需对现有代码、配置或部署结构进行任何更改。这是构建模块化系统的一个典型要求,稍后我们将介绍如何使用 OSGi 核心服务来实现这一点。

为了充分发挥 OSGi 的强大功能,对应用程序的架构进行细致的设计非常必要,虽然设计并不是很复杂。讨论 OSGi 应用程序设计原则的文章有很多,我建议您参阅这些文章(请参阅 参考资料)。

图 1 显示了该样例数据收集器应用程序的基础架构。该应用程序由三种 bundle 组成:数据收集器框架 bundle、文本解析器 bundle 和收集器 bundle。

图 1. 样例数据收集器应用程序的基础架构
此图显示了该数据收集器框架 Bundle 如何具备了面向文本解析、数据收集器和配置向导的 API,并可与各种收集器和文本解析 bundle 进行交互

框架 bundle 充当整个应用程序的核心组件。它可以是一个 bundle(提供了面向其他 bundle 的收集器和向导 UI API),也可以是一个客户端(使用来自其他功能性 bundle 的服务)。通过将 API 和客户端 bundle 并入到一个 bundle,第三方数据收集器开发人员就可以将最少数量的 bundle jar 导入到其 IDE 来实现 API,然后再在 Eclipse 中运行并测试整个应用程序。收集器 bundle 提供了数据收集服务和向导页服务。另一方面,收集器 bundle 还能充当一个客户端,调用由框架 bundle 提供的文本解析器 API,而单独的解析器 bundle 将为之提供服务。

所有这些 bundle 都会通过 OSGi 核心服务松散地进行相互藕合和交互。因此,除了框架 bundle 之外的所有 bundle 都能很容易地被添加、删除、暂停或升级,即便是应用程序部署之后,仍能如此。这对于自动化的软件交付过程非常重要。

接下来,我们将展示实现并部署一款小型而又完整的 OSGi 应用程序的过程和实践。


定义 OSGi 项目布局

我们需要在 Eclipse 中为每个 bundle 创建一个新的插件项目。如下假设您已经具备了在 Eclipse 中创建 OSGi 的经验和知识。此外,还要确保下载了 Eclipse IDE 的最新发布版以及单独的 Equinox SDK。建议使用 Eclipse 3.4 或更高版本。

正如 图 1 所描述的,需要至少三个 bundle 项目,框架 bundle、文本解析器 bundle 和数据收集器 bundle。定义项目的布局(比如包和文件夹等级结构设计)非常重要。让我们以 图 2 所示的这个框架 bundle 为例。

图 2. 样例项目布局
屏幕截图显示了一个典型项目的目录结构

创建 OSGI-INF 文件夹来存储该 bundle 中所有的 OSGi 组件定义文件(组件概念将稍后介绍)。根据功能类别创建各个包。我们需要在早期阶段计划其他 bundle 可见的包以避免 bundle 之间的严重耦合。在该样例框架 bundle 中,会导出下列两个包。

  • dw.sample.dc.api:包含了要实现的所有 API。
  • dw.sample.dc.consts:包含 bundle 之间共享的常数和枚举,比如事件参数。

使用 OSGi Declarative Services 注入服务

模块化面向服务 是 OSGi 规范中的两个关键概念。公开实现类虽受 OSGi 框架支持,但是并不推荐使用。相反,从编程角度来看,bundle 应能够通过发布和消费服务类实例 来进行相互交互。OSGi 提供了两个方法来实现此目标。

  • 通过 OSGi 服务层提供的 BundleContext 在服务注册表中注册和检索服务。
  • 利用 OSGi R4 规范内的 Declarative Services。

Declarative Services (DS) 声明了组件模型的概念。一个组件就是由组件定义文件定义的一个 Java 对象,用来提供服务以及检索所引用的服务。

组件的生命周期是由此 OSGi 容器管理。而且,所引用的服务实例可动态注入到组件。DS 可处理服务查找、以及根据预定义的策略将服务与组件进行绑定或解除绑定。因此,开发人员能够使用简洁的代码生成一个高度动态的系统。

创建第一个服务组件

让我们从一个简单的用例开始:数据收集器框架 bundle 定义了一个文本解析器 API,文本解析器 bundle 提供默认的解析器服务。

在框架 bundle 中,服务 API 在所导入的 dw.sample.dc.api 包中进行定义。参见 清单 1

清单 1. 文本解析器 API
public interface ITextParser {
public String parseText(String input);
}

在文本解析器 bundle 项目中, 首先,必须将 dw.sample.dc.api 包导入到此 manifest 来确保这个接口类可由当前的 bundle 类加载程序进行加载。其次,创建一个类来实现上述接口。

最后,也是最为重要的一步是,将实现类定义为一个组件,这就需要创建一个组件定义文件并在 bundle manifest 中注册它。为了简化该过程,我们使用 Eclipse 提供的向导(老版本可能没有):

  1. 右键单击此解析器 bundle 中的 OSGI-INF 文件夹并选择 New>Component Definition
  2. 将刚刚创建的这个实现类文件指定为组件类,再指定定义文件名称和组件名称,然后单击 Finish
  3. 在图形组件定义配置窗口中,切换到 Services 选项卡并将此框架 bundle 的服务接口类指定为这个组件所提供的服务。参见如下 图 3 所示。
图 3. 指定所提供的服务
这个屏幕快照显示了将所提供的服务配置为 dw.sample.dc.api.ITextParser

这时,向导会生成一个新的 textParserComponent.xml 并将这个组件注册到 bundle manifest,如 清单 2 所示。

清单 2. 新生成的组件配置
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0"
  name="dw.sample.textparser.defaultparser">
   <implementation class="dw.sample.textparser.service.DataCollectorDefaultTextParser"/>
   <service>
      <provide interface="dw.sample.dc.api.ITextParser"/>
   </service>
</scr:component>

MANIFEST.MF
...
Service-Component: OSGI-INF/textParserComponent.xml

现在,我们实现了一个简单而又完整的组件,当某客户端调用框架 bundle 中的 API 时,该组件将能在运行时提供服务。

接下来,我们将展示一个更复杂的组件并介绍如何使用其他组件的服务。

一个较高级的用例

让我们考虑这样一个场景:用户要能通过 GUI 与数据收集器应用程序进行交互,这就意味着用户要能够在一系列向导页的指导下输入所需数据。然后再启动收集的过程并观察此 UI 上的进程。显然,框架并不知道第三方数据收集器需要什么数据。为了满足这一需求,数据收集器 bundle 需要提供至少一个配置向导页。此外,还需要此框架在应用程序部署到用户环境后能够无缝和动态地集成和提供所有的第三方向导。听起来很酷,是不是?那么就让我们来看看如何利用 OSGi Declarative Services 的强大功能来实现这个目的(参见 图 4)。

图 4. 动态的向导顺序
此图显示了如何从向导群组 1 如何进入向导群组 2,后者又如何进入到向导 4。向导 3 已被灰色屏蔽。

与之前的用例类似,需要定义一个 API。对于本例而言,我们需要仔细设计接口以避免在确保此 API 能够满足来自数据收集器端的关键功能需求的同时增加向导系统的复杂度。因此,这个 API 的设计需要至少满足如下几点:

  • 不允许来自不同数据收集器的向导服务之间有任何的通信和依赖性,但不限制同一数据收集器的向导服务间的通信和依赖性。
  • 在用户步出数据收集器的最后一个向导页时保存用户此阶段的输入。

这是本例中最为重要的一步。一个结构良好的 API 设计在于能够提供清晰的界面而又同时能保持服务的高度内聚,而并不在乎实现中的逻辑和代码的复杂性。

SWT 和 JFace 被用作此应用程序向导及其他 GUI 组件的底层库。如果我们计划在这个两个库中跨多个包使用资源和类,只需将它们作为所需的 bundle 添加到框架 bundle 和数据收集器 bundle 的 manifest 文件。此外,还需要导入 org.osgi.service.component 包,因为我们稍后会调用这个包中的类。

框架 bundle 中所定义的这个向导 API 由两个类组成,如 清单 3 所示。

清单 3. 定义向导服务 API
public interface ICollectorWizardGenerator {
public List<CollectorWizardPage> getWizardSequence();
public int getWizardPositionIndex();
}

---
/**
 * Inherit the jface WizardPage by adding two customized methods
 */
public abstract class CollectorWizardPage extends WizardPage {
    …
/**
* Trigger the customized logic when the next button on the wizard is
 * pressed
 * 
 * _cnnew1@return whether to jump to next page.
 */
public abstract boolean nextPressedTrigger();

/**
* Save user input in the current wizard.
 */
public abstract void saveWizardInput();    
}

在数据收集器 bundle 中,实现此向导 API 并将实现 ICollectorWizardGenerator 接口的类定义为服务组件(参见 清单 4)。

清单 4. 向导服务组件
public class ProfileWizardGeneratorImpl implements ICollectorWizardGenerator {
    private int wizardPosition = 10;

    protected void activate(ComponentContext context) {
        try {
            wizardPosition = ((Integer) context.getProperties().get(
                    "wizardPosition")).intValue();
        } catch (NumberFormatException e) {
            wizardPosition = 10;
        }
    }
    public List<CollectorWizardPage> getWizardSequence() {
        List<CollectorWizardPage> wizardPageSeq = new ArrayList<CollectorWizardPage>();
        wizardPageSeq.add(new ProfileCollectorWizardPage1());
        wizardPageSeq.add(new ProfileCollectorWizardPage2());
        return wizardPageSeq;
    }
    public int getWizardPositionIndex() {
        return wizardPosition;
    }
}

---
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" 
  name="dw.sample.dc.profile.wizardgenerator">
   <implementation class="dw.sample.dc.profile.service.ProfileWizardGeneratorImpl"/>   
   <service>
      <provide interface="dw.sample.dc.api.ICollectorWizardGenerator"/>
   </service>
   <property name="wizardPosition" type="Integer" value="2"/>
</scr:component>

注意,此组件类中的 activate 方法被覆盖了。这个方法以及 deactivate 方法是在激活和去激活组件时由 OSGi 框架所调用的方法。将 ComponentContext 对象传递至 activate 方法以便我们能获得诸如 XML 文件中定义的组件属性的组件相关信息。在这里,数据收集器供应商可以定义向导的位置索引以便框架 bundle 可以相应地按排向导出现顺序。

至此,我们已经制作了两个组件作为服务提供者。

使用服务

在 Declarative Services 模型中,服务被动态地注入到组件中。下列 清单 5 中的代码展示了如何在 bundle 中引用和使用它们。

清单 5. 解析器服务加载器组件
public class TextParserLoader {
    private static TextParserLoader instance;
    ... ...
    private ITextParser parser;

    public static TextParserLoader getInstance() {
        return instance;
    }
    protected void activate(ComponentContext context) {
        try {
            locker.lock();
            instance = this;
        } finally {
            locker.unlock();
        }
    }
    public void setTextParser(ITextParser parser) {
        this.parser = parser;
    }
    public void unsetTextParser(ITextParser parser) {
        if (this.parser != parser) {
            return;
        }
        this.parser = null;
    }
    public ITextParser getTextParser() {
        return parser;
    }
}

---
<implementation class="dw.sample.dc.profile.service.TextParserLoader"/>
<reference bind="setTextParser" cardinality="1..1" 
  interface="dw.sample.dc.api.ITextParser" name="ITextParser" 
policy="dynamic" unbind="unsetTextParser"/>

在这里,我们在这个组件类中应用了 singleton 模式。此 bundle 中的其他非组件对象能获取和使用来自这个惟一的 TextParserLoader 实例的服务,同时又能摆脱掉特定于 OSGi 的代码。该加载器类的功能有些类似于传统 Java 应用程序中的 factory 类,但惊人之处是它不需要执行任何繁琐的类加载、反射和实例初始化的操作。

DS 支持此组件 XML 文件中定义的各种服务引用策略:

  • Policy:policy 属性具有两个值,即静态和动态。默认为静态策略,表明只要所引用的服务有任何变更,组件都将被重新激活。相反,动态策略则只调用预定义的绑定和去绑定方法,所以更受推荐。
  • Cardinality:这个属性具有四个可用值:1..1、1..n、0..1 和 0..n。考虑到同一个 API 可能会有多个服务可用,结尾的数字指明是否所有的服务或者只有其中的一个服务将注入到此组件中,而开始数字则指明是否强制可用服务来激活此组件。

基数 1..1 是默认值,我们从上面的示例中已经了解到了它是如何使用的。对于 0..n 的情况,它也可以与此 singleton 模型一起得到很好的处理,如 清单 6 所示。

清单 6. 注入多个服务
public class DataCollectorWizardSequence {
    ...
    private List<ICollectorWizardGenerator> wizardGeneratorSeq = 
	                            new ArrayList<ICollectorWizardGenerator>();
    ...
    public void addCollectorWizardGenerator(ICollectorWizardGenerator wizardGenerator) {
        wizardGeneratorSeq.add(wizardGenerator);        
    }
    public void removeCollectorWizardGenerator(ICollectorWizardGenerator wizardGenerator)
    {        
        wizardGeneratorSeq.remove(wizardGenerator);        
    }
    public List<CollectorWizardPage> getDataCollectorWizardSequence() {
        List<CollectorWizardPage> collectorWizardSequence = 
		                    new ArrayList<CollectorWizardPage>();
        try {
            locker.lock();
            /*
             * Arrange the wizard sequence according to position indexes 
	    * defined by each wizard services
	    */
            Collections.sort(wizardGeneratorSeq,
                    new CollectorWizardSequenceComparator());
            for (ICollectorWizardGenerator generator : wizardGeneratorSeq) {
                collectorWizardSequence.addAll(generator.getWizardSequence());
            }
            return collectorWizardSequence;
        }
        finally {
            locker.unlock();
        }
    }
}

---
<reference bind="addCollectorWizardGenerator" cardinality="0..n" 
       interface="dw.sample.dc.api.ICollectorWizardGenerator" 
	   name="ICollectorWizardGenerator" policy="dynamic" 
	   unbind="removeCollectorWizardGenerator"/>

在理解了如何利用引入到 Declarative Services 中的组件来发布和使用服务后,接下来我们将来看看如何有效地利用其他的 OSGi 核心服务。


用 Configuration Admin Service 管理 bundle 配置

Configuration Admin Service 为 OSGi 应用程序提供了一种统一的方式来管理本地或远端的配置数据,可以是服务级别,也可以是 bundle 级别。我们将以一个最为常见的用例开始,即本地存储、检索和更新 bundle 配置数据。

首先,检查 Eclipse plugins 文件夹中是否存在 org.eclipse.equinox.cm_<version>.jar,它是 OSGi Configuration Admin Service 的 Equinox 实现。如果没有的话,单独下载 Equinox SDK 并将其放至该文件夹中。然后,将 org.eclipse.equinox.cm 包导入到目标 bundle manifest。

要使用 Configuration Admin Service,首先,也是最重要的是必须实现并发布此 ManagedService API 作为目标 bundle 的服务。只要实现 updated 方法即可。不同的 ManagedServices 是由随服务注册的 SERVICE_PID 来区别的,因此对其配置进行更改时,就会触发相应的服务。为了管理 bundle 级别的配置,清单 7 中的 bundle 激活器将是一个能提供 ManagedService 的合适对象。

清单 7. 提供 ManagedService 的 bundle 激活器
public class Activator implements BundleActivator {
    private ServiceRegistration cmSvrReg;

    public void start(BundleContext context) throws Exception {
cmSvrReg = context.registerService(ManagedService.class.getName(),
                    this, initMgrServiceProp());
}
    public void stop(BundleContext context) throws Exception {
        cmSvrReg.unregister();
    }
    public void updated(Dictionary properties) throws ConfigurationException {
        if (cmSvrReg == null) {
            return;
        }
        if (properties == null) {
            cmSvrReg.setProperties(initMgrServiceProp());
        } else {
            cmSvrReg.setProperties(properties);
        }
    }
    public static Dictionary initMgrServiceProp() {
        Dictionary result = new Hashtable();
        // Suppose that the Activator class name is unique across bundles…
result.put(Constants.SERVICE_PID, Activator.class.getName());
        return result;
    }
}

接下来,如 清单 8 所示,将创建一个组件来加载由 OSGi 框架发布的 Configuration Admin Service。

清单 8. ConfigManager 组件
public class DCFrameworkConfigManager {
    private ConfigurationAdmin cm;
    ...
    protected void activate(ComponentContext context) {
        ...
        Configuration config = getFrameworkConfig();
try {
            writeLock.lock();
            if (config.getProperties() == null) {
                config.update(context.getProperties());
            }
        } finally {
            writeLock.unlock();
        }
    }
    public void setConfigAdmin(ConfigurationAdmin cm) {
        this.cm = cm;
    }
    public void unsetConfigAdmin(ConfigurationAdmin cm) {
        this.cm = null;
    }
    public Configuration getFrameworkConfig() throws IOException {
        try {
	readLock.lock();
            return cm.getConfiguration(Activator.class.getName());
        } finally {
            readLock.unlock();
        }
    }
    public void updateFrameworkConfig(Dictionary<String, String> properties)
            throws IOException {
        try {
            writeLock.lock();
            Configuration config = getFrameworkConfig();
            if (config != null) {
                config.update(properties);
            }
        } finally {
            writeLock.unlock();
        }
    }
}

---
<reference bind="setConfigAdmin" cardinality="1..1" 
    interface="org.osgi.service.cm.ConfigurationAdmin" name="ConfigurationAdmin" 
    policy="dynamic" unbind="unsetConfigAdmin"/>
<properties entry="OSGI-INF/initConfig.properties"/>

激活后,ConfigManager 组件会从一个属性文件中启动 bundle 配置并提供一些可用在此 bundle 的任何地方中读取和保存运行时配置和方法。它们受锁定器保护,可处理并发调用情况。


使用 Event Admin Service 处理事件

对于很多系统来说,非常有必要实现一种事件处理模型。OSGi 面向服务的框架可通过 Event Admin Service 提供这样一种实现机制。

为了正确使用 Event Admin Service,需要记住如下三点:

  • 事件发布器需要利用所引用的 EventAdmin 服务发送 Event 对象。
  • 事件订阅器通过实现 handleEvent 方法处理所接受的事件来提供 EventHandler 服务。
  • Event 对象的 event.topics 属性决定了哪个事件处理器可以接收此事件。

OSGi Event Admin Service 以一种更为动态的方式实现了传统的事件处理编程模型,而又能不向这个模型引入新的概念。与本文中介绍的其他 OSGi 服务类似,我们将通过组件对象检索此服务。DS 支持组件既可以是服务提供者又可以是服务消费者,这就使组件可以同时成为事件发布器和事件处理器。

考虑这样的要求:用户可以在应用程序 UI 上观察数据收集进程并能通过 UI 控件随时停止该进程。对于一个数据收集器 bundle 来说,从技术上讲,这意味着在应用程序框架 bundle 和数据收集器 bundle 这两端应具有双通道的事件发送和处理功能(参见如下的 清单 9)。

让我们仅仅以数据收集器 bundle 端为例。同样地,首先检查 Eclipse plugins 文件夹,如果其内没有 org.eclipse.equinox.event_<version>.jar ,就将它复制到此文件夹中,然后将此包导入到这个 bundle manifest。

清单 9. 事件发布器和处理器组件
public class ProfileDataCollectorImpl implements IDataCollector, EventHandler {
    public final static String EVENT_TOPIC =
                    "dw/sample/dc/event/collector/ProfileDataCollectorImpl/status";
    private EventAdmin eventAdmin;
    ...
    public int collectAndOutput() {        		
        while (...) {
            if (isCancelled()) {
                cancelProcess(false);
                return -1;
            }
            ...
            publishEvent(GUIMsg.STATUS_AND_PROGRESS.flag(), 
                "Collecting profile data...", 0, true);
            ...
        }
        ...
    }
    private void publishEvent(int status, String statusMsg, int progress,
        boolean async) {
        Dictionary<String, String> props = new Hashtable<String, String>();
        props.put(FrameworkEventConsts.PARAM_STATUS, String.valueOf(status));
        props.put(FrameworkEventConsts.PARAM_STATUS_MSG, statusMsg);
        props.put(FrameworkEventConsts.PARAM_PROGRESS, String.valueOf(progress));
        if (!async) {
            eventAdmin.sendEvent(new Event(EVENT_TOPIC, props));
        } else {
            eventAdmin.postEvent(new Event(EVENT_TOPIC, props));
        }
    }
    public void handleEvent(Event event) {
        String action = (String) event
                .getProperty(FrameworkEventConsts.PARAM_ACTION);
        if (FrameworkEventConsts.VAL_CANCEL.equalsIgnoreCase(action)) {
            cancelProcess(true);
        }
    }
    ...
}

---
<property name="event.topics" type="String" 
    value="dw/sample/dc/event/framework/UserCmdEventPublisher/uicmd"/>
<service>
   <provide interface="org.osgi.service.event.EventHandler"/>
   <provide interface="dw.sample.dc.api.IDataCollector"/>
</service>
<reference bind="setEventAdmin" cardinality="1..1" 
    interface="org.osgi.service.event.EventAdmin" name="EventAdmin" policy="dynamic" 
    unbind="unsetEventAdmin"/>

在本示例中,请注意 event.topics 属性值的格式。而且,我们还能看到 Event Admin Service 允许开发人员定义可通过 Event 对象传递的数据。


使用 Application Admin Service 指定启动模型

到目前为止,我们已介绍了开发 OSGi 应用程序的主要部分,现在,这个示例应用程序应该准备就绪可以运行了。如图 1 所示,一个原生的 OSGi 应用程序是由运行在 OSGi 容器顶端的 bundle 组成的。在默认的情况下,OSGi 控制台会在应用程序启动时出现。如果它是运行在服务器端,那表示没有什么问题。但是如果它作为一个客户端应用程序交付的话,那么就会出现此类问题:如何打包此应用程序以便它能够像一个现代应用程序一样执行,也就是如何以更为 '专业' 的方式启动而同时又保持其内部的模块化和动态性。

这里,我们引入了一个非常简单的方法,即基于 OSGi Application Admin Service 实现此 Equinox 应用程序模型。

第一步是将 org.eclipse.equinox.app bundle 作为 Require-bundle 添加到此应用程序框架 bundle 的 manifest 中。创建一个类来实现 IApplication 接口,如 清单 10 所示。

清单 10. 实现 IApplication 接口
public class GUIApplication implements IApplication {    
    public Object start(IApplicationContext context) {
        // Invoke the entry GUI object here
        ...
        return IApplication.EXIT_OK;        
    }
    public void stop() {
        // Add the logic when the application quits.
    }
}

其次,通过在 plugin.xml 中声明此扩展将应用程序框架 bundle 定义为一个 Eclipse 应用程序,如 清单 11 所示。

清单 11. plugin.xml
<plugin>
    <extension id="GUIApp"
         point="org.eclipse.core.runtime.applications">
      <application cardinality="1">
         <run
               class="dw.sample.dc.launcher.GUIApplication">
         </run>
      </application>
   </extension>      
</plugin>

此应用程序基数指定了此应用程序每次只能有一个实例。由于此 bundle 已经声明了一个扩展,必须在 Bundle-Symbolic manifest 头中追加这个 singleton 指令,如下所示:

Bundle-SymbolicName: dw.sample.dc;singleton:=true

接下来,创建一个新的配置以在 Eclipse IDE 中启动此 OSGi 应用程序(参见 图 5)。

图 5. 创建一个新的配置
屏幕截图显示了将参数添加至一个新配置

Bundles 选项卡,需要单击 Add Required Bundles 来将所有的强制依赖项作为平台 bundle 包括进来。在 Arguments 选项卡,需要添加两个新的参数来启用这个应用程序模型。请注意 -application 参数的值应该是 "bundle symbolicname"."extension id" 的格式。

如果我们能够运行这个应用程序并验证所有功能都能按预期的那样在 Eclipse 中正常工作,那么则表明我们已经完成了构建 OSGi 应用程序的开发工作。因此,我们就能够将每个 bundle 项目导出至一个 jar 文件中、对它们进行部署并在一个实际环境中运行所集成的测试,但是这部分内容已超出了本文所讨论的范围。


结束语

本文展示了使用 OSGi 核心服务在 Eclipse 中开发和启动一个模块化应用程序的详细步骤。本文的示例显示了,这个带有 DS 的面向服务组件模型 (Service-Oriented Component Model) 在帮助开发人员有效使用其他 OSGi 核心服务提高模块化应用程序的动态性方面发挥了重要的作用。

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Information Management
ArticleID=790130
ArticleTitle=用 Eclipse 构建轻量级的 OSGi 应用程序
publish-date=01162012