OSGi 系列(七)之服务的监听、跟踪、声明等

1. OSGi 服务的事件监听

和 bundle 的事件监听类似,服务的事件监听是在服务注册、注销,属性被修改的时候,OSGi 框架会发出各种不同的事件供事先注册好的事件监听器处理。

1.1 服务的事件监听简介

服务的事件类型:

事件名称 描述 事件值
REGISTERED 服务被成功注册 1
MODIFIED 服务属性被修改 2
UNREGISTERING 服务被卸载 4
MODIFIED_ENDMATCH 服务属性被修改,且不再匹配当前监听 8

注册服务监听:

bundleContext.addServiceListener(listener)
bundleContext.addServiceListener(listener, filter)

移除服务监听:

bundleContext.removeServiceListener(listener)

1.2 实战演示

(1) 在 email-client 中修改 BundleActivator

@Override
public void start(BundleContext context) throws Exception {
//1. 获取163的服务
ServiceReference<?>[] refs = context.getServiceReferences(EmailService.class.getName(), "(vendor=163)");
if (refs != null) {
for (ServiceReference ref : refs) {
EmailService emailService = (EmailService) context.getService(ref);
System.out.println(emailService);
}
} context.addServiceListener(new ServiceListener() {
@Override
public void serviceChanged(ServiceEvent event) {
System.out.println(event.getSource() + " ==> " + event.getType());
}
});
}

测试结果如下:

可以看到 email-client 中注册的服务正在监听 email-service-163 服务状态改变

(2) filter 的使用与服务的获取类似

修改 email-service-163 中服务的注册:

@Override
public void start(BundleContext context) throws Exception {
Dictionary properties = new Hashtable<>();
properties.put("vendor", "163");
serviceRegistration = context.registerService(EmailService.class.getName(), new EmailServiceFactory(), properties); new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
;
}
Dictionary properties2 = new Hashtable<>();
properties.put("vendor", "456");
serviceRegistration.setProperties(properties2);
}).start();
}

修改 email-client 中服务的监听:

context.addServiceListener(new ServiceListener() {
@Override
public void serviceChanged(ServiceEvent event) {
System.out.println(event.getSource() + " ==> " + event.getType());
}
}, "(vendor=163)");

测试结果如下:

2. OSGi 服务跟踪器

如果服务消费者需要对服务进行跟踪,比如服务何时被注册,何时被注销等可以使用服务跟踪器类主要可以跟踪到服务的注册、注销、属性修改等三个操作。

// S:追踪的服务类型;T:具体的服务对象的类型
public interface ServiceTrackerCustomizer<S, T> {
public T addingService(ServiceReference<S> reference);
public void modifiedService(ServiceReference<S> reference, T service);
public void removedService(ServiceReference<S> reference, T service);
}
  • addingService 添加服务时
  • modifiedService 修改服务属性时
  • removedService 删除服务时

使用步骤

第一步:实现一个 ServiceTrackerCustomizer 的 ServiceTrackerCustomizerImpl 类

第二步:编写 Activator

public class Activator implements BundleActivator {

    private ServiceTracker<?, ?> tracker;

    public void start(BundleContext context) throws Exception {
ServiceTrackerCustomizer stc = new ServiceTrackerCustomizerImpl();
tracker = new ServiceTracker<>(context, 追踪类.class.getName(), stc);
tracker.open();
} public void stop(BundleContext context) throws Exception {
tracker.close();
}
}

3. OSGi 服务钩子

服务钩子(Service Hook)也是一种 OSGi 服务

  • EventListenerHook 服务的注册、注销、服务属性修改的时候,触发
  • FindHook 服务请求时触发
  • ListenerHook 服务监听增加或删除的时候触发

3.1 EventListenerHook

一个 Bundle 注册一个接口为 EventHook 的 Service 后,当 Framework 中有如 register,modify,unregister Service 操作时,这个勾子的 event 方法将会被调用。这个调用先于 ServiceEvent 的送出。EventHook 的 event 方法有两个参数

方法参数:

  • ServiceEvent event 表示将要发送的事件

  • Map listeners 为一个 BundleContext 对象的集合,表示所有会接收此 ServiceEvent 的 Bundle

3.2 ListenerHook

一个 Bundle 注册一个接口为 ListenerHook 的 Service 后,当 Framework 中有 Service Listener 加入或删除时,会调用这个勾子的 added 或 removed 方法,这两个方法都只有一个参数

有一点需要说明的是,对于 added 方法,它在这个 ListenerHook 注册入 Framework 后会立刻被调用,从而得到在这个勾子注册之前 Framework 中已经存在的 Service Listener 对象。

方法参数:

  • Collection listeners 为其内部类 ListenerHook.ListernerInfo 定义的 Listener 集合。表示刚加入 Framework 或刚从 Framework 中删除的一组 Listener。同样,我们也不可以向这个 Collection 增加元素。

有一点需要说明的是,对于 added 方法,它在这个 ListenerHook 注册入 Framework 后会立刻被调用,从而得到在这个勾子注册之前 Framework 中已经存在的 Service Listener 对象。

3.3 FindHook

一个 Bundle 注册一个接口为 FindHook 的 Service 后,当 Framework 中有如 getServiceReference 等操作时,这个勾子的 find 方法将会被调用。

方法参数为:

  • BundleContext context 表示调用 getServiceReference 方法的 BundleContext 对象
  • String name 表示打算寻找的 Class 的名称,null 表示寻找所有 Service
  • String filter 表示打算使用的 filter
  • boolean allServices true表示传入的 references 参数为 getAllServiceRefereces 的结果
  • Collection references 表示最终会返回给最开始调用如 getServiceReference 方法的 Bundle 的 Service Reference 集合

实际工作中 FindHook 比较常用,另外两种则用的很少,下面以 FindHook 演示服务钩子的使用方法:

public class Activator implements BundleActivator {
private ServiceRegistration<FindHook> serviceRegistration; public void start(BundleContext bundleContext) throws Exception {
serviceRegistration = bundleContext.registerService(FindHook.class, new MyFindHook(), null);
} public void stop(BundleContext bundleContext) throws Exception {
serviceRegistration.unregister();
}
}
class MyFindHook implements FindHook {
public void find(BundleContext context, String name, String filter, boolean allServices,
Collection<ServiceReference<?>> references) {
System.out.println("service hook is invoke "); for(ServiceReference<?> sf : references) {
if("APP".equals(sf.getProperty("from"))) {
references.remove(sf);
}
}
}
}

上面的例子会将属性 from=APP 的服务过滤掉,这样 getServiceReference 时就获取不到这个服务了,起到了一个权限控制的作用。

4. OSGi 声明式服务(Declarative Service)

详情查看 felix 声明式服务官方文档

4.1 传统的服务

传统方式下,我们注册服务都是在 bundle 的激活器 (Activator) 中使用 BundleContext.registerService() 方法完成的。而服务的获取需要通过 BundleContext.getServiceReference() 获取 ServiceReference 实例,进而使用 BundleContext.getService() 得到真正的服务实例。

这种方式虽然能够完成服务的发布与使用,但是有一定的不足,具体来讲:

  • 重复代码太多,太啰嗦。OSGi 的 bundle 是动态化的,伴随着 bundle 的安装和卸载,它所发布的服务也会动态地处于可用或不可用的状态,因此每次使用服务的时候,我们都需要借助 BundleContext 对象去服务注册中心查找,而不能通过一次查找,一劳永逸地持有服务对象的引用。尽管有 ServiceListener 和 ServiceTracker 帮助我们监听和跟踪服务的状态,但是总体而言这种方式较为繁琐且容易出错。

  • 影响启动时间,服务在激活器中注册时,需要实例化所有要发布的服务对象,因为激活器的start()方法是同步调用的,所以会影响到整个应用的启动时间。

  • 加大内存的占用,在激活器中注册服务时,我们需要实例化所有的服务对象,但是这些服务在应用运行期间,并不一定会用到,这在无形中加大了内存的占用。

  • API 依赖引起的平台侵入性。使用传统方式注册和使用服务,会用到大量的 OSGi API,从而产生与 OSGi 平台的耦合,如果要将代码复用到非 OSGi 场景之中,需要较多的重构工作。

