《OSGi与Equinox》第四章

 由  池建强 发布

第4章 你好,Toast

任何一个项目在建立初期都充满了诱惑。我们会设计一整套复杂的bundle,以此证明在编写代码之前我们的应用如何做到功能全面。但是这种做法不利于一个敏捷项目的演进。当项目结束时再来审视当初所写的代码,往往会发现那些代码早已面目全非。

所以我们并不会在一开始就讨论架构设计,而是仔细分析一种远程信息管理领域的简单场景。实际上,构建Toast应用的第一步甚至和OSGi没有一点儿关系。本章结论部分将设计bundle构建Toast。在后续章节中,我们将会为Toast增加OSGi以及远程信息管理领域的相关功能。

本章有如下几个目标:

  • 创建一些简单的类,用于实现远程信息管理领域中的某个简单场景;
  • 借助于PDE工具并基于一些原始代码,创建3个bundle工程;
  • 使用OSGi加载配置,运行我们所创建的3个bundle应用。

4.1 简单的场景

在远程信息管理领域,我们要处理的第一种情况是紧急情况通知。现在假定汽车配备了两个设备——安全气囊和GPS定位系统。一旦安全气囊弹出,紧急情况监视器会马上启动。监视器通过GPS定位系统查询汽车当前的位置,然后向现场外服务中心报告上述紧急情况。现在,我们要做的是在控制台上打印汽车当前的位置。

4.1.1 创建工程

首先,我们在编写代码之前需要创建一个工程,此次我们采取的做法有别于传统的OSGi程序开发。我们需要的仅仅是可用的功能,暂时无需担心 bundle以及其他OSGi相关事项。

当完成第一个非OSGi迭代版本的Toast并使其运行后,我们将重构并将其改造成为一个更具模块化的系统。

针对本书中涉及的所有实例,如果你不想按部就班按照实例操作,可以跳过这些操作步骤,直接使用示例管理器加载“Chapter 4.1 Hello, Toast”中的示例代码。

  • 首先需要创建一个普通的Java工程。在工作台中选择File>New>Project> ,展开Java,然后选择Java Project ,运行一个New Java Project向导,操作步骤如图4-1所示。

http://assets.osgi.com.cn/article/7289496/图片1.jpg

图4-1 新JAVA工程向导

  • 在项目名称选项中输入Toast。接受所有其他默认选项,点击Finish
  • 你可能会看到一个对话框,提示切换到Java视图。若同意,点击Yes

4.1.2 GPS

现在,我们需要创建两个设备——GPS和安全气囊。我们先来创建Gps类。Gps类提供查询汽车位置的API。当然,我们没有真正可以交互的GPS硬件,Gps类仅仅简单地返回一些硬编码的数值。

选择File > New > Class开启New Java Class向导,以此来创建Gps类(如图4-2所示)。输入Toat/src来表示源代码文件夹,输入org.equinoxosgi.toast为包名。输入Gps为类名。其他信息使用系统默认值,点击 Finish

Gps类创建完毕后,Java编辑器会自动将该类打开。

http://assets.osgi.com.cn/article/7289496/图片2.jpg

图4-2 新JAVA类向导

  • 将如下代码填入Gps类:

             Toast/Gps 
             public class Gps {
                public int getHeading() {
                return 90; // 90度 (东)
             } 
              public int getLatitude() {
                      return 3776999; // 37.76999 N
             } 
              public int getLongitude() {
               return -12244694; // 122.44694 W
             }
             public int getSpeed() {
               return 50; // 50千米/小时
             }
            }
    

4.1.3 Airbag和IAirbagListener

现在,我们来定义安全气囊类以及监听气囊打开事件的方式。

  • 选择File > New > Interface创建监听器接口,并将其放在Toast/src源码文件夹下的org.equinoxosgi.toast包中。安全气囊类拥有监听接口后,可以与监听其的对象之间保持相互独立。
  • 与上述操作步骤一样,只不过这次创建接口IAirbagListener,如下所示:

             Toast/AirbagListener
             public interface IAirbagListener {
             public void deployed();
             }
    

