前言

作为OSGi框架中最上面的一层,服务层带给了我们更多的动态性,并且使用了大家或多或少都曾了解过的面向服务编程模型,其好处是显而易见的。

1 什么是服务

简单的说,服务就是“为别人所做的工作”,比如两个对象互相调用方法,那么被调用者就是在为调用者做工作。

那么如何将服务和一次普通的方法调用区别开来呢?其实一个服务可以看作是在服务的提供者和使用者之间的一个契约。使用者一般不关心其实现的细节,甚至连谁提供的都不想知道,只要满足这个契约(服务应该提供什么功能,满足什么格式)就好了。使用服务的过程也包含了发现服务和达成协议的形式,也就是说我们需要通过服务的标志性特征来找到对应的服务。

其实,Java的接口可以说提供了一种契约的提供方式,我们能通过修改classpath来替换接口的不同的具体实现。但是OSGi能够为找到服务提供更加高层的抽象并且在应用的执行时动态替换服务的实现,这些特性在稍后将会提到。

2 为什么要使用服务

服务(更准确的说是面向服务的编程模型)给予了我们一种即插即用的软件开发方法,意味着更强的灵活性。这种灵活性是如何体现的呢?

  • 低耦合,利于组件复用:通过服务我们能够清晰的定义组件的边界,从而将服务的使用者和提供者之间的耦合度降到很低。
  • 更加强调接口而不是在具体的实现:Java的interface提供了一种形式的契约,在OSGi的服务层中充分利用了接口特性的优势,这样使得无论有多少个类实现了这个接口,只要满足对这个接口的功能需要,就可以被使用者使用。
  • 对于依赖有比较清晰地描述:单是接口本身只包含服务的名称和参数类型,并不足以清楚的描述服务的所有特征,而面向服务的编程模型中要求了更加清晰的描述使得这些特征能够唯一标识一个服务。
  • 支持对多个竞争实现(多个实现同一个接口的类)的筛选:服务框架会帮助你记录服务的元数据,可以据此帮助使用者查询和筛选服务,使用者更加的主动,这一点和传统的依赖注入框架不同。

    3 什么时候使用/不使用服务

  • 可以考虑使用的时候: 
    当你常常想要对主要的组件进行替换和升级而不想重写应用的其他部分,或者当你在程序中想要查找和选择不同的接口实现的时候。

  • 不应该使用的时候: 
    服务的加入和维护往往持续的增加框架的开销,所以当你开发的代码对性能需求敏感的时候,不要使用服务。 
    显然它也不应该出现在两段经常一起开发和更新的紧耦合代码之间,除非你真的需要在自己写的代码中得到“一个接口多个实现”的选择权。

最后,如果不确定是否应该使用服务,可以先用面向接口的方式实现,这至少是和使用服务很接近了,并且它也能简化你的开发。如果哪天你下定决心想把他们移植到服务层了,在面向接口的基础上这个一直工作也会变得非常容易。

4 OSGi服务层基础

首先,需要说明的是,OSGi的服务层除开前面提到的面向服务的编程模型,还有一个区别于其他很多类似模型的特性,那就是服务的完全动态性。也就是说,当一个bundle发现并开始使用OSGi中的一个服务了以后,这个服务可能在任何的时候改变或者是消失。这方面的内容将在以后更加深入的讲解。 
OSGi框架有一个中心化的注册表,这个注册表遵从publish-find-bind模型:

一个提供服务的bundle可以发布POJO作为服务的实体;一个使用服务的bundle可以通过这个注册表找到和绑定服务。 
我们可以通过BundleContext接口来完成上述的工作,下面就是含有这方面功能的接口列表:

public interface BundleContext {

  ...void addServiceListener(ServiceListener listener,String filter) throws InvalidSyntaxException;

  void addServiceListener(ServiceListener listener);

  void removeServiceListener(ServiceListener listener);

  ServiceRegistration registerService(String[] clazzes,Object service,Dictionary properties);

  ServiceRegistration registerService(String clazz,Object service,Dictionary properties);

  ServiceRegistration[] getServiceReferences(String clazz,String filter)throws InvalidSyntaxException;

  ServiceRegistration[] getAllServiceReferences(String clazz,String filter)throwsInvalidSyntaxException;

  ServiceReference getServiceReference(String clazz);

  Object getService(ServiceReference reference);

  boolean ungetService(ServiceReference reference);

...}

4.1 发布服务

为了让别的bundle能发现这个服务,你必须在发布它之前对其进行特征描述。这些特征包括接口的名字(可以是名字的数组),接口的实现,和一个可选的java.util.Dictionary类型的元数据信息。下面是一个例子:

String[] interfaces =newString[]{StockListing.class.getName(),StockChart.class.getname()};

Dictionary metadata =new Properties();
metadata.setProperty(“name”,“LSE”);
metadata.setProperty(“currency”,Currency.getInstance(“GBP”));
metadata.setProperty(“country”,“GB”);