4.2 声明式服务

OSGi 通过声明式服务(Declarative Service)以及 Blueprint 规范来解决这些问题。声明式服务基于组件模型理论,最早出现在 R4 compendium 规范之中,而 Blueprint 规范来源于 Spring Dynamic Modules 项目,最早出现于 R4.2 企业规范之中。

  • Declarative Service 是一个面向服务的组件模型,它制定的目的是更方便的在 OSGi 服务平台上发布,查找,绑定服务,对服务进行动态管理。

  • Declarative Service 采用服务组件的延迟加载以及组件生命周期管理的方式来控制对于内存的占用以及启动的速度,很好的解决了传统的OSGi 服务模型在开发和部署比较复杂的内存占用,启动慢等问题。

  • 在 Declarative Service 中,Componect 可以是 Service 的提供者和使用者。

  • Declarative Service 对服务组件的描述采用结果 XML 实现。

4.2 实战演示

(1) 新建 2 个 bundle,目录结构如下:

(2) 编写 declare-service 服务类

第一步:编写服务类

package com.github.binarylei;

public class RunableService implements Runnable {
@Override
public void run() {
System.out.println("service run...");
}
}

第二步:编写 declare.xml 配制文件:

<?xml version="1.0" encoding="UTF-8"?>
<component name="declare.service" immediate="true"
xmlns="http://www.osgi.org/xmlns/scr/v1.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.osgi.org/xmlns/scr/v1.2.0 https://osgi.org/xmlns/scr/v1.2.0/scr.xsd"> <implementation class="com.github.binarylei.RunableService" />
<property name="service.description" value="Declarative Service" />
<property name="service.vendor" value="Apache" />
<service>
<provide interface="java.lang.Runnable" />
</service>
</component>

第三步:将 declare.xml 配制文件添加到 META-INF/MANIFEST.MF 中

<!--Service-Component: declare.xml-->
<Service-Component>declare.xml</Service-Component>

(3) declare-client 与 declare-service 类似

第一步:编写服务类

package com.github.binarylei;

import java.util.Map;

public class MyClient {

    public void bind(Runnable service, Map<?, ?> properties) {
System.out.println("start...");
service.run();
} public void unbind(Runnable service, Map<?, ?> properties) {
System.out.println("end...");
service.run();
}
}

第二步:编写 declare.xml 配制文件:

<?xml version="1.0" encoding="UTF-8"?>
<component name="declare.client" immediate="true"
xmlns="http://www.osgi.org/xmlns/scr/v1.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.osgi.org/xmlns/scr/v1.2.0 https://osgi.org/xmlns/scr/v1.2.0/scr.xsd">
<implementation class="com.github.binarylei.MyClient" />
<reference interface="java.lang.Runnable" bind="bind" unbind="unbind"/>
</component>

第三步:将 declare.xml 配制文件添加到 META-INF/MANIFEST.MF 中

<!--Service-Component: declare.xml-->
<Service-Component>declare.xml</Service-Component>

(4) 运行 felix

要想在 felix 使用 Declarative Service ,先到 http://felix.apache.org/downloads.cgi 下载 SCR(Declarative Services)

测试一下哟!

