【OSGi】Service

概念

  • 服务

服务就是:为别人完成的工作。目的:是让别人为你工作,而不是试图自己做一切事情。

和方法调用的区别:服务是指提供者及其使用者之间的一个契约,使用者不关心服务的具体实现,甚至不关心是谁提供的,只要遵守约定的契约即可。

面向服务的设计方式鼓励用即插即用的方式来进行软件开发,这意味着在开发、测试、部署、维护过程中有更大的灵活性。

  • 接口+依赖注入

Java中一般是基于接口编程的,然而除非我们知道具体的接口实现类,否则我们无法创建一个接口实例。

传统Java可以通过依赖注入(Dependency Injection, DI)解决这个问题。

缺点:

  • 依赖注入是静态的,注入只能发生一次,一般是在应用启动时注入,在应用关闭前就一直不能改变了。
  • sensitive dependence on start-up ordering。
  • 另外不能指定元数据(属性)。

OSGi则可以通过动态服务解决这个问题。可以支持多个服务实现,使用时根据元数据筛选出所需要的服务实现。

发布服务


 
  • 注册服务

注册服务是与OSGi交互的一种方式,所以必须通过BundleContext来完成。
 
public class WelcomeMailboxActivator implements BundleActivator {
   public void start(BundleContext context) throws Exception {
       context.registerService(Mailbox.class.getName(), new FixedMailbox(), null) ; //1
   }
   public void stop (BundleContext context) throws Exception {
   }
 }

BundleContext.registerService()

传入的三个参数分别是:interface name、service object、service properties。第一个参数可以传入String[],将服务注册到多个接口名下。

启动上述bundle后,执行services命令就能查看当前已注册的所有服务:

osgi > services
. . .
{ org . osgi . book . reader . api . Mailbox }={ service . id =24}
Registered by bundle : welcome_mailbox_0 . 0 . 0 [ 2 ]
No bundles using service .
. . .

 

  • 移除服务

上例在start()方法中注册了一个服务,是否应该在stop()中移除这个服务?

一般来说是不需要的,因为当bundle停止时,OSGi框架会自动移除相关联的服务。当然也可以手工移除:

serviceRegistration.unregister();
 

serviceRegistration是BundleContext.registerService()返回的对象。

 

使用服务

  • 查找服务