在同一文件夹及包中新建一个Airbag类,该类拥有监听器添加(addListener)功能和监听器移除(removeListener)功能,如下所示:

Toast/Airbag
public class Airbag {
private List listeners;
  public Airbag() {
   super();
   listeners = new ArrayList();
  }

  public synchronized void addListener(IAirbagListener listener) {
   listeners.add(listener);
  }

  public synchronized void deploy() {
   for (Iterator i = listeners.iterator(); i.hasNext();)
  ((IAirbagListener) i.next()).deployed();
  }

  public synchronized void removeListener(IAirbagListener listener) {
   listeners.remove(listener);
  }
}
  • 若要修复任何编译错误,可以对Aribag类的导入包重新进行组织,在Package Explorer中选择包文件,或者在Java编辑器中将包文件打开然后按Ctrl+Shift+O 键,或者在工具条菜单中选择Source > Organize Imports。阅读本书时,你会经常遇到这样的操作。

编译警告

创建Aribag类的过程中,你可能会注意到一些关于raw types的编译警告。针对List<E>ArrayList<E>以及Iterator<E>等泛型,如果在使用时没有指定具体类型,在默认情况下Eclipse编译器会对其进行警告提示。

  • 由于Toast使用的是Java1.4,并未用到泛型,所以我们大可无视这些编译警告。选择Window > Preferences打开首选项对话框,然后选择Java > Compiler > Errors/Warnings 。展开Generic types标签,然后将每个编译设置项修改为Ignore。点击OK 时,系统会提示你是否进行一次整体构建,此时选择Yes(如图4-3所示)。

http://assets.osgi.com.cn/article/7289496/图片3.jpg

图4-3 工作空间编译器首选项页面

4.1.4 EmergencyMonitor

Gps、Airbag类以及IAirbagListener接口都已经定义,接下来我们将定义EmergencyMonitor类,并完成其内部逻辑。
  • 创建EmergencyMonitor类,并实现IAirbagListener接口,代码如下所示:

    Toast/EmergencyMonitor 
    public class EmergencyMonitor implements IAirbagListener {     
        private Airbag airbag;     
        private Gps gps;      
        public void deployed() {
            System.out.println("Emergency occurred at  lat=" + gps.getLatitude() + " lon=" + gps.getLongitude() + " heading=" + gps.getHeading() + " speed=" + gps.getSpeed());    
        }
        public void setAirbag(Airbag value){      
            airbag = value; 
        }      
        public void setGps(Gps value) {
            gps = value;  
        }   
        public void shutdown() {
            airbag.removeListener(this);    
        }    
        public void startup() {     
            airbag.addListener(this);    
        } 
    }
    

    通过setGpssetAirbag方法,我们可以采用依赖注入的方式,设置独立于实例化的依赖方式。我们本可以通过构造函数完成这一切,但是如同我们将要在下一章看到的,将初始化与实例化逻辑分离是非常有用的。

同样,startupshutdown方法的分离,有效地将监控器的生命周期与EmergencyMonitor类的实例化进行了解耦。结果表明,该做法也同样非常有用,尤其是当Toast变得更具模块化和动态化时。

值得注意的是,监控器的业务逻辑基本都包含在deployed方法中。该方法满足监听器接口的要求,并且真正实现了紧急情况下的业务处理。所有其他的代码都是支持该部分代码的基础。

行为对称性

在程序设计时,将startup方法与shutdown方法设计成彼此对称是一种很好的做法;也就是说,无论startup行为如何,shutdown应该具备完全相反的行为,并可以进行撤销操作。

4.1.5 Main