OSGi 系列(七)之服务的监听、跟踪、声明等的更多相关文章

  1. Socket(TCP)客户端请求和服务端监听和链接基础(附例子)

    一:基础知识回顾 一: Socket 类 实现 Berkeley 套接字接口. Socket(AddressFamily, SocketType,ProtocolType) 使用指定的地址族.套接字类 ...

  2. Linux下启动Oracle服务和监听程序步骤

    Linux下启动Oracle服务和监听程序启动和关闭步骤整理如下: 1.安装oracle: 2.创建oracle系统用户: 3./home/oracle下面的.bash_profile添加几个环境变量 ...

  3. laravel进阶系列--通过事件和事件监听实现服务解耦

    简介 Laravel 事件提供了简单的观察着模式实现,允许你订阅和监听应用中的事件.事件类通常存放在 app/Events 目录. 监听器存放在 app/Listeners. 如果你在应用中没有看到这 ...

  4. linux上使用netstat查看当前服务和监听端口

    netstat这个命令常用在网络监控方面.利用这个命令,可以查看当前系统监听的服务和已经建立的服务,以及相应的端口.协议等信息. netstat参数说明 netstat参数虽然很多,但是常用的不多,主 ...

  5. linux用netstat查看服务及监听端口

    [root@localhost ~]# netstat -nlp netstat命令各个参数说明如下: -t : 指明显示TCP端口 -u : 指明显示UDP端口 -l : 仅显示监听套接字(所谓套接 ...

  6. nodejs中创建web服务,监听本地IP

    nodejs官网例子 var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {' ...

  7. Windows API 教程(七) hook 钩子监听

    茵蒂克丝 如何创建一个窗口 手动创建窗口的流程 实际代码 安装钩子 (Install hook) 钩子简介 SetWindowsHookEx 函数 设置监听[键盘]消息 设置监听[鼠标]消息 如何创建 ...

  8. cmd启动Oracle服务和监听服务

    启动数据库服务 net start oracleserviceorcl 启动数据库监听 lsnrctl start

  9. Linux下的启动oracle服务 启动监听 开放端口操作

    尝试登录oracle 使用root用户将没有sqlplus命令 [root@localhost ~]# sqlplus /nolog bash: sqlplus: 未找到命令...     [root ...

随机推荐

  1. web本质

    知识内容: 1.网络协议复习 2.模拟web 3.web本质总结 参考: http://www.cnblogs.com/wupeiqi/articles/5237672.html http://www ...

  2. 通过python给mysql建表

    一.python连接mysql from sqlalchemy import create_engine # 数据库数据 HOSTNAME = '127.0.0.1' # linux本地 PORT = ...

  3. sklearn的estimator

    estimator的工作流程 在sklearn中,估计器(estimator)是一个重要的角色,分类器和回归器都属于estimator.在估计器中有有两个重要的方法是fit和transform. fi ...

  4. zabbix监控windows磁盘空间

    监控windows磁盘空间,不是百分比. 当windows系统添加相应的windows模板后,会自动生成检测系统空间的监控项,在应用集(Filessystem)里面,Free disk space o ...

  5. 5. jdk路径配置

    path , classpath 的配置及作用? 1) PATH环境变量.作用是指定命令搜索路径,在i命令行下面执行命令如javac编译java程序时,它会到PATH变量所指定的路径中查找看是否能找到 ...

  6. CentOS Tomcat启动 Neither the JAVA_HOME nor the JRE_HOME environment variable is defined

    链接:http://blog.csdn.net/shangdiyisi/article/details/9477521 [bravoinfo@bravoinfo-hk-01 apache-tomcat ...

  7. Cookie的过期时间设置

    https://pan.baidu.com/s/1ibUQhLt6ZgVyhVM6mnrtHg 密码:9psc

  8. 常用HTTP状态码

    1.常用状态码介绍 在http响应协议中,我们通过HttpWatch抓包抓取到响应信息.其中响应首行中就包含一个状态码.状态码由三位数字组成,表示请求是否被理解或者被满足.HTTP响应状态码的第一个数 ...

  9. ubuntu 安装搜狗输入法

    from:http://blog.csdn.net/qq_21792169/article/details/53152700 最近开始学习linux 在安装输入法中遇到的一些问题,最终成功安装,也得益 ...

  10. pip cannot confirm SSL certificate: SSL module is not available

    centos6.8编译安装python2.7之后,使用pip报错:pip cannot confirm SSL certificate: SSL module is not available 解决方 ...