1. 简介

许多Java关键任务和中间件应用都有严格的技术要求。有些需要支持热部署,避免中断运行服务;有些则需要能同时使用同一包的不同版本,以兼容外部遗留系统。

OSGi平台是满足这类需求的理想解决方案。

OSGi(Open Service Gateway Initiative) 是定义基于Java的组件系统的规范。目前由*OSGi Alliance管理,首个版本可追溯至1999年。如今它已成为组件系统的黄金标准,被广泛应用。例如,Eclipse IDE* 就是基于OSGi构建的应用程序。

本文将通过Apache的实现,探索OSGi的核心功能。

2. OSGi 基础

在OSGi中,单个组件称为包(bundle)。

逻辑上,包是具有独立生命周期的功能单元——意味着它可以独立启动、停止和卸载。

技术上,包就是带有特殊MANIFEST.MF文件的jar文件,该文件包含OSGi特定的头部信息。

OSGi平台提供机制,当包可用或被移除时发送通知。这使得设计良好的客户端即使依赖的服务暂时不可用,也能继续工作(可能功能降级)。

因此,包必须显式声明需要访问的包,OSGi平台仅在依赖项存在于包自身或已安装的其他包中时才会启动它。

3. 获取工具

我们从这里下载最新版Apache Karaf开启OSGi之旅。Apache Karaf是运行OSGi应用的容器,基于ApacheOSGi实现——*Apache Felix*。

KarafFelix基础上提供了便捷功能(如命令行界面),帮助我们快速上手OSGi。安装步骤可参考官方文档

4. 包入口点

OSGi环境中执行应用,需将其打包为OSGi包并定义入口点——注意,入口点不是常规的public static void main(String[] args)方法。

让我们构建一个基于OSGi的"Hello World"应用。

首先添加核心OSGi API依赖:

<dependency>
    <groupId>org.osgi</groupId> 
    <artifactId>org.osgi.core</artifactId>
    <version>6.0.0</version>
    <scope>provided</scope>
</dependency>

依赖声明为provided,因为OSGi运行时已提供,无需嵌入包中。

接着编写简单的HelloWorld类:

public class HelloWorld implements BundleActivator {
    public void start(BundleContext ctx) {
        System.out.println("Hello world.");
    }
    public void stop(BundleContext bundleContext) {
        System.out.println("Goodbye world.");
    }
}

*BundleActivator是*OSGi提供的接口,必须由包的入口点类实现。**

start()方法在包启动时被OSGi平台调用,stop()则在包停止前调用。每个包最多包含一个BundleActivatorBundleContext对象允许与OSGi运行时交互(后续详述)。

5. 构建包

修改pom.xml将其转换为真正的OSGi包:

  1. 声明打包类型:

    <packaging>bundle</packaging>
    
  2. 使用Apache Felix社区的maven-bundle-plugin打包:

    <plugin>
     <groupId>org.apache.felix</groupId>
     <artifactId>maven-bundle-plugin</artifactId>
     <version>3.3.0</version>
     <extensions>true</extensions>
     <configuration>
         <instructions>
             <Bundle-SymbolicName>
                 ${pom.groupId}.${pom.artifactId}
             </Bundle-SymbolicName>
             <Bundle-Name>${pom.name}</Bundle-Name>
             <Bundle-Version>${pom.version}</Bundle-Version>
             <Bundle-Activator>
                 com.baeldung.osgi.sample.activator.HelloWorld
             </Bundle-Activator>
             <Private-Package>
                 com.baeldung.osgi.sample.activator
             </Private-Package>            
         </instructions>
     </configuration>
    </plugin>
    

instructions部分指定要包含在MANIFEST文件中的OSGi头部:

  • Bundle-Activator:入口点类的全限定名
  • Private-Package:包含在包中但不对外暴露的包

执行mvn clean install构建包。

6. 安装和运行包

启动Karaf

<KARAF_HOME>/bin/karaf start

Karaf控制台安装包:

> bundle:install mvn:com.baeldung/osgi-intro-sample-activator/1.0-SNAPSHOT
Bundle ID: 63

Karaf会分配数字ID(因环境而异)。此时包仅安装未启动,执行以下命令启动:

> bundle:start 63
Hello World

控制台立即输出"Hello World"。停止并卸载包:

> bundle:stop 63
> bundle:uninstall 63

根据stop()方法,控制台输出"Goodbye World"。

7. OSGi 服务

编写一个简单的OSGi服务:定义问候接口:

package com.baeldung.osgi.sample.service.definition;
public interface Greeter {
    public String sayHiTo(String name);
}