现在,我们准备运行这个非OSGi的、比较简单的Toast应用,此时需要一个main方法并以此实例化上述三个类,同时将它们组织在一起,并强制气囊打开:

  • 定义Main类,如下所示:

             Toast/Main
             public class Main {
               public static void main(String[] args) {
                 System.out.println("Launching");
                  Gps gps = new Gps();
                 Airbag airbag = new Airbag();
                 EmergencyMonitor monitor = new EmergencyMonitor();
                 monitor.setGps(gps);
                  monitor.setAirbag(airbag);
                  monitor.startup();
                  airbag.deploy();
                   monitor.shutdown()  
                   System.out.println("Terminating");
              }
            }
    

4.1.6 运行

运行实例相对简单。

  • Package Explorer中选择Main类,然后在菜单项中选择Run As > Java Application 即可以启动Toast。控制台中会显示如下内容:

          Launching
           Emergency occurred at lat=3776999 lon=-12244694 heading=90 speed=50
          Terminating
    

4.1.7 检查点

此时的Toast非常简单,但是透过上述代码,我们已经发现一些在软件设计方面的架构模式。这些模式功能强大,尤其当系统变得日趋庞大和复杂时,仍能保持业务对象与业务逻辑相互独立,既增加了代码的内聚性又降低了程序的耦合度。

4.2 将Toast划分为Bundle

此时的Toast仅是一个单一的工程,与OSGi无关。其实这和真实的场景是一致的——很多应用最初都是一个整体,或附着一些小范围的、固有的模块化机制。对模块化的需求也随着需求及部署场景的变化而日趋增加。当然这正是OSGi大显身手的时候。

在本节,我们将把Toast转换为一组OSGi bundle。尽管如此,Toast还是非常简单,同时转换为OSGi的过程也非常容易,尤其是当你将更加复杂的应用移植到OSGi环境时,我们所采用的模式和方法还是非常有用的。

将一个应用进行模块化改造时,第一步是识别核心结构。这将帮助你理解依赖关系,并识别应用内部的交互,然后就可以定义模块。请看图4-4所示结构图。

http://assets.osgi.com.cn/article/7289496/图片4.jpg

图4-4 bundle依赖

图字翻译:

Emergency Monitor:紧急情况监视器

Airbag:安全气囊

需要注意的是,紧急情况监视器类同时依赖于GPS和安全气囊类,并且GPS和安全气囊彼此相互独立。识别这一点很有价值,我们可以将系统分解成三个相互独立的组成部分。更进一步来说,了解了依赖的关系层次可以避免循环依赖。

避免循环依赖

当我们在设计一个由bundle组成的OSGi系统时,需要尽量避免出现循环依赖的现象。虽然OSGi支持循环依赖,但是这样做会增加系统设计的复杂性,在系统构建和部署时会带来不必要的困难和迷惑。

识别完候选组件后,接下来要考虑的是,将这些组件划分为独立的bundle,还是将组件以包的方式放在一起?在很多时候,以上问题在部署中常常会遇到,例如:如果设计成没有GPS的安全气囊类或者没有安全气囊的GPS类,这样做是否合适?在这里,这样做是合适的。如果设计成没有紧急情况监视器的安全气囊类或者GPS是否合理?在这里同样合理。将每个功能单独划分为bundle,这样做使我们获得了很好的扩展性,但同时会变得更加复杂。在设计组件时,这也是一个很好的例子,它充分体现了高内聚低耦合的设计思路。随着Toast的演进,我们会不断调整设计;同时在进行bundle编码时,我们会更加关注类似的设计思路以及其他一些最佳实践,对这些内容的探讨会贯穿全书。

在学习基于OSGi的Toast的过程中,既可以循序渐进地按书中指导学习,也可以在学习过程中通过示例管理器直接加载“Chapter 4.2 Hello, Toast”中的示例代码。

4.2.1 GPS bundle

下面开始创建一个新的插件工程,用来构建GPS bundle:

  • 从对话框中选择File > New > Project….,展开Plug-In Development ,选择Plug-in Project,然后点击Next 打开New Plug-in Project向导,如图4-5所示。

http://assets.osgi.com.cn/article/7289496/图片5.jpg

