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应用的容器,基于Apache的OSGi实现——*Apache Felix*。
Karaf在Felix基础上提供了便捷功能(如命令行界面),帮助我们快速上手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()
则在包停止前调用。每个包最多包含一个BundleActivator。BundleContext
对象允许与OSGi运行时交互(后续详述)。
5. 构建包
修改pom.xml
将其转换为真正的OSGi包:
声明打包类型:
<packaging>bundle</packaging>
使用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();
}
}
我们使用BundleContext向OSGi平台注册服务实例。
配置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 {
}
实现BundleActivator
的start()
方法:
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
执行流程:
- 服务包启动 → 注册服务到平台
- 平台通知客户端服务可用
- 客户端获取服务引用并调用实现
10. 总结
本文通过简单示例探索了OSGi的核心功能,足以理解其潜力。当需要保证应用更新时不中断服务时,OSGi是可靠的解决方案。
完整代码见GitHub仓库。