OSGi enRoute – 2.3_3 – Provider Bundle 工程

在本章中你将会学到什么

在本章中,我们会创建另外一个提供Eval API实现的工程, 即所谓的Provider。在本例中我们将其命名为simple.provider。我们将会从Provider Bundle中Export对应的api Package,并且解释为何这样做会简化很多事情。

20180212_101

确保你在顶层文件夹中:

关于 Service

Service是一个由Bundle注册的对象。它会注册在一个接口和多个属性之下。Service是动态的,意味着我们必须在编写使用Service的代码时尽量保守。我们使用Declarative Services 注解来大大简化了使用Service的工作。

创建一个Provider工程

在之前的章节中我们创建了一个用于提供表达式计算器的API。我们现在需要一个此服务的Provider。一个Provider负责定义在API中的契约,使得Consumer可以使用对应的服务。在Eval Service的例子中,Consumer将会调用eval(String)方法,而Provider必须实现该方法。

在 osgi.enroute.examples.eval 文件夹中,我们创建了一个名为simple.provider的文件夹。在这个文件夹中我们创建了POM。

POM

对应的parent和packaging元素与API工程中的一样:

在之前的章节中,我们学习过工程名称的最后一段定义了工程的类型;这同样在OSGi enRoute模板中得到支持。  一个Provider工程因此必须以.provider为名称的后缀。工程的命名我们一般以工作空间的名称、Service API名称以及一个能表示这是何种服务的实现的词语作为开头。下面来实现一个非常简单的服务实现,因此对应的名称可以为:osgi.enroute.examples.eval.simple.provider。因此POM我们需要有如下内容:

我们继承了OSGi enRoute的基础API(一系列标准化并且特殊的服务契约,以便于开发Web App),不过在本例中我们需要将API工程作为dependency:

服务实现的源码

下面我们需要增加一个实现类。在本例中,我们使用正则表达式来作为解析器,以便在文件src/main/java/osgi/enroute/examples/eval/provider/EvalImpl.java中实现一个非常简单的表达式解析器:

由于增加了@Component注解,这个类被设置为了Declarative Service(DS)服务Component。如果你的Component类实现了一个或多个接口,则这些接口会被自动注册为OSGi Service。因此在本例中,我们希望实现Eval接口并将其注册为Eval Service。

由于我们将org.osgi:osgi.enroute.examples.eval.api:1.0.0-SNAPSHOT 工程添加到了依赖之中,因此可以使用Eval接口。

Bnd文件

如果要生成一个合适的Bundle,我们需要在POM的相同文件夹中生成一个bnd.bnd文件。此文件内容如下:

导出(Export API

细心的读者应该已经发现了osgi.enroute.examples.eval.api 包被Export了,但是它并不是这个工程的一部分。这是bnd提供的很有用的一个小技巧。任何在Export-Package 或Private-Package Header中列出的包都会从classpath中复制出来,即使它们不是工程的一部分也是如此。

不过这样带来了一个问题,为什么API还被包含在Bundle之中?难道API不应该被放到单独的Bundle之中吗?

我们从Provider Bundle中Export API的原因是这样使得构建可执行文件更加简单,而且这样并不会带来任何隐性成本。没有隐性成本的原因是因为它的兼容性。正如我们在语义化版本控制章节所述,一个Provider与它的API没有后向兼容性。在API中的任何改变都需要改变Provider的代码,以确保Provider会实现服务契约所需的内容。

因此,将API与其Provider隔离开没有什么作用,因为一个给定的Provider几乎总是使用相同的API版本。使用这个版本的API会依赖更少的Bundle,并且更不容易造成metadata中的错误。

不过,OSGi专家们并不完全同意这些观点。

构建

用Maven的术语来说我们已经定义了一个多模块(Multi-Module)工程,目前它包含两个Module;api和simple.provider。这些Module应该在一个aggregator pom中进行定义,而这经常在Parent pom中进行。我们应该在父文件夹中的pom.xml中添加一段内容。

我们现在有了充分的工程信息来构建Bundle。由于我们修改了Parent pom,最好进行一次clean build。然后我们就可以看看Module(Bundle)到底是怎样的情况了。注意确保你处在顶层文件夹中!

通过这样我们可以看到Provider Bundle具有如下布局:

● Private packages – 仅在Bundle内部可用的包。其他的Bundle可以有同名但内容不同的包。

● Exported packages –提供给其他Bundle的包,需要及时维护它们。

● Imported packages – 期望其他Bundle Export的包,期望它的作者也会及时维护它们。

如下图所示:

20180212_102

它是如何工作的?

消费者(Consumer)和提供者(Provider)的概念很容易混淆,主要是因为接口的实现者和客户端常常被混淆。然而,在Service包中,一个Service API的Provider可以同时是接口的实现和客户端。一个Provider负责提供契约的价值,一个Consumer则接收契约的价值。我们需要区分出这两者的原因在于他们对Bundle的打包和版本控制有广泛的影响。

比如你从我手里买了一栋房子。在这个场景中,你是契约中的Consumer而我是Provider。令人惊讶的是这些角色并非对称的。例如,如果卖家在契约签订完毕后增加了一个额外的房间,买家是不会反对的(OK,你应该已经知道我想说什么了)。但是如果卖家移除了一个房间则买家会不满。一个Consumer可以期望向后兼容性,但是Provider会跟契约绑定。几乎Service契约中的所有改变都会需要Provider更新来提供新的功能。

因此一个Consumer相对来说离契约更远,它可能会在多个不同的Service契约中扮演Consumer的角色。一个Provider通常仅提供一个Service契约,同时它可以在其它契约中扮演Consumer角色。

因此在OSGi中,Provider的最佳实践是包含其Service API代码并且将它们Export出来。将API和Provider隔离开没有多大意义,因为Provider和API往往是一一对应的,这与Consumer会从API获得向后兼容性不同。将API放在实现Bundle之中会让事情变得更加简单。即便如此,也不要将它们放到同一个工程之中,因为编译会需要包含接口的实现的JAR。编译应该仅仅依赖带有API的JAR包,避免不小心依赖实现代码。

(译者注:这部分还需要推敲)

我们学到了什么?

在本章中,我们为Eval Service API创建了一个简单的Provider Bundle。我们在这个Bundle中Exprot了Eval Service API,同时也Import了它。

 

打赏一下
支付宝
微信
除非注明,博客文章均为原创,转载请标明文章地址
本文地址: http://www.javafxchina.net/blog/2018/02/osgi-enroute-0203_3/
百度已收录