图4-5 New Plug-in Project向导

  • 项目名称输入org.equinoxosgi.toast.dev.gps。在Target Platform 选项中,选择an OSGi framework ,在下拉列表中选择Equinox
  • 向导中的其他设置项参考上图,然后点击Next

Project 和 Bundle名称

类似org.equinoxosgi.toast.dev.gps这样的项目名称,乍一看上去可能觉得有些奇怪——因为它们很像Java的包名称!实际上,这种做法自有其道理。Bundle的命名空间是扁平的一维空间,因此所有bundle的ID必须经过有效地组织管理。

这种管理方式更多地是采用习惯做法而非具体规则。我们通常使用反向域名的方式识别bundle,该方式与Java包命名方式类似,Java开发人员对此应该并不陌生。与Java中的包类似,所有的bundle都被放在一起,并需要彼此区分。管理命名空间时,使用反向域名非常便利,而且具备良好的可读性。

在Eclipse工具中,每个bundle都是基于一个单独的工程开发的。因为工程项目名称的命名空间也是一维的,所以最好将项目名称与bundle的Bundle-SymbolicName相匹配。

  • 在向导的Content页面,在Name选项中输入Toast Gps,在 Execution Environment选项中选择J2SE-1.4。阅读本书的过程中,每次创建bundle时都按照上述习惯进行操作。
  • 确保所有Options中的选项没有被选中,其他值都使用默认选项,然后点击Finish
  • 你可能会看到一个对话框,提示你切换到插件开发视图。如果接受,则点击Yes

一旦项目被创建,会弹出OSGi bundle的manifest编辑器。暂时先保留该编辑器,不要关闭;随后我们再来讨论。

在前面的学习中,我们已经编写了一个可以工作的Gps类,现在仅仅需要将该类添加到项目中:

  • Toast项目中选择Gps类,从操作菜单中选择Refactor > Move…。在org.equinoxosgi.toast.dev.gps项目下选择src文件夹,然后点击Create Package…按钮。
  • 包名同项目名保持一致:org.equinoxosgi.toast.dev.gps
  • 随后选择新创建的包,然后点击OK

现在GPS bundle已经具备了其所需的功能,但所有功能仅仅局限在bundle内部——从bundle外部是不可见的。实际上,也正是因为这样,原始的Toast项目提示存在编译错误。为了使代码对外可见,以便外部可以直接使用包中的功能,需要将该包进行导出(exported)。

  • 要进行上述操作,需要回到之前已经打开的OSGi bundle的manifest文件。如果关闭了该文件,在GPS项目的META-INF文件夹下找到MANIFEST.MF文件,并双击打开。
  • Runtime标签的Exported Packages 区域,点击Add…按钮。
  • 选择org.equinoxosgi.toast.dev.gps包,然后点击OK,进行保存。

打开manifest编辑器后,花点时间留意下bundle的manifest文件。通过manifest编辑器下方的标签页,可以编辑bundle manifest的各个组成部分;MANIFEST.MF标签页以文本的形式展示了manifest文件。下面,让我们来分析下GPS bundle的MANIFEST.MF文件的一些细节:

org.equinoxosgi.toast.dev.gps/MANIFEST.MF
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Toast Gps
Bundle-SymbolicName: org.equinoxosgi.toast.dev.gps
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: J2SE-1.4
Export-Package: org.equinoxosgi.toast.dev.gps

注意Bundle-SymbolicNameBundle-Version头标识,这两个头标识是必须的,它们是bundle间相互区别的唯一的标识符。许多其他的头标识,正是我们当初在填写创建bundle的向导时,按照提示添加的。Export-Package头标识列出了该bundle所有的导出包,这些导出包可以被其他bundle使用。

这就是你创建的第一个bundle。当然,随着后续学习的逐渐深入,你的bundle会变得更加复杂,但是一个bundle之所以为bundle,真正需要的是两样东西:一些有趣的构建(在本例中是Gps类);一个manifest文件。

4.2.2 安全气囊bundle