ServiceRegistration registration = bundleContext.registerService(interfaces,new LSE(), metadata);

在上面的代码中,我们得到了ServiceRegistration对象,我们可以用这个对象来更新服务的元数据: 
registration.setProperties(newMetadata);

也可以直接就把这个服务移除: 
registration.unregister();

需要注意的是这个对象不能和其他Bundles共享,因为它和发布服务的bundle的生命周期相互依存,也就是说,如果这个bundle已经不在框架执行环境中存在,那么这个对象也不应该存在了,“皮之不存毛将焉附”就是这个道理。

试想如果这个ServiceRegistration共享给了其他的bundle(具体的说就是其他bundle中存在对这个对象的引用),那么发布服务的那个bundle即使被移除了,由于其他bundle中的引用依然存在,那么垃圾处理机制不会抹去这个对象,这样不但于理不合,而且实际上这个对象也是不可用的,因为这个对象所依存的bundle已经不在了。

代码中的参数new LSE()是一个POJO,这个对象不需要实现任何OSGi类型或者使用标注,只要满足服务约定(这里就是接口)就可以了。

此外,如果在删除发布的服务之前bundle停止了,框架会帮助你删除这些服务。

4.2 发现和绑定服务

上一小节我们说明了如何描述和发布一个服务,那么现在我们可以根据服务约定从注册表中找到正确的服务。 
下面是发现服务并获得其引用的接口:

ServiceReference reference =  bundleContext.getServiceReference(StockListing.class.getName());

这是根据实现的接口名称获得的服务,也是最简单的方法。

注意这里的reference是服务对象的间接引用,可是为什么要用间接引用而不直接返回那个实际的服务对象呢?实际上是为了将服务的使用和服务的实现进行解耦,将服务注册表作为两者的中间人,达到跟踪和控制服务的目的,同时还可以在服务消失了以后通知使用者。

这个方法的返回类型是ServiceReference,它可以在bundle之间互享,因为它和使用服务的bundle的生命周期无关。

4.2.1 选择最适合你的服务

在getServiceReference这个方法中,选择service的默认优先级是先选择service.rank最高的,在rank相等的情况下选择最早在框架中注册的。除了这个默认的规则,我们还可以在 getServiceReferences中通过添加过滤参数(作为调用该方法的第二个参数)来做一些筛选。

ServiceReference[] references = bundleContext.getServiceReferences(StockListing.class.getName(),“(&(currency=GBP)(objectClass=org.example.StockChart))”);

在这里的匹配参数是一个字符串,这个字符串的格式属于LDAP查询格式,在RFC 1960标准中有完整的描述。