实现类同时作为BundleActivator,在启动时注册服务:

package com.baeldung.osgi.sample.service.implementation;
public class GreeterImpl implements Greeter, BundleActivator {

    private ServiceReference<Greeter> reference;
    private ServiceRegistration<Greeter> registration;

    @Override
    public String sayHiTo(String name) {
        return "Hello " + name;
    }

    @Override 
    public void start(BundleContext context) throws Exception {
        System.out.println("Registering service.");
        registration = context.registerService(
          Greeter.class, 
          new GreeterImpl(), 
          new Hashtable<String, String>());
        reference = registration
          .getReference();
    }

    @Override 
    public void stop(BundleContext context) throws Exception {
        System.out.println("Unregistering service.");
        registration.unregister();
    }
}

我们使用BundleContextOSGi平台注册服务实例。

配置maven-bundle-plugin

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Bundle-SymbolicName>
                ${project.groupId}.${project.artifactId}
            </Bundle-SymbolicName>
            <Bundle-Name>
                ${project.artifactId}
            </Bundle-Name>
            <Bundle-Version>
                ${project.version}
            </Bundle-Version>
            <Bundle-Activator>
                com.baeldung.osgi.sample.service.implementation.GreeterImpl
            </Bundle-Activator>
            <Private-Package>
                com.baeldung.osgi.sample.service.implementation
            </Private-Package>
            <Export-Package>
                com.baeldung.osgi.sample.service.definition
            </Export-Package>
        </instructions>
    </configuration>
</plugin>

⚠️ 关键点:

  • 通过Export-Package导出com.baeldung.osgi.sample.service.definition
  • com.baeldung.osgi.sample.service.implementation标记为私有,其他包无法直接访问

8. OSGi 客户端

编写客户端,在启动时查找并调用服务:

public class Client implements BundleActivator, ServiceListener {
}

实现BundleActivatorstart()方法:

private BundleContext ctx;
private ServiceReference serviceReference;

public void start(BundleContext ctx) {
    this.ctx = ctx;
    try {
        ctx.addServiceListener(
          this, "(objectclass=" + Greeter.class.getName() + ")");
    } catch (InvalidSyntaxException ise) {
        ise.printStackTrace();
    }
}

addServiceListener()让客户端监听符合表达式的服务通知。表达式使用类似LDAP的语法,这里监听Greeter服务。

实现回调方法:

public void serviceChanged(ServiceEvent serviceEvent) {
    int type = serviceEvent.getType();
    switch (type){
        case(ServiceEvent.REGISTERED):
            System.out.println("Notification of service registered.");
            serviceReference = serviceEvent
              .getServiceReference();
            Greeter service = (Greeter)(ctx.getService(serviceReference));
            System.out.println( service.sayHiTo("John") );
            break;
        case(ServiceEvent.UNREGISTERING):
            System.out.println("Notification of service unregistered.");
            ctx.ungetService(serviceEvent.getServiceReference());
            break;
        default:
            break;
    }
}

Greeter服务状态变更时触发:

  • ✅ 服务注册:获取引用并调用
  • ❌ 服务注销:释放服务引用

最后实现stop()方法:

public void stop(BundleContext bundleContext) {
    if(serviceReference != null) {
        ctx.ungetService(serviceReference);
    }
}

客户端依赖配置:

<dependency>
    <groupId>com.baeldung</groupId>
    <artifactId>osgi-intro-sample-service</artifactId>
    <version>1.0-SNAPSHOT</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.osgi</groupId>
    <artifactId>org.osgi.core</artifactId>
    <version>6.0.0</version>
</dependency>

9. 客户端与服务交互

Karaf中安装服务包和客户端包:

> install mvn:com.baeldung/osgi-intro-sample-service/1.0-SNAPSHOT
Bundle ID: 64
> install mvn:com.baeldung/osgi-intro-sample-client/1.0-SNAPSHOT
Bundle ID: 65

⚠️ 包ID因环境而异。启动客户端包:

> start 65

此时无输出,因为客户端正在等待服务。启动服务包:

> start 64
Registering service.
Service registered.
Hello John

执行流程:

  1. 服务包启动 → 注册服务到平台
  2. 平台通知客户端服务可用
  3. 客户端获取服务引用并调用实现

10. 总结

本文通过简单示例探索了OSGi的核心功能,足以理解其潜力。当需要保证应用更新时不中断服务时,OSGi是可靠的解决方案。

完整代码见GitHub仓库


原始标题:Introduction to OSGi