在此,我们不再重复创建第一个bundle时的那些操作细节,这里我们将给出一系列步骤,并以此完成对安全气囊bundle的创建。

  • 为该bundle创建一个名称为org.equinoxosgi.toast.dev.airbag的工程。记得使用Toast Airbag作为Name,在Execution Environment选项中选择J2SE-1.4
  • 将已有的Airbag类和IAirbagListener接口移至新bundle的org.equinoxosgi.toast.dev.airbag包中。
  • 打开bundle的manifest编辑器,并且将org.equinoxosgi.toast.dev.airbag包导出.

在manifest 编辑器中查看MANIFEST.MF页面,你会发现Airbag bundle的manifest与GPSbundle类似,均有一个单独的包被导出:

Org.equinoxosgi.toast.dev.airbag/MANIFEST.MF
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Toast Airbag
Bundle-SymbolicName: org.equinoxosgi.toast.dev.airbag
Bundle-Version: 1.0.0.qualifier
Bundle-RequiredExecutionEnvironment: J2SE-1.4
Export-Package: org.equinoxosgi.toast.dev.airbag

4.2.3 紧急情况监视器bundle

创建紧急情况监视器bundle也遵循类似的方式。

  • 创建一个名为org.equinoxosgi.toast.client.emergency的bundle工程。记得使用Toast Emergency作为Name,并在Execution Environment选项中选择J2SE-1.4
  • 将已有的EmergencyMonitor类移至新bundle的org.equinoxosgi.toast.client.emergency包中。

由于其他bundle并未使用该bundle中的代码,因此无需导出新包。相反,紧急情况监视器bundle需要从另外两个bundle导入包。实际上,在操作过程中,你的工作空间里应该会提示一些编译错误,这是由于安全气囊和GPS相关的类对于紧急情况监视器bundle来说是不可见的。

  • 打开紧急情况监视器bundle的manifest编辑器,然后选择Dependencies标签。
  • 在右侧的Imported Packages列表中,添加org.equinoxosgi.toast.dev.gpsorg.equinoxosgi.toast.dev .airbag包。这样可以在紧急情况监视器bundle看到位于其他bundle中的GPS和安全气囊的代码。
  • 此时,你可以向Imported Packages列表中添加org.osgi.framework包。在后续的处理中,会使用到OSGi框架中的某些类。

包过滤

在添加包的过程中,有一种简单的缩小包查找范围的方法,即:在Package Selection对话框顶部的查询区域内,输入*toast或者*osgi,这样可以按照输入的关键字过滤包。

现在,唯一剩下的就是Main中的代码了——该代码会将上述处理逻辑实例化并启动程序。在基于OSGi的系统中,应用程序里面没有main方法。

取而代之的是,应用程序由一系列与OSGi框架的生命周期相关联的bundle构成。bundle可以参与到框架的生命周期中的一种途径是定义一个bundle激活器。

一个bundle激活器必须实现OSGi BundleActivator接口。这正是之前我们需要导入org.osgi.framework包的原因。现在,我们在应急工程中创建一个新的bundle激活器类。

  • 打开manifest编辑器,在Overview标签上点击Activator 链接,此时会打开一个已经完成部分内容的New Java Class向导。
  • org.equinoxosgi.toast.client.emergency包中创建一个名称为Activator的类。点击Finish后,将创建一个Activator类的基本结构,该结构中包含所需要的startstop方法。
  • 将下列代码添加至类中。

    org.equinoxosgi.toast.client.emergency/Activator
    public class Activator implements BundleActivator {
        private Airbag airbag;
        private Gps gps;
        private EmergencyMonitor monitor;
        public void start(BundleContext context) throws Exception {
        System.out.println("Launching");
        gps = new Gps();
        airbag = new Airbag();
        monitor = new EmergencyMonitor();
        monitor.setGps(gps);
        monitor.setAirbag(airbag);
        monitor.startup();
        airbag.deploy();
        }
        public void stop(BundleContext context) throws Exception {
        monitor.shutdown();
        System.out.println("Terminating");
        }
    }
    