public void start ( BundleContext context ) throws Exception {
  this.context = context;
  printMessageCount( );

 
private void printMessageCount() throws MailboxException {
  ServiceReference ref = context.getServiceReference (Mailbox.class.getName());  // 1
  if (ref != null) {
     Mailbox mbox = (Mailbox) context.getService(ref); // 2
     if (mbox != null) {
       try {
         int count = mbox.getAllMessages().length; // 3
         System.out.println("There are " + count + " messages");
       } finally {
         context.ungetService(ref); // 4
       }
     }
 }

1、BundleContext.getServiceReference()

getServiceReference()参数是接口名称,获取该接口名称下的服务引用

2、BundleContext.getService()

如果服务引用不为null,则用getService()获取实际的服务对象。

获取到实际服务对象后,仍然要检查是否为null,因为可能此时服务变为不可用,就会返回null。

3、调用服务方法

mbox.getAllMessages().length。和调用普通Java对象的方法一样。

4、BundleContext.ungetService()释放服务

ungetService()的参数是服务引用,告诉框架我们不再用这个服务了。框架会记住有几个bundle在使用某个服务,如果数目为0,则提供服务的bundle停止时,可以安全地移除该服务。

ungetService()要放在finally块中!保证即使抛出异常,仍然会执行。

——注意这一步是必须的!不像serviceRegistration.unregister()

 

  • 服务属性

【注册服务时】

在注册服务时,除了接口名,我们还可以为服务附加metadata,即服务属性。

registerService()方法的第三个参数是一个java.util.Dictionary对象(可传入其子类java.util.Properties),表示服务属性,用于描述该服务;在查找服务时,可用于过滤。

例:

public void start( BundleContext context) throws Exception {
  Properties props = new Properties();
  props.put(Mailbox.NAME_PROPERTY, "welcome");
  context.registerService(Mailbox.class.getName(), new FixedMailbox(), props);
}

启动bundle后,通过services命令可以看到这些服务属性:

osgi > services
. . .
{ org . osgi . book . reader . api . Mailbox }={ mailboxName=welcome , service . id =27}
Registered by bundle : welcome_mailbox_0 . 0 . 0 [ 2 ]
No bundles using service .
. . .

service.id是一个内置的服务属性,其取值由框架分配。

其实前面的{org . osgi . book . reader . api . Mailbox }也是一个内置服务属性:objectClass

内置属性名可以查看org.osgi.framework.Constants。

 

【查找服务时】

ServiceReference[] refs = bundleContext.getServiceReferences(Mailbox.class.getName(),
      "& (mailboxName=welcome) (objectClass=...)");

第二个参数接受标准的LDAP过滤字符串

查找服务时可以通过ServiceReference.getProperty()查询某个属性的值;还可以通过getPropertyKeys()查询所有的属性名。

  • 服务匹配规则

可以看到,ServiceReference.getService()要么返回null,要么返回一个服务。如果有多个服务匹配,也只会返回一个服务。那么是如何匹配的呢?OSGi会遵循两个规则:

  1. service.ranking 属性最高的。如果注册时为指定该属性,则默认值为0
  2. service ID属性最小的。也就是最先注册的服务。
  • 查找服务为什么要分两步

上面看到查找服务时要分两步:

  1. 通过BundleContext.getServiceReference()获取ServiceReference对象
  2. 通过BundleContext.getService()获取真实的服务对象

为什么要这么麻烦呢?其中一个原因时,有时我们并不真的想马上获取服务对象,而是想获取服务的属性。——这通过ServiceReference就能办到。

另外,ServiceReference是一个轻量对象,可以在bundle直接随意传递。而如果直接传递服务对象,则OSGi框架就不知道到底是哪个bundle使用了服务。所以当我们需要让其他bundle使用service来做什么事的时候,我们应当传一个ServiceReference给那个bundle,由它自己来调用getService和ungetService。

  • 注意

能否在start()里查找服务,然后把服务对象存到一个实例变量中,在其他方法中直接使用该服务对象?

——NO!在其他方法使用服务对象时,可能服务已被撤销。

那么存储服务引用呢?

——NO!在其他方法使用服务引用.getService(),如果服务正好被撤销,则找不到服务了。即便服务后来又被注册。

 

监听服务

Whenever a service is either registered or unregistered, the framework publishes a ServiceEvent to all registered ServiceListeners.

【场景】假设我们要在DataSource服务启动时,启动一个DbMailbox;当DataSource移除时同时也移除DbMailbox。应该如何实现?

  • 监听新Service

【解决】可用ServiceListener:

public void start(BundleContext context) throws Exception {
  this.context = context;
  String filter = "(" +Constants.OBJECTCLASS  + "=" + DataSource.class.getName() + ")";
  context.addServiceListener(new DbListener (), filter );
}

class DbListener implements ServiceListener {
 
  DataSource db;

  public void serviceChanged(ServiceEvent event) {
      switch () {
          case ServiceEvent.REGISTERED:
               this.db = (DataSource) context.getService(event.getServiceReference());
               ... 使用db 注册DbMailbox.....
               break;
          case ServiceEvent.UNREGISTERED:
               this.db = null;
               break;
         ....
      }
  }
}

【问题】只会监听到最新注册的Service;对已有的Service无法处理。

  • 扫描已有Service + 监听新Service

【解决】先注册监听;然后扫描已有的Service,发送伪事件:

public void start(BundleContext context) throws Exception {
   this.context = context;
   this.listener = new DbListener();
   synchronized(listener) {
         // 1. 注册监听
         String filter = "(" +Constants.OBJECTCLASS  + "=" + DataSource.class.getName() + ")";
         context.addServiceListener(new DbListener (), filter );

          //2. 扫描已有的service
          ServiceReference[] refs = context.getServiceReferences(null, filter);
          if (refs != null) {
               for (ServiceReference ref : refs) {
                    this.listener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, ref)); //发送伪事件
               }
          }
          
   }
}

在监听器中应该保存所有的Service,而不仅仅是最新的Service:

class DbListener implements ServiceListener {
 
  SortedSet<ServiceReference> refs = new TreeSet<ServiceReference>();

  public void serviceChanged(ServiceEvent event) {
      switch () {
          case ServiceEvent.REGISTERED:
               refs.add(event.getServiceReference());
               ... 使用getService() 注册DbMailbox.....
               break;
          case ServiceEvent.UNREGISTERED:
               refs.remove(event.getServiceReference());
               break;
         ....
      }
  }
}
 
 
public synchronized DataSource getService() {
    if (refs.size() > 0) {
        return (DataSource) context.getService(refs.last());
    }
    return null;
}
  • 注意

