OSGi:声明性服务和注册


在前两篇文章中,我介绍了building an OSGi bundle以及architecture of a multi-bundle OSGi solution。该多捆绑包解决方案的一个关键特性是associated GitHub repository是OSGi声明性服务的使用。

OSGi声明性服务是处理实例化问题的OSGi方式:事实上,我们想要对接口进行编码,但是我们需要某种方式来实例化类,并且需要某种方式来提供接口的一些具体实例,以便我们的模块化应用程序的各个部分能够一起工作。

像大多数这种类型的解决方案一样,OSGi服务有三个部分:服务接口、服务注册和服务实现。这与以下设计完全相同:

  • Spring:服务接口被用作setter方法或构造函数的类型;服务注册表是应用程序上下文,服务实现是bean。
  • JDBC:服务接口是JDBC应用编程接口本身,服务注册中心是JDBC驱动程序管理器,服务实现是驱动程序。
  • JNDI/EJB:一旦我们进行了查找,服务接口就是我们所铸造的任何类型,上下文就是服务注册,EJB就是服务实现。

当然,所有这些使用相同设计模式的原因是因为这是可发现服务所需的最小数量的东西。但是作为一个关键,我们可以得出这样的结论,只要我们对普通的旧Java接口进行编码,并且将这些接口的实例化和注入分开,我们就可以编写在OSGi、Spring和Java Enterprise中运行良好的代码。(当然,像数据库访问、远程查找和事务这样的事情稍微打破了纯粹的“框架独立性”,但是它仍然适用于我们的许多代码。(

接口编码

对于我们的OSGi声明性服务示例,我们首先创建自己的普通旧Java接口:

public interface Greeter {
    String greet();

}


然后,我们编写一些使用该接口的“管理器”代码:

// ...
public String greet(String language) {
    // ... Fetch the right greeter
    return greeter.greet();
    // ...
}
// ...


注意,在我们的example,这些都在单独的包中,因此我们必须导出api包装来自interfaces捆绑并导入到manager捆绑,以便界面可见。

声明服务

为了创建这个服务的实际实例,我们编写了实现该接口的普通Java代码:

public class FrenchGreeter implements Greeter {
    private static final Logger LOGGER = LoggerFactory.getLogger(FrenchGreeter.class);
    @Override
    public String greet() {
        LOGGER.info("Le 'greeter' en francais!");
        return "Bonjour tout le monde!";
    }
}


到目前为止,这些例子都不包括任何OSGi特有的东西。为了在OSGi上下文中使用这个服务,我们需要做两件事:告诉服务注册中心关于实现的信息,并且有一种方法从注册中心查找我们需要的实现。

有几种方法可以在OSGi服务注册中心注册服务。

束激活器

首先,我们可以通过编程注册服务。为此,我们需要一个对服务注册中心的引用,以及一个告诉OSGi容器在它启动我们的包时为我们调用一些代码的方法。我们可以用一个bundle activator。如果我们编写一个实现这个接口的类,并告诉我们的Maven Bundle plugin以便在META-INF/MANIFEST.MF文件,那么OSGi将在我们的包启动之后和停止之前调用我们的类。我们可以使用BundleContext注册我们的服务是我们的责任。出于礼貌,我们还应该在服务被停止时取消注册。

它可能看起来像这样(在开始端):

public class Activator implements BundleActivator {
    public void start(BundleContext context) throws Exception {
        Dictionary<String,String> props = new Dictionary<>();
        props.put("language", "fr");
        context.registerService(Greeter.class.getName(),
            new FrenchGreeter(), props);
    }
    // ... stop
}


这种方法的好处是非常清楚。不利的一面是,我们正在编写样板代码,这些代码在每个带有服务实现的包中都是OSGi特有的。

蓝图

OSGi还支持XML配置,这与Spring框架所支持的非常相似。如果我们将一个XML文件放入捆绑包JAR文件中的OSGI-INF/蓝图中,那么当我们的捆绑包启动时,OSGI容器将自动解析它。

在未来的文章中,我将更详细地展示一个蓝图XML示例,但这里有一个简单的介绍:

    <bean id="frenchGreeter" class="org.anvard.karaf.greeter.french.FrenchGreeter">
    </bean>
    <service id="frenchGreeterService" ref="frenchGreeter"
        interface="org.anvard.karaf.greeter.api.Greeter">
        <service-properties>
            <entry key="language" value="fr" />
        </service-properties>
    </service>


这样做的好处是它避免了样板Java,但是它包含了样板XML。如果我们不使用蓝图XML,我们可能不想仅仅为了这个目的而引入它。

服务组件运行时

由于我们的示例使用的是Apache Karaf,它是基于Apache Felix的OSGI功能构建的,因此我们有SCR对我们可用,包括Java注释。这意味着我们可以注释我们的实现类,并让费利克斯自动发现它并将其注册为服务。

生成的代码如下所示:

@Component(immediate = true)
@Service
@Property(name="language", value="fr")
public class FrenchGreeter implements Greeter {

    private static final Logger LOGGER = LoggerFactory.getLogger(FrenchGreeter.class);

    @Override
    public String greet() {
        LOGGER.info("Le 'greeter' en francais!");
        return "Bonjour tout le monde!";
    }
}


这很好,因为它非常独立。我们确实要为代码中费利克斯特有的注释付出代价,所以对于是否使用OSGi以及使用哪个容器,我们不再保持中立。

包扎

当然,Java注释本身什么也做不了,我们还需要有一种方法在服务注册时查找它。下一次我将涵盖这两个主题。