请注意,start方法中的内容与此前我们写的main方法基本相同。OSGi框架会在启动该bundle时调用start方法。同样,当框架停止该bundle时调用stop方法。

至此,紧急情况监视器bundle已经创建完成了。现在我们看一下manifest。注意Bundle-Activator头标识,它标识了bundle的启动入口。与此同时,你也会发现该bundle导入了三个包,而其本身并未导出任何包。

 org.equinoxosgi.toast.client.emergency/MANIFEST.MF
 Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-Name: Toast Emergency
 Bundle-SymbolicName: org.equinoxosgi.toast.client.emergency
 Bundle-Version: 1.0.0.qualifier
 Bundle-RequiredExecutionEnvironment: J2SE-1.4
 Bundle-Activator: org.equinoxosgi.toast.client.emergency.Activator
 Import-Package: org.equinoxosgi.toast.dev.airbag,
  org.equinoxosgi.toast.dev.gps,
  org.osgi.framework;version="1.5.0"
  • 随着代码重构的完成,你可以在工作空间中安全地将此前创建的Toast工程删除了。

4.2.4 启动

现在,我们已经完成了三个bundle,基于OSGi 的Toast目前具备了启动的条件。回顾 4.1.6节,我们运行程序时使用了Run As > Java Application菜单运行Toast。在该菜单下,有一个启动配置(launch configuration)选项,描述了如何运行Toast。下面,我们将介绍如何运行基于OSGi的系统。当然,你同样可以使用示例管理器来装载本章的代码,然后使用应急工程中的启动配置。按照如下步骤,创建一个启动配置(如图4-6所示)。

  • 从菜单栏中选择Run > Run Configurations…
  • 选择OSGi Framework,然后从菜单中选择New,此时将创建一个新的启动配置。
  • 输入名称Toast
  • 在右侧选择Deselect All按钮。
  • Workspacebundle列表中,选择Toast的三个bundle,并勾选前面的选项。
  • Target Platform中选择org.eclipse.osgi
  • 取消对Add new workspace bundles to this launch configuration automatically选项的勾选。
  • Common页中,选择Shared file 选项,使用/org.equinoxosgi.toast.client.emergency作为文件夹。这样能够将launch configuration保存在工程中,由此可以更加便捷地与其他小组成员共享。
  • 最后,点击Run按钮。

http://assets.osgi.com.cn/article/7289496/图片6.jpg

图4-6 基于OSGi的Toast启动配置

运行该启动配置后会立即启动OSGi框架。框架随后会装载启动配置中列出的bundle并启动。这时,应急监控bundle激活器中的start方法开始运行,在控制台中,你应该会看到如下信息:

 osgi> Launching 
 Emergency occurred at lat=3776999 lon=-12244694 heading=90 speed=50

框架会保持运行状态,直到你在控制台中输入close,此时框架会关闭。框架关闭时会调用应急监控bundle激活器中的stop方法,这时控制台会显示如下信息:

Launching 
 Emergency occurred at lat=3776999 lon=-12244694 heading=90 speed=50
 osgi> close
 Terminating

4.3 总结

通过引入一个简单的通信场景,我们开始本章的介绍。我们所做的第一步工作,只是采用面向对象的方法进行设计,而暂不考虑OSGi。对Toast的第一次重构是将其划分为三个bundle,分别对应于GPS和安全气囊,以及紧急情况监视器。bundle之间的依赖关系简单清晰。通过在紧急情况监控器bundle中加入激活器,可以使其具备OSGi bundle生命周期的特征。系统的运行借助了基于OSGi的启动配置,通过查看应用程序的控制台的输出,可以即时查看系统的启动和停止。

通过对本章的学习,你已经了解了如何将一个非OSGi的工程转换为一组OSGi bundle。同时你也学会了如何创建bundle的manifest,以及如何使用启动配置运行OSGi应用。

将这样一个简单的系统转换为一个动态的通信领域里的应用程序,同时还要求其功能全面,还有一段很长的路要走,但是我们已经完成了第一步——定义并运行bundle。

查看评论