`

OSGi开发常见问题与编程技巧

 
阅读更多

1.摘要

本文简要讨论了如何看待OSGi,以及在OSGi之上进行开发的一些关键概念,常见问题及编程提示 。

2.如何看待OSGi

JAVA面向对象的编程语言彻底推翻了过程化编程的模型,开启了软件开发的一个新的时代。但是,Java语言并没有过多的关注如何为大规模系统的开发提供支持。在Java技术中,包(Package)的应用将模块化开发推进了一小步,代码文件的归档(JAR文件)发布为模块化部署推进了一小步。为了实现动态扩展的应用系统,降低系统的维护成本,优秀的软件开发人员必须绞尽脑汁为业务系统建立松散耦合的实现模型,采用各种各样的开发模式。尽管如此,系统的需求变更往往是牵一发而动全身。

抛除OSGi的其他特性,OSGi与通常的面向对象的编程的唯一不同是基于OSGi的开发必须建立在模块化编程的概念之上。OSGi通过在JVM之上提供一个基础框架提供动态的模块化系统开发的支持。

OSGi可以说抓住了系统模块化开发需求的关键,在充分利用现有技术(重新建立一套编程语言并提供模块化需求开发支持将是不可想象的)的同时,定义了包(Package)的可见性机制为这一问题提供了一种巧妙的解决方案。OSGi模块化开发支持的实现,其关键是充分利用了JAVA的类加载机制并扩展了JAR元数据清单(META-INF/MANIFEST.MF)来定义模块(Bundle)。因此,在进行OSGi编程开发之前,开发人员必须熟悉JAVA的类加载机制和代码文件归档(JAR)机制。

3.理解类加载机制

关于Java的类加载机制问题可以说是一个热门话题,但是据我所知,大多数Java开发人员对于Java的类加载机制并不是完全理解甚至是并不过多的关注。在此我不打算详细讨论Java的类加载机制,如果你不是很熟悉,可以Google一下:)。

通常,虚拟机启动时首先加载内部类(java.*等等,即:rt.jar),然后加载位于CLASSPATH环境变量下的类代码,然后加载用户的类代码。很多应用系统,如Tomcat在加载完CLASSPATH下的类代码后使用自定义的ClassLoader开始内部的类代码及Web应用的加载过程。

如果开发人员在自己的类定义A中引用其他的类定义B,虚拟机在加载该类时,根据加载该类的ClassLoader的实现机制,通常与虚拟机加载相反的过程查找引用的类。此处常见的两种机制是Self-First和Parent-First。

Self-First机制首先查找当前ClassLoader加载的所有类中是否存在引用的类B,如果没有则交给其Parent ClassLoader进行查找,如果父类加载器加载的类中存在类B则返回,否则代理交给其父类加载器继续查找,依次类推直至根类加载器。

对于Parent-First机制,当前类加载器首先将查找过程代理给其父类加载器进行查找,父类加载器同样继续代理给它自己父类加载器直至根类加载器,如果根类加载器查找到自己加载的类中存在引用的类B则返回,否则,由其子类加载器查找类B,直至当前类加载器。

image

上图展示了上述两种类加载机制,红色箭头表示当前ClassLoader的类查找操作。在上述两种机制中,类A可以引用类B,如果类A同时引用了类B',而在运行时B'由C5类加载器加载,则会出现ClassNotFoundException。

注意,不同的ClassLoader机制可能不同。

4.常见的类加载问题

下图简单展示了OSGi的类加载机制,Bundle1内部私有的类A引用了Bundle2中公开的类B,类C为Bundle2内部私有。Bundle1的ClassLoader可以直接加载类B,可以通过框架的ClassLoader加载框架实现类(这与具体的OSGi实现框架有关,不同的实现可能不同)。Bundle1的类域为启动类加载器加载的所有类,框架类加载器加载的所有类,Bundle1引用的其他Bundle公开的类及Bundle1内部的类。对于下图来说,Bundle1是无法访问Bundle2的内部类C及Bundle3公开的类D的。

image

在OSGi的初始开发过程中,最常见的问题就是无法找到类的异常。碰到此类问题时可以根据上述类加载及查找机制逐步分析问题出现的原因。

5.编程技巧与提示

说明:本文中给出技巧与提示部分分别来自于Peter KriensaQute网站和Knopflerfish网站。下述给出的技巧与提示其可用性是相对的,开发人员在使用过程中应该根据实际情况进行权衡。

5.1功能接口定义与内部实现分包定义

在确定了一个Bundle提供的功能后,应该将该Bundle对外公布的接口和接口功能的实现使用不同的包定义,充分利用Bundle的包公开和隐藏机制。如,可以将Bundle的功能接口定义在包com.acme.service中,而将其实现定义在com.acme.service.internal包或com.acme.service.impl包中,在Export-Packages中只发布com.acme.service包而隐藏其他的内部包。

BundleActivator的使用

BundleActivator是一个Bundle与OSGi环境交互的桥梁,也是一个Bundle的生命周期的入口。如果一个Bundle不需要获取任何的其他Bundle和OSGi环境的信息,而且也不需要在Bundle启动和停止时进行某些初始化和资源释放操作,则不必定义BundleActivator。

如果Bundle内部功能实现需要获取其他Bundle或OSGi环境信息(如OSGi服务),则可以通过BundleActivator从OSGi环境中接收的BundleContext获取。可以将BundleContext以静态变量的形式存储下来,Bundle内部的其他类可以直接访问而不必为每一个需要的类定义接收BundleContext的构造函数。如下述代码所示:

// Static bundle context
public class Activator implements BundleActivator {

   static BundleContext bc;

   public void start(BundleContext bc) {
     Activator.bc = bc;
     ...
   }

   public void stop(BundleContext bc) {
     Activator.bc = null; // allow for garbage collection
     ...
   }
}
<style type="text/css"> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }</style>

5.2资源释放与自动的服务注销

如果一个Bundle注册了服务(Service),不必在BundleActivator的Stop方法中手动注销此服务,因为当Bundle停止时OSGi框架会自动注销该Bundle注册的服务。

如果在Bundle启动过程中获取了其他的资源,如线程,内存或窗口句柄等,则必须在此Bundle的Stop方法中置为NULL以便于系统进行垃圾回收。如下述代码所示:

public class Activator implements BundleActivator {

  public void start(BundleContext bc) {

    Hashtable props = new Hashtable();
    props.put("service.pid", "myfantasticpid");

    ManagedService mg = new ManagedService() {
      public void updated(Dictionary conf) {
        ...
      }
    };

    // BundleActivator methods now hidden
    // from outside access.
    bc.registerService(ManagedService.class.getName(),
                       mg, props);

  }
 // Cleanup up resources in stop method
public void stop(BundleContext bc) {
  Activator.bc = null;
  if(window != null) {
    window.setVisible(false);
    window = null;
  }
}
}
<style type="text/css"> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }</style>

5.3不要在BundleActivator中处理耗时的任务

如果一个Bundle在启动过程中需要处理耗时的动作,则不要在BundleActivator的启动线程中处理,否则将会阻塞其他Bundle的启动,从而影响整个OSGi的启动过程。

public void start(BundleContext bc) {
    new Thread("longTaskName") {
      { start(); } // I like instance initializer blocks ;)
      public void run() {
         ...long operation
      }
    };
  }
<style type="text/css"> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }</style>

5.4跟踪使用的服务

如果在一个Bundle中需要使用其他Bundle提供的服务,则需要在考虑服务不存在或突然被注销的情形。由于OSGi环境是一个动态的环境,Bundle可以在任何时候安装,启动,停止或卸载,所以使用其他Bundle提供的服务时,必须跟踪这些服务的状态。

如果使用一个服务,可以使用ServiceTracker对该服务进行跟踪。

ServiceTracker logTracker =
    new ServiceTracker(bc,
                       LogService.class.getName(),
                       null);
  logTracker.open();

  // this might throw a NullPointerException
  // if the log is not available
  (LogService)logTracker
    .getService().log(LogService.LOG_INFO,
                      "Hello log");
<style type="text/css"> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }</style>

如果使用多个服务,可以使用ServiceListener监听该Bundle所关注的所有服务引发的事件,跟踪这些服务的状态。

 ServiceListener listener = new ServiceListener() {
    public void serviceChanged(ServiceEvent ev) {
     ServiceReference sr = ev.getServiceReference();

      switch(ev.getType()) {
        case ServiceEvent.REGISTERED:
          ...
          break;
        case ServiceEvent.UNREGISTERING:
          ...
         break;
      }
    }
  };

  String filter =
   "(objectclass=" + HttpService.class.getName() + ")";

  try {
    bc.addServiceListener(listener, filter);

    // get all already registered services.
    ServiceReference[] srl =
      bc.getServiceReferences(null, filter);

    for(int i = 0; srl != null && i new ServiceEvent(ServiceEvent.REGISTERED,
          srl[i]));
    }
  } catch (Exception e) {
     log.error("Failed to set up listener for http",
               e);
  }
<style type="text/css"> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }</style>

开发人员也可以继承ServiceTracker提供对某一项服务的跟踪。

class HttpTracker extends ServiceTracker {
    int            count;
    ServiceTracker logTracker;

    HttpTracker(BundleContext context,
        ServiceTracker logTracker) {
      super(context, HttpService.class.getName(), null);
      this.logTracker = logTracker;
    }

    public Object addingService(ServiceReference reference) {
      try {
        HttpService http = (HttpService) super
            .addingService(reference);
        http.registerServlet("/hello", new MiniServlet(),
            null, null);
        log("Added http service #" + ++count);
        return http;
      } catch (Exception nse) {
        log("Failed to add http service: " + nse);
      }
      return null;
    }

    public void removedService(ServiceReference reference,
        Object service) {
      super.removedService(reference, service);
      count--;
      log("Removed http service, left #" + count--);
    }

    public void modifiedService(ServiceReference reference,
        Object service) {
      super.modifiedService(reference, service);
      log("Modified http service");
    }

    void log(final String message) {
      LogService log = (LogService) logTracker.getService();
      if (log != null) log.log(LogService.LOG_INFO, message);
      else System.err.println(message);
    }
  }
<style type="text/css"> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }</style>
分享到:
评论

相关推荐

    Enterprise OSGI in action

    同时,本书也解答了为何传统上的企业级Java应用和OSGi结合得并不理想,以及企业OSGi如何解决这一问题,从而提出了一种新的编程模型。 书中分成三个部分:首先介绍了为何现在使用OSGi对企业编程很重要,然后带领读者...

    osgi资料

    - 最佳实践:分享开发者在OSGi开发过程中总结的经验和技巧,帮助避免常见陷阱。 3. **OSGI实战.pdf**: 这本书可能专注于通过实际项目展示OSGi的应用: - 开发工具:介绍支持OSGi的IDE(如Eclipse)和构建工具...

    osgi实战(pdf超请版)

    - **6.9 注意事项**:总结在开发过程中需要注意的关键问题和常见陷阱。 #### 七、深入OSGI - **7.1 关于OSGI**:更深层次地探讨OSGI的概念、设计理念和核心原则。 - **7.2 OSGI R4规范**:详细解析OSGI R4规范,...

    Osgi in Action: Creating Modular Applications in Java Jun 2010

    这些技术包括编程实践、利用多类加载器的技巧以及进程内组件之间的序列化等。然而,这些方法本质上是脆弱且容易出错的,因为它们无法通过编译时或运行时检查进行强制执行。 这一缺陷对应用程序生命周期中的多个阶段...

    OSGI实战

    - **6.8.2 C/S**: 在客户端/服务器架构下的OSGI开发案例。 - **6.8.3 嵌入式**: 讨论如何在嵌入式设备上部署OSGI系统。 - **6.9 注意事项**: - **兼容性**: 跨平台和跨版本的兼容性问题。 - **性能调优**: 如何...

    SWT应用的开发实例:没有使用到OSGi

    SWT(Standard Widget Toolkit)是Java中用于构建图形用户界面(GUI)的一种库,它是Eclipse项目的一...通过阅读和分析“Lottery.jar”和数据库相关文件,我们可以学习到SWT的具体应用和Java GUI编程的一些基本技巧。

    OSGi实战(文档+源代码)

    通过阅读“OSGi实战”和实践源代码,读者可以逐步掌握OSGi的实际应用技巧,提升在大型复杂系统中的模块化设计和管理能力。 总之,“OSGi实战”是一本面向实践者的指南,它不仅解释了OSGi的基本原理,还提供了丰富的...

    插件开发入门所需要的文档

    四、插件开发工具与技巧 1. **调试工具**:大多数插件开发环境都提供了调试工具,帮助开发者定位问题。 2. **代码复用**:合理利用面向对象编程原则,提高代码的复用性和可维护性。 3. **文档阅读**:理解插件...

    kotlin-reference-chinese

    它由JetBrains公司开发,其主要目标是解决Java平台开发中的一些常见问题。Kotlin不仅与Java语言完全兼容,还能够调用任何现有的Java库,并能够在现有的Java代码基础上进行扩展。Kotlin 1.1版本带来了许多新特性,...

    Modular Java

    ### 模块化Java:创建灵活的应用程序与OSGi及Spring #### 一、模块化Java的概念 在软件开发领域,模块化已经成为一种普遍接受的设计原则。它将一个大型应用程序分解为更小、更可管理的部分,每个部分都有明确的...

    kotlin入门教程

    Kotlin是一种现代、静态类型的编程语言,它被设计成与Java完全兼容,可以在任何支持Java的平台上运行。Kotlin由 JetBrains 开发,并于2011年首次发布。它旨在解决Java的一些限制,如冗余的语法和空指针异常等问题,...

    2017eclipse 最新中文版教材

    Eclipse是一款强大的开放源代码集成开发环境(IDE),主要用于Java语言的开发,但通过插件也可以支持其他编程语言,如C++、Python等。它以其丰富的功能、高度可扩展性和灵活性,成为了全球开发者广泛使用的开发工具...

    kotlin 官网教程中文翻译

    Kotlin是一种运行在Java虚拟机(JVM)上的编程语言,由JetBrains公司开发,旨在解决实际开发中遇到的常见问题。Kotlin不仅适合用于服务器端开发,也被广泛应用于Android应用程序开发和JavaScript应用中。其特性包括...

    Spring3 Enterprise Recipes.pdf

    本书《Spring3 Enterprise Recipes》是一部针对专业开发者的作品,采用问题—解决方案—工作原理的结构来阐述Spring框架及其在企业级应用中的应用技巧。以下是对该书中关键知识点的深入解析: 1. **Spring框架3概述...

    kotlin2018年最新中文文档

    最后,文档还提供了常见问题的解答,包括Kotlin与Java语言的比较,以及对Kotlin最新特性的介绍,如Kotlin 1.1和1.2的新增特性。这部分内容帮助开发者更好地理解Kotlin的设计哲学,并与其它流行的编程语言相比较。 ...

    spring-dm-reference

    - **了解 OSGi**:OSGi 是一种模块化系统和应用编程框架,用于构建可动态更新、扩展和服务导向的应用程序。 - **尝试示例**:通过官方文档提供的示例来快速上手 Spring DM,这些示例通常包括安装步骤、基本配置和...

    Eclipse RAP Deploy - 针对Eclipse 3.5 + Tomcat

    Tomcat是Apache软件基金会的开源Servlet容器,它实现了Java Servlet和JavaServer Pages(JSP)规范,是开发和运行Java Web应用的常见选择。在Eclipse RAP中,Tomcat作为服务器端运行环境,处理HTTP请求并返回动态...

    Kotlin教学

    - **Kotlin 与 OSGi**:支持 OSGi 框架下的模块化开发。 - **编译器插件**:允许扩展 Kotlin 编译器的功能。 #### 常见问题 (FAQ) Kotlin 官方文档中提供了一系列常见问题解答,涵盖了从安装到高级主题的各个方面。...

    大数据面试宝典

    这份“大数据面试宝典”涉及的内容包括了面试技巧和专业知识的多方面考核,其中所提及的知识点非常丰富,涵盖了大数据、Java编程、设计模式、JVM、并发编程等多个领域。下面详细说明文档中的关键知识点: 1. **JDK...

Global site tag (gtag.js) - Google Analytics