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


    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());
            }
        });
    }
    

    测试结果如下:

    图7.1 服务监听

    可以看到 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)");
    

    测试结果如下:

    图7.2 服务监听

    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,目录结构如下:

    图7.4.1 目录结构

    (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)

    图7.4.2 Declarative Services下载

    测试一下哟!

    图7.4.3 Declarative Services测试

  • 相关阅读:
    关于音视频同步
    redis JedisConnectionException: Could not get a resource from the pool
    ping指定IP的指定端口号
    如何查看端口号是否被占用
    mongodb增删改查基础语法
    mongodb重置密码
    本地MongoDB服务开启与连接本地以及远程服务器MongoDB服务
    MongoDB服务的安装与删除
    可视化
    Elasticsearch下载安装
  • 原文地址:https://www.cnblogs.com/binarylei/p/8542060.html
Copyright © 2020-2023  润新知