【注意1】能否先扫描已有service,再注册监听?

——NO!如果在扫描之后、监听注册完前,有新的service注册,则这个service会被忽略!

而如果是先注册再扫描,则这个service会被监听到2次。重复总比忽略好!

 

【注意2】OSGi框架以同步的方式来传递服务事件,所以应该确保监听器内方法尽可能短,不要产生阻塞

 

追踪服务

  • ServiceTracker

可以看到监听服务是很复杂的,而且有很多陷阱。OSGi提供了一个ServiceTracker的工具,弥补了监听服务的缺点。Rather than simply “listening”, which is passive, we wish to actively “track” the ser-vices we depend on.

用法示例:

public class MessageCountActivator2 implements BundleActivator {
  private ServiceTracker mboxTracker ;
  public void start(BundleContext context) throws Exception {
      mboxTracker = new ServiceTracker(context, Mailbox.class.getName(), null); //1
      mboxTracker.open(); // 2
      printMessageCount ( ) ;
  }
  public void stop(BundleContext context) throws Exception {
      mboxTracker.close(); // 3
  }
 
  private void printMessageCount ( ) throws MailboxException {
      Mailbox mbox = (Mailbox) mboxTracker.getService(); // 4 如何得到多个service?——
      if (mbox != null) {
          int count = mbox.getAllMessages().length ; // 5
          System.out.println("There are " + count + " messages");
     }
  }
}

获取服务除了可以用ServiceTracker.getService(),还可以用ServiceTracker.waitForService (5000) ;如果服务不可用,则会等待5秒再尝试获取。

与监听的区别:

The first difference, which we can see at marker 1 of the start method, is that instead of saving the bundle context directly into a field, we instead construct a new ServiceTracker field, passing it the bundle context and the name of the service that we are using it to track. Next at marker 2, we “open” the tracker, and at marker 3 in the stop() method we “close” it.

——创建ServiceTracker

The next difference is at marker 4, where we call getService on the tracker. Refreshingly, this immediately gives us the actual service object (if available) rather than a ServiceReference. So we simply go ahead and call the service at marker 5, bearing in mind that we still need to check if the service was found.  Also, we don’t need to clean up after ourselves with a finally block: we simply let the variable go out of scope, as the tracker will take care of releasing the service.

——getService直接返回服务对象,而不是ServiceReference;使用完服务后,无需再ungetService,因为ServiceTracker会替我们做。

  • ServiceTrackerCustomizer

具体到上一节的DbMailbox的例子,可以这么改造:

public class DbMailboxActivator implements BundleActivator {
    private BundleContext context;
    private ServiceTracker tracker;
    public void start(BundleContext context) throws Exception {
        this.context = context ;
        tracker = new ServiceTracker(context, DataSource.class.getName(), new DSCustomizer()); // 1
       // 2
       // When we call open() on a service tracker, it hooks up a ServiceListener
       // and then scans the pre-existing services, eliminates duplicates etc.
       tracker.open(); 
    }
    public void stop(BundleContext context) throws Exception {
        tracker.close(); // 3
    }

    private class DSCustomizer implements ServiceTrackerCustomizer {
    
  // when we return null from addingService, which the tracker takes to mean that we don’t care about this particular service reference.
  // That is, if we return null from addingService, the tracker will “forget” that particular service reference and will not call either modifiedService or removedService later if it is modified or removed.
  // Thus returning null can be used as a kind of filter, but in the next section we will see a more convenient way to apply filters declaratively
        public Object addingService(ServiceReference ref) {
          DataSource ds = (DataSource)context.getService(ref); // 4
          DbMailbox mbox = new DbMailbox(ds);
          ServiceRegistration registration = context.registerService(Mailbox.class.getName(), mbox, null); // 5
          return registration; // 6
       }
       public void modifiedService(ServiceReference ref, Object service ) {
       }
       public void removedService(ServiceReference ref, Object service ) {
          ServiceRegistration registration = (ServiceRegistration) service; // 7 这里的参数service,是addingService()的返回值
          registration.unregister(); // 8
          context.ungetService(ref); // 9
       }
    }
}

1、构造ServiceTracker:we pass in an instance of the ServiceTrackerCustomizer interface, which tells the tracker what to do when services are added, removed  or modified.

2、ServiceTracker.open():会自动注册ServiceListener,并扫描已有的服务,还会去除重复。

3、ServiceTracker.close():

4&5、addingService():获取DataSource service对象、基于该service构建新服务,注册新服务

6、addingService()返回值:会把该返回值传递给后续调用的modifiedService()和removedService()。

如果返回null,则表示我们不关注这个服务引用,tracker会忽略掉该服务引用,当该服务引用后续被修改或移除时,不会调用modifiedService()和removedService()。

所以返回null可以用作一种“过滤”,但是下一节的Filter更常用。

7&8&9、当DataSource service reference被移除时,移除掉我们自己的服务,并且释放DataSource service。

 
 

与监听不同,不仅当service注册时会触发addingService(),而且已有的已注册的service也会触发addingService()。所以也就不需要像监听那样先注册监听新service、再扫描已有的service。

unlike a listener, the adding and removed methods of ServiceTracker are called not only when the state of a service changes but also when the tracker is opened, to notify us of pre-existing services.

The addingService() method is called multiple times when the tracker is opened, once for each service currently registered, and it is also called whenever a new service is registered at any time later for as long as the tracker is open.

Furthermore the removedService() is called any time a service that we have been previously been notified of goes away, and it is also called for each service when the tracker closes.

Therefore we can deal with services in a uniform fashion without needing to distinguish between pre-existing services and ones that are registered while our listener is active. This greatly simplifies the code we need to write.

  • Filter

Filter filter = FrameworkUtil.createFilter (
    " (&( objectClass =" + Mailbox.class.getName() + ")" +
    "( mailboxName = welcome )( lang = en ))" ) ;
tracker = new ServiceTracker(context, filter, null ;

这相当于在getServiceReference中传入Properties进行过滤:

context.getServiceReference(Mailbox.class.getName(),
      " (&( mailboxName = welcome )( lang = en )) " )

 

注意FrameworkUtil.createFilter()会抛出InvalidSyntaxException,需要捕获这个异常。

为了减少语法错误的概率,可以用String.format();并用常量,而非字符串变量:

context.createFilter ( String.format(" (&(%s =%s )(%s =%s )(%s=%s))" ,
    Constants.OBJECTCLASS, Mailbox.class.getName(),
    Mailbox.NAME_PROPERTY, "welcome",
    "lang", "en*")) ;

服务工厂

【作用】

  • 有时service需要知道是哪个bundle在使用它。例如logger服务,它需要在日志中记录是哪个bundle调用它的。
  • 延迟初始化Service:ServiceFactory allows us to delay the creation of service objects until they are actually needed.

【用法】

上文在注册服务是都是传入了服务对象,我们还可以传入ServiceFactory。The factory does not itself implement the service interface, but it knows how to create an object that does.

这对消费者是透明的,它不能知道提供服务的是普通Service还是ServiceFactory。

【接口】

ServiceFactory接口定义如下:


public interface ServiceFactory {
    public Object getService ( Bundle bundle, ServiceRegistration registration ) ;
    public void ungetService ( Bundle bundle, ServiceRegistration registration, Object service ) ;
}

getService方法创建实际的服务对象实例。参数1-bundle 就是调用的bundle,参数2-registration 是注册服务时创建的ServiceRegistration。

——一个ServiceFactory可以创建多种服务;即根据ServiceRegistration来判断该创建那种服务。

ungetService方法当特定的bundle不再使用该服务时,执行一些清理操作。参数3-service 是getService返回的服务对象。

【实例】

import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;
class LogImpl implements Log {
    private String sourceBundleName ;
    public LogImpl(Bundle bundle) {
        this.sourceBundleName = bundle.getSymbolicName();
    }
    public void log(String message) {
        System.out.println(sourceBundleName + ": " + message );
    }
}

public class LogServiceFactory implements ServiceFactory {
    public Object getService(Bundle bundle, ServiceRegistration registration ) {
        return new LogImpl(bundle) ;
    }
    public void ungetService(Bundle bundl, ServiceRegistration registration, Object service) {
        // No spec i a l clean −up required
    }
}
 
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class LogServiceFactoryActivator implements BundleActivator {
    public void start(BundleContext context) throws Exception {
        context.registerService( Log.class.getName(),
                          new LogServiceFactory(), null) ;
    }
    public void stop(BundleContext context) throws Exception {
    }
}
 
 
 
 
 
 
 
 
 
 
 
 
 
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读