上面的字符串中等号左边的内容就是前面提到的元数据(Dictionary)中的左值,通过这个左值对应的右值来与服务所带有的元数据进行匹配。一些简单的匹配示例如下: 
属性匹配: 
(name=John Smith) 
(age>=20) 
(age<=65) 
模糊匹配: 
(name~=johnsmith) 
通配符匹配: 
(name=Jo*n*Smith*) 
判断某个属性是否存在: 
(name=
条件与: 
(&(name=John Smith)(occupation=doctor)) 
条件或: 
(|(name~=John Smith)(name~=Smith John)) 
*
条件非: ** 
(!(name=John Smith))

4.2.2 绑定和使用服务

在你发现了服务之后,使用服务之前,你必须从注册表中绑定实现的服务。

StockListing listing =(StockListing) bundleContext.getService(reference);

这个方法返回的POJO实例和之前在注册表中注册的实例是同一个。

每次使用getService方法的时候,注册表会将对应服务的使用次数加1,同时会记录谁在使用这个服务。所以当你不在想使用这服务的时候,最好告诉注册表一声。

bundleContext.ungetService(reference);
listing =null;

给出第二条语句的目的并不是为了通知注册表,而是为了让java的垃圾处理机制安全运作。因为这里我们用了一个局部变量listing来作为服务对象的一个引用,(不妨假设listing是最后一个引用这个对象的变量),如果我们不设为null,那么在这个listing消亡之前,那个服务对象有可能不会被垃圾处理掉(即使在程序逻辑上这个服务对象已经是“垃圾”了),这可能会引发一些问题。

不过,这种用局部变量引用服务对象的方式本来就不对。一般来说,还是应该在每次需要使用的时候临时从ServiceReference获得,并且要考虑到这个服务在任何时候都有可能消亡。

OSGi-入门篇之服务层(03)的更多相关文章

  1. 转:OSGi 入门篇:生命周期层

    OSGi 入门篇:生命周期层 前言 生命周期层在OSGi框架中属于模块层上面的一层,它的运作是建立在模块层的功能之上的.生命周期层一个主要的功能就是让你能够从外部管理应用或者建立能够自我管理的应用(或 ...

  2. 转:OSGi 入门篇:模块层

    OSGi 入门篇:模块层 1 什么是模块化 模块层是OSGi框架中最基础的一部分,其中Java的模块化特性在这一层得到了很好的实现.但是这种实现与Java本身现有的一些模块化特性又有明显的不同. 本文 ...

  3. [原创]Linq to xml增删改查Linq 入门篇:分分钟带你遨游Linq to xml的世界

    本文原始作者博客 http://www.cnblogs.com/toutou Linq 入门篇(一):分分钟带你遨游linq to xml的世界 本文原创来自博客园 请叫我头头哥的博客, 请尊重版权, ...

  4. Tesseract——OCR图像识别 入门篇

    Tesseract——OCR图像识别 入门篇 最近给了我一个任务,让我研究图像识别,从我们项目的screenshot中识别文字信息,so我开始了学习,与大家分享下. 我看到目前OCR技术有很多,最主要 ...

  5. Erlang Rebar 使用指南之一:入门篇

    Erlang Rebar 使用指南之一:入门篇 全文目录: https://github.com/rebar/rebar/wiki 本章原文: https://github.com/rebar/reb ...

  6. Java工程师学习指南 入门篇

    Java工程师学习指南 入门篇 最近有很多小伙伴来问我,Java小白如何入门,如何安排学习路线,每一步应该怎么走比较好.原本我以为之前的几篇文章已经可以解决大家的问题了,其实不然,因为我之前写的文章都 ...

  7. (转载)从Java角度理解Angular之入门篇:npm, yarn, Angular CLI

    本系列从Java程序员的角度,带大家理解前端Angular框架. 本文是入门篇.笔者认为亲自动手写代码做实验,是最有效最扎实的学习途径,而搭建开发环境是学习一门新技术最需要先学会的技能,是入门的前提. ...

  8. Hadoop生态圈-Hive快速入门篇之HQL的基础语法

    Hadoop生态圈-Hive快速入门篇之HQL的基础语法 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本篇博客的重点是介绍Hive中常见的数据类型,DDL数据定义,DML数据操作 ...

  9. 【Android开发日记】之入门篇(十二)——Android组件间的数据传输

    组件我们有了,那么我们缺少一个组件之间传递信息的渠道.利用Intent做载体,这是一个王道的做法.还有呢,可以利用文件系统来做数据共享.也可以使用Application设置全局数据,利用组件来进行控制 ...

随机推荐

  1. - Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as <script>, as they will not be parsed.

    vue.js报错如下: - Templates should only be responsible for mapping the state to the UI. Avoid placing ta ...

  2. linux 简易启动脚本

    #/bin/bash pid=`ps -ef | grep 'testDemo' | grep -v grep |awk '{print $2}'` in start) nohup java -j t ...

  3. 【Spring】XML配置整合Mybatis

    注意:项目开发使用了mybatis的mapper代理! 首先是mybatis自己的配置文件,被spring整合之后,只有typeAliases存在了,其他都整合在了spring-mybatis.xml ...

  4. WeQuant交易策略—简单均线

    简单双均线策略(Simple Moving Average) 策略介绍简单双均线策略,通过一短一长(一快一慢)两个回看时间窗口收盘价的简单移动平均绘制两条均线,利用均线的交叉来跟踪价格的趋势.这里说的 ...

  5. windows 10 安装tensorflow

    人工智能一浪接一浪,随着谷歌公布tensorflow源码,尤其是支持windows 10平台的python3.5以上版本,更是让更多人都想用windows操作tensorflow. 第一次安装,也不知 ...

  6. angular中封装fancyBox(图片预览)

    首先在官网下载最新版的fancyBox(一定要去最新网站,以前依赖的jquery版本偏低),附上链接:http://fancyapps.com/fancybox/3/ 然后在项目中引用jquery,然 ...

  7. Selenium 学习笔记(一)

    selenium 学习整理 初学者,如果有不当得地方请指出,非常感谢. 准备事项: 1. Python 安装包 安装Python,并勾选添加环境变量. 安装完成后,打开dos窗口,输入python,看 ...

  8. [2014-11-02]为EF6+Mysql+CodeFirst启用Migration

    刚为一个EF6 CodeFirst项目启用了Migration,记几个注意点. 启用方法 在Nuget控制台使用以下命令启用Migration Enable-Migrations #此时生成当前数据库 ...

  9. [2014-08-24]为 Xamarin Studio 创建的 Asp.Net Mvc 项目配置 gitignore

    今天在尝试 Mac 下使用 Xamarin Studio (以下简称XS) 开发 Asp.Net Mvc 项目,发现XS没启用版本控制,故自己去命令行下使用 git init,想到需要一个.gitig ...

  10. 面试题收集---grep和find的区别

    grep是通过文件找内容 find 是通过内容找文件 Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来. 而linux下的find, 在目录结构 ...