OSGi 系列(六)之服務的使用


OSGi 系列(六)之服務的使用

1. 為什么使用服務

  • 降低服務提供者和服務使用者直接的耦合,這樣更容易重用組件
  • 隱藏了服務的實現細節
  • 支持多個服務的實現、這樣你可以互換這實現

2. 服務的使用

2.1 服務的注冊

bundle 通過在框架的服務注冊中心注冊一個服務對象來發布一個服務。安裝在 OSGi 環境下的其它 bundle 就可以訪問到在框架中注冊的服務對象。

bundle 通過使用 BundleContext.registerService,在框架中注冊一個服務對象:

registerService(String, Object, Dictionary)   //用於一個服務接口的服務注冊
registerService(String[], Object, Dictionary) //用於多個服務接口的服務注冊
  • String 表示服務的接口
  • Object 表示服務的實現類
  • Dictionary 表示服務屬性
  • 調用之后,返回 ServiceRegistration 對象

2.2 服務的銷毀

ServiceRegistration.unregister() 

2.3 服務的屬性

屬性 類型 常量 描述
objectClass String[] OBJECTCLASS objectClass 屬性包含了注冊到框架中的服務對象所有實現的接口的集合。這個屬性必須由框架自動設置
service.id Long SERVICE_ID 每一個注冊了的服務對象都由框架分配了一個惟一的service.id。將這個標志數字添加到服務對象的屬性中。框架給每一個注冊的服務對象分配一個惟一的標志值,這個值要比原來分配的任何值要大,也就是說是遞增分配的。
service.pid String SERVICE_PID service.pid屬性是可選的,標記了服務對象的持久惟一標記。
service.ranking Integer SERVICE_RANKING 服務的排行,當有多個服務的時候,會返回service.ranking值最大的那個服務
service.description String SERVICE_DESCRIPTION service.description屬性用於文檔性的描述,這個屬性是可選的
service.vendor String SERVICE_VENDOR 這是一個可選屬性,描述服務對象的開發商信息

2.4 服務的查找

查找服務時要分兩步:

bundleContext.getServiceReference() //1. 獲取 ServiceReference 對象
bundleContext.getService()          //2. 獲取真實的服務對象

查找單個服務:

bundleContext.getServiceReference() 要么返回 null,要么返回一個服務。如果有多個服務匹配,也只會返回一個服務。

  • 找 service.ranking 屬性最高的。如果注冊時為指定該屬性,則默認值為0
  • 找 service ID 屬性最小的。也就是最先注冊的服務。

查找多個服務:

bundleContext.getServiceReferences(Clazz clazz, String filter)
bundleContext.getServiceReferences(String clazz, String filter)

第二個參數接受標准的LDAP過濾字符串。示例:

屬性匹配:(vendor=Apache)、(count>3)
通配符:(vendor=Apache*)
判斷某個屬性是否存在:(vendor=)
條件非:(!(vendor=Apache))
條件與:(&(objectClass=com.edu.osgi.user.IUserService)(type=1))
條件或:(|(type=1)(type=2))

3.實戰演示

3.1 新建 4 個 bundle,目錄結構如下:

圖6.1 目錄結構

3.2 email-api 為接口

package com.github.binarylei.email.api;

public interface EmailService {
    void sendEmail(String to, String title, String content);
}

注意: email-api 要將接口暴露出去,配制如下:

<Import-Package>org.osgi.framework</Import-Package>
<Export-Package>com.github.binarylei.email.api</Export-Package>

3.3 email-service-139 實現

package com.github.binarylei.email.service;

import com.github.binarylei.email.api.EmailService;

public class EmailService139 implements EmailService {

    public void sendEmail(String dest, String title, String content) {
        System.out.println("139 email send. dest=" + dest + ",title=" + title + ",content=" + content);
    }
}

BundleActivator 如下:

package com.github.binarylei.email.internal;

import com.github.binarylei.email.api.EmailService;
import com.github.binarylei.email.service.EmailService139;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;

public class Activator implements BundleActivator {
    ServiceRegistration<EmailService> serviceRegistration;

    @Override
    public void start(BundleContext context) throws Exception {
        serviceRegistration = context.registerService(EmailService.class, new EmailService139(), null);
    }

    @Override
    public void stop(BundleContext context) throws Exception {
        serviceRegistration.unregister();
    }
}

注意: email-service-139 要將引入 email-api 接口,配制如下:

<Import-Package>org.osgi.framework,com.github.binarylei.email.api</Import-Package>
<Bundle-Activator>com.github.binarylei.email.internal.Activator</Bundle-Activator>

3.4 email-service-163 實現

與 email-service-139 類似

3.5 email-client 服務的使用

package com.github.binarylei.email.internal;

import com.github.binarylei.email.api.EmailService;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;

public class Activator implements BundleActivator {
    @Override
    public void start(BundleContext context) throws Exception {
        //1. 獲取所有的服務
        ServiceReference<?>[] refs = context.getAllServiceReferences(EmailService.class.getName(), null);
        if (refs != null) {
            for (ServiceReference ref : refs) {
                EmailService emailService = (EmailService) context.getService(ref);
                emailService.sendEmail("binarylei@qq.com", "OSGi", "OSGi Service");
            }
        }
        //2. 獲取單個服務
        ServiceReference<?> ref = context.getServiceReference(EmailService.class.getName());
        if (ref != null) {
            EmailService emailService = (EmailService) context.getService(ref);
            emailService.sendEmail("binarylei@qq.com", "OSGi", "OSGi Service");
        }
    }

    @Override
    public void stop(BundleContext context) throws Exception {

    }
}

注意: email-client 要將引入 email-api 接口,配制如下:

<Import-Package>org.osgi.framework,com.github.binarylei.email.api</Import-Package>
<Bundle-Activator>com.github.binarylei.email.internal.Activator</Bundle-Activator>

3.6 felix 測試

將這 4 個 bundle 拷貝到 felix-framework-5.6.10/bundle 下,啟動 felix

圖6.2 服務的測試

4. 服務的屬性使用

4.1 服務添加屬性

分別給 email-service-163 和 email-service-139 添加屬性 vendor

@Override
public void start(BundleContext context) throws Exception {
    Dictionary properties = new Hashtable<>();
    properties.put("vendor", "163"); // 139
    serviceRegistration = context.registerService(EmailService.class, new EmailService163(), properties);
}

4.2 根據服務屬性獲取對應的服務

email-client 獲取屬性

@Override
public void start(BundleContext context) throws Exception {
    // 根據屬性獲取163的服務
    ServiceReference<?>[] refs = context.getServiceReferences(EmailService.class.getName(), "(vendor=163)");
    if (refs != null) {
        for (ServiceReference ref : refs) {
            EmailService emailService = (EmailService) context.getService(ref);
            emailService.sendEmail("binarylei@qq.com", "OSGi", "OSGi Service");
        }
    }
}

5. 服務工廠

使用傳統的方式獲取的服務都是單例的,同一個服務,不管是在同一個 bundle, 還是在不同 bundle 中間獲取。

5.1 測試單例

將 email-client-1.0.0.jar 復制一份 email-client-2.0.0.jar,修改 META-INF/MANIFEST.MF 的 Bundle-Version 為 2.0.0

圖6.3 測試單例

5.2 ServiceFactory

org.osgi.framework.ServiceFactory,使用服務工廠好處:

  1. 有時 service 需要知道是哪個 bundle 在使用它。例如 logger 服務,它需要在日志中記錄是哪個 bundle 調用它的。
  2. 延遲初始化 Service
  3. 這對消費者是透明的,它不能知道提供服務的是普通 Service 還是 ServiceFactory
  4. 可以創建多種服務,根據參數 ServiceRegistration 來判斷

5.3 實例

(1) email-service-163 添加 EmailServiceFactory 類:

package com.github.binarylei.email.service;

import com.github.binarylei.email.api.EmailService;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;

public class EmailServiceFactory implements ServiceFactory<EmailService> {

    @Override
    public EmailService getService(Bundle bundle, ServiceRegistration<EmailService> registration) {
        return new EmailService163();
    }

    @Override
    public void ungetService(Bundle bundle, ServiceRegistration<EmailService> registration, EmailService service) {
    }
}

(2) 修改 Activator 類:

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

(3) 更新 email-service-163,重啟 email-client-1.0.0.jar 和 email-client-2.0.0.jar ,結果如下:

圖6.4 測試多例

6. OSGi 服務

核心服務:包管理、啟動級別、權限管理、URL處理

compendium 服務:

  • LOG Service(日志服務)
  • HTTP Service(注冊servlet和資源)
  • Configuration Admin(配置管理)
  • Event Admin(事件通知)
  • Declarative Services(定義輕量級的面向服務的組件模型)
  • Blueprint(一個類似 IOC 容器的實現)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM