1. Introduction
1.介绍
Several Java mission-critical and middleware applications have some hard technological requirements.
一些Java关键任务和中间件应用有一些硬性技术要求。
Some have to support hot deploy, so as not to disrupt the running services – and others have to be able to work with different versions of the same package for the sake of supporting external legacy systems.
有些必须支持热部署,以便不破坏正在运行的服务–而另一些则必须能够使用同一软件包的不同版本,以便支持外部遗留系统。
The OSGi platforms represent a viable solution to support this kind of requirements.
OSGi平台代表了支持这种要求的可行解决方案。
The Open Service Gateway Initiative is a specification defining a Java-based component system. It’s currently managed by the OSGi Alliance, and its first version dates back to 1999.
Open Service Gateway Initiative是一个定义基于Java的组件系统的规范。它目前由OSGi Alliance管理,其第一个版本可追溯到1999年。
Since then, it has proved to be a great standard for component systems, and it’s widely used nowadays. The Eclipse IDE, for instance, is an OSGi-based application.
从那时起,它被证明是组件系统的一个伟大的标准,而且现在被广泛使用。例如,Eclipse IDE就是一个基于OSGi的应用程序。
In this article, we’ll explore some basic features of OSGi leveraging the implementation provided by Apache.
在这篇文章中,我们将利用Apache提供的实现,探索OSGi的一些基本功能。
2. OSGi Basics
2.OSGi基础知识
In OSGi, a single component is called a bundle.
在OSGi中,单个组件被称为bundle.。
Logically, a bundle is a piece of functionality that has an independent lifecycle – which means it can be started, stopped and removed independently.
从逻辑上讲,捆绑是一块具有独立生命周期的功能–这意味着它可以独立启动、停止和移除。
Technically, a bundle is just a jar file with a MANIFEST.MF file containing some OSGi-specific headers.
从技术上讲,bundle只是一个带有MANIFEST.MF文件的jar文件,其中包含一些OSGi专用头文件。
The OSGi platform provides a way to receive notifications about bundles becoming available or when they’re removed from the platform. This will allow a properly designed client to keep working, maybe with degraded functionality, even when a service it depends on, is momentarily unavailable.
OSGi平台提供了一种方法来接收关于捆绑程序可用或从平台移除的通知。这将允许一个设计合理的客户端继续工作,即使它所依赖的服务暂时不可用,也可能会降低功能。
Because of that, a bundle has to explicitly declare what packages it needs to have access to and the OSGi platform will start it only if the dependencies are available in the bundle itself or in other bundles already installed in the platform.
正因为如此,一个捆绑包必须明确声明它需要访问哪些包,而且只有在捆绑包本身或平台上已经安装的其他捆绑包中存在依赖关系时,OSGi平台才会启动它。
3. Getting the Tools
3.获取工具
We’ll start our journey in OSGi by downloading the latest version of Apache Karaf from this link. Apache Karaf is a platform that runs OSGi-based applications; it’s based on the Apache‘s implementation of OSGi specification called Apache Felix.
我们将通过从这个链接下载最新版本的Apache Karaf来开始我们的OSGi之旅。Apache Karaf是一个运行基于OSGi-的应用程序的平台;它基于Apache对OSGi规范的实现,名为Apache Felix。
Karaf offers some handy features on top of Felix that will help us in getting acquainted with OSGi, for example, a command line interface that will allow us to interact with the platform.
Karaf在Felix的基础上提供了一些方便的功能,这将有助于我们熟悉OSGi,例如,一个命令行界面将允许我们与平台互动。
To install Karaf, you can follow the installation instruction from the official documentation.
要安装Karaf,你可以按照官方文档中的安装说明进行安装。
4. Bundle Entry Point
4.捆绑式入口点
To execute an application in an OSGi environment, we have to pack it as an OSGi bundle and define the application entry point, and that’s not the usual public static void main(String[] args) method.
要在OSGi环境中执行一个应用程序,我们必须把它打包成一个OSGi包,并定义应用程序的入口点,而这不是通常的public static void main(String[] args)方法。
So, let’s start by building an OSGi- based “Hello World” application.
因此,让我们从构建一个基于OSGi-的 “Hello World “应用程序开始吧!。
We start setting up a simple dependency on the core OSGi API:
我们开始设置一个对核心OSGi API的简单依赖。
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
The dependency is declared as provided because it will be available in the OSGi runtime, and the bundle doesn’t need to embed it.
依赖关系被声明为provided,因为它将在OSGi运行时可用,捆绑程序不需要嵌入它。
Let’s now write the simple HelloWorld class:
现在让我们来写一个简单的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 is an interface provided by OSGi that has to be implemented by classes that are entry points for a bundle.
BundleActivator是一个由OSGi提供的接口,必须由作为bundle入口点的类实现。
The start() method is invoked by the OSGi platform when the bundle containing this class is started. On the other hand stop() is invoked before just before the bundle is stopped.
start()方法被OSGi平台在包含该类的bundle启动时调用。另一方面,stop()在捆绑体被停止之前被调用。
Let’s keep in mind that each bundle can contain at most one BundleActivator. The BundleContext object provided to both methods allows interacting with the OSGi runtime. We’ll get back to it soon.
让我们记住,每个捆绑包最多可以包含一个BundleActivator。提供给两个方法的BundleContext对象允许与OSGi运行时进行交互。我们很快就会回到它上面。
5. Building a Bundle
5.构建一个捆绑式产品
Let’s modify the pom.xml and make it an actual OSGi bundle.
让我们修改pom.xml,使其成为一个实际的OSGi捆绑包。
First of all, we have to explicitly state that we’re going to build a bundle, not a jar:
首先,我们必须明确指出,我们要建立一个bundle,而不是一个jar。
<packaging>bundle</packaging>
Then we leverage the maven-bundle-plugin, courtesy of the Apache Felix community, to package the HelloWorld class as an OSGi bundle:
然后我们利用maven-bundle-plugin(由Apache Felix社区提供),将HelloWorld类打包成OSGi包。
<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>
In the instructions section, we specify the values of the OSGi headers we want to include in the bundle’s MANIFEST file.
在说明部分,我们指定了我们想要包含在捆绑的MANIFEST文件中的OSGi头文件的值。
Bundle-Activator is the fully qualified name of the BundleActivator implementation that will be used to start and stop the bundle, and it refers to the class we’ve just written.
Bundle-Activator是BundleActivator实现的完全限定名称,它将用于启动和停止捆绑程序,它指的是我们刚刚编写的类。
Private-Package is not an OSGi header, but it’s used to tell the plugin to include the package in the bundle but not make it available to other ones. We can now build the bundle with the usual command mvn clean install.
Private-Package不是一个OSGi头,但它被用来告诉插件将包包含在捆绑包中,但不使其对其他的包可用。现在我们可以用通常的命令mvn clean install来构建捆绑包。
6. Installing and Running the Bundle
6.安装和运行软件包
Let’s start Karaf by executing the command:
让我们通过执行命令来启动Karaf。
<KARAF_HOME>/bin/karaf start
where <KARAF_HOME> is the folder where Karaf is installed. When the prompt of the Karaf console appears we can execute the following command to install the bundle:
其中<KARAF_HOME>是安装Karaf的文件夹。当Karaf控制台的提示出现时,我们可以执行以下命令来安装捆绑程序。
> bundle:install mvn:com.baeldung/osgi-intro-sample-activator/1.0-SNAPSHOT
Bundle ID: 63
This instructs Karaf to load the bundle from the local Maven repository.
这将指示Karaf从本地Maven仓库加载软件包。
In return Karaf prints out the numeric ID assigned to the bundle that depends on the number of bundles already installed and may vary. The bundle is now just installed, we can now start it with the following command:
作为回报,Karaf会打印出分配给分配给捆绑包,这取决于已经安装的捆绑包的数量,可能会有所不同。现在捆绑包刚刚安装完毕,我们现在可以用以下命令启动它。
> bundle:start 63
Hello World
“Hello World” immediately appears as soon the bundle is started. We can now stop and uninstall the bundle with:
捆绑程序一启动,”Hello World “就立即出现。我们现在可以用以下方法停止和卸载捆绑程序。
> bundle:stop 63
> bundle:uninstall 63
“Goodbye World” appears on the console, accordingly to the code in the stop() method.
根据stop()方法中的代码,”再见世界 “出现在控制台。
7. An OSGi Service
7.一个OSGi服务
Let’s go on writing a simple OSGi service, an interface that exposes a method for greeting people:
让我们继续编写一个简单的OSGi服务,一个暴露了问候方法的接口。
package com.baeldung.osgi.sample.service.definition;
public interface Greeter {
public String sayHiTo(String name);
}
Let’s write an implementation of it that is a BundleActivator too, so we’ll be able to instantiate the service and register it on the platform when the bundle is started:
让我们也写一个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();
}
}
We use the BundleContext as a mean of requesting the OSGi platform to register a new instance of the service.
我们使用BundleContext作为请求OSGi平台注册一个新服务实例的手段。
We should also provide the type of the service and a map of the possible configuration parameters, which aren’t needed in our simple scenario. Let’s now proceed with the configuration of the maven-bundle-plugin:
我们还应该提供服务的类型和可能的配置参数图,在我们的简单方案中并不需要。现在让我们继续配置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>
It’s worth noting that only the com.baeldung.osgi.sample.service.definition package has been exported this time, through the Export-Package header.
值得注意的是,这次只有com.baeldung.osgi.sample.service.define包通过Export-Package头被导出。
Thanks to this, OSGi will allow other bundles to invoke only the methods specified in the service interface. Package com.baeldung.osgi.sample.service.implementation is marked as private, so no other bundle will be able to access the members of the implementation directly.
得益于此,OSGi将允许其他捆绑程序只调用服务接口中指定的方法。包com.baeldung.osgi.sample.service.implementation被标记为私有,所以其他捆绑程序将不能直接访问该实现的成员。
8. An OSGi Client
8.一个OSGi客户端
Let’s now write the client. It simply looks up the service at startup and invokes it:
现在我们来编写客户端。它只是在启动时查找服务并调用它。
public class Client implements BundleActivator, ServiceListener {
}
Let’s implement the BundleActivator start() method:
我们来实现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();
}
}
The addServiceListener() method allows the client to ask the platform to send notifications about the service that complies with the provided expression.
addServiceListener()方法允许客户端要求平台发送关于符合所提供表达式的服务的通知。
The expression uses a syntax similar to the LDAP’s one, and in our case, we’re requesting notifications about a Greeter service.
该表达式使用了类似于LDAP的语法,在我们的案例中,我们要求得到关于Greeter服务的通知。
Let’s go on to the callback method:
让我们继续讨论回调方法。
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;
}
}
When some modification involving the Greeter service happens, the method is notified.
当涉及Greeter服务的一些修改发生时,该方法被通知。
When the service is registered to the platform, we get a reference to it, we store it locally, and we then use it to acquire the service object and invoke it.
当服务被注册到平台时,我们得到一个引用,我们把它存储在本地,然后用它来获取服务对象并调用它。
When the server is later unregistered, we use the previously stored reference to unget it, meaning that we tell the platform that we are not going to use it anymore.
当服务器后来被取消注册时,我们使用以前存储的引用来取消它,也就是说,我们告诉平台,我们不会再使用它。
We now just need to write the stop() method:
我们现在只需要编写stop()方法。
public void stop(BundleContext bundleContext) {
if(serviceReference != null) {
ctx.ungetService(serviceReference);
}
}
Here again, we unget the service to cover the case in which the client is stopped before the service is being stopped. Let’s give a final look at the dependencies in the pom.xml:
在这里,我们再次取消了服务,以涵盖在服务被停止之前客户端被停止的情况。让我们最后看看pom.xml中的依赖关系。
<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. Client and Service
9.客户和服务
Let’s now install the client and service bundles in Karaf by doing:
现在让我们在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
Always keep in mind that the identifier numbers assigned to each bundle may vary.
请始终牢记,分配给每个捆绑物的标识符号可能有所不同。
Let’s now start the client bundle:
现在让我们启动客户端捆绑程序。
> start 65
Therefore, nothing happens because the client is active and it’s waiting for the service, that we can start with:
因此,什么都没有发生,因为客户端是活动的,它在等待服务,我们可以开始使用。
> start 64
Registering service.
Service registered.
Hello John
What happens is that as soon as the service’s BundleActivator starts, the service is registered to the platform. That, in turn, notifies the client that the service it was waiting for is available.
发生的情况是,一旦服务的BundleActivator启动,该服务就被注册到平台上。这反过来又通知了客户,它所等待的服务是可用的。
The client then gets a reference to the service and uses it to invoke the implementation delivered through the service bundle.
然后,客户端获得对服务的引用,并使用它来调用通过服务包交付的实现。
10. Conclusion
10.结论
In this article, we explored the essential features of OSGi with a straightforward example that it’s enough to understand the potential of OSGi.
在这篇文章中,我们用一个简单的例子探讨了OSGi的基本特征,这足以让我们了解OSGi的潜力。
In conclusion, whenever we have to guarantee that a single application has to be updated without any disservice, OSGi can be a viable solution.
总之,只要我们必须保证单个应用程序的更新不受任何影响,OSGi就可以成为一个可行的解决方案。
The code for this post can be found over on GitHub.
这篇文章的代码可以在GitHub上找到over。