`

OSGI之服务层

    博客分类:
  • OSGI
阅读更多

前言

 

什么是服务,简单的说就是把一件事情交给别人去做(服务提供者),至于别人如何去 对应调用者(服务使用者)来说是不care的。在现实世界中 服务使用者(客户),只关心结果是什么,而不关心过程;比如客户点了一个芝士披萨,餐厅如何去做这个披萨客户不会去过问(过程),客户只管最后的披萨是否好吃(结果),在程序设计的世界中也是如此。

 

服务化或者微服务化,本质上就是对一组服务按业务进行分类封装,并进行独立部署,这是构建分布式系统的基础。服务化可以实现系统之间的解耦:核心业务逻辑都在各自系统内部完成,有些工作需要使用外部协助时,只需要根据一定协议调用外部服务即可。这种松耦合的关系,还可以实现服务的热插拔;另外服务不可用时,也可以方便的采取一些兜底措施。

 

对于一个大型电商网站系统的设计也是如此,该系统一般会由成百上千个子系统构成,这些子系统各自处理自己内部的核心业务,当需要其他子系统协助时,通过调用服务接口即可。如此,各个系统可以独自并行开发和维护,并且所有的服务只需要开发一次就可以在多个子系统中复用。这就是涉及到公司级的SOA服务治理,一般都会采用成熟的服务治理框架来完成,以现在流行的RPC服务治理框架为例,服务框架一般分为:服务提供者、服务注册中心、服务使用者,如下:



 

 

好了 打住 关于服务就不扯远了,这跟今天的主题OSGI服务层有什么关系呢?其实OSGI服务层的作用跟SOA服务治理框架的作用完全一样,而且设计思想也几乎是一样的,都是按照上图的三个角色以及关系进行设计的。唯一不同的时:SOA服务治理面向的公司级服务治理,而OSGI服务层面向的是单个JVM实例内部服务治理,也就是普通服务化的缩小版。

 

OSGI服务层

 

为什么要在JVM内部还要实现服务化呢?其实原因与公司级服务化的目的类似:

可以实现多个模块的完全解耦(高内聚低耦合模块),实现一个由多个松耦合模块(Bundle)构建起的单个系统;

实现服务的热插拔,与普通SOA服务治理一样,OSGI框架可以在其内部实现服务的热更新、热替换(换另外一种实现);

并行开发,在项目组内部可以提前把单个系统拆分成多个模块(Bundle),定义好各个模块之间的边界协议(接口,或者说服务),由不同的开发人员并行开发,他们只需要专注自己负责的模块即可。想想就是一件令人兴奋的事,下面就开始来看看OSGI规范是如何定义的服务层API,自己我们如何去使用这些API。服务层的API定义在上下文BundleContext中:

public interface BundleContext extends BundleReference {
    //添加服务监听器
    void addServiceListener(ServiceListener var1, String var2) throws InvalidSyntaxException;
    //添加服务监听器
    void addServiceListener(ServiceListener var1);
    //取消服务监听器
    void removeServiceListener(ServiceListener var1);
    //注册服务
    ServiceRegistration<?> registerService(String[] var1, Object var2, Dictionary<String, ?> var3);
    //注册服务
    ServiceRegistration<?> registerService(String var1, Object var2, Dictionary<String, ?> var3);
    //注册服务
    <S> ServiceRegistration<S> registerService(Class<S> var1, S var2, Dictionary<String, ?> var3);
    //获取服务引用
    ServiceReference<?>[] getServiceReferences(String var1, String var2) throws InvalidSyntaxException;
    //获取服务引用
    ServiceReference<?>[] getAllServiceReferences(String var1, String var2) throws InvalidSyntaxException;
    //获取服务引用
    ServiceReference<?> getServiceReference(String var1);
    //获取服务引用
    <S> ServiceReference<S> getServiceReference(Class<S> var1);
    //获取服务引用
    <S> Collection<ServiceReference<S>> getServiceReferences(Class<S> var1, String var2) throws InvalidSyntaxException;
    //获取服务对象
    <S> S getService(ServiceReference<S> var1);
    //取消服务对象
boolean ungetService(ServiceReference<?> var1);
 
//省略其它生命周期层方法
}

 

 

纵观这些API,大致可以归为3类:注册服务方法、获取服务方法、监听器相关方法。

 

注册服务方法

OSGI框架提供了几个重载的注册服务方法,其作用就是发布服务到注册中心。发布一个服务很简单:

public class HelloWorldAtivictor implements BundleActivator{
    @Override
    public void start(BundleContext context) throws Exception {
        Dictionary dictionary = new Properties();
        dictionary.put("test","test");
        ServiceRegistration registration = context.registerService(HelloService.class.getName(),new HelloServiceImpl("小明"),dictionary);
 
        dictionary.put("test","test2");
        registration.setProperties(dictionary);//修改元数据
 
        System.out.println("start server bundle");
        registration.unregister();//取消服务注册
    }
 
    @Override
    public void stop(BundleContext context) throws Exception {
        System.out.println("stop");
    }
}

 

本示例中使用的注册方法有三个参数:第一个是服务的名字(一般是接口名),第二个是服务的具体实现对象,第三个是元数据主要用来区分同一个接口发布成不同的实现服务,客户端可以根据条件筛选自己需要的服务。

 

服务注册成功后会返回一个ServiceRegistration对象,这个对象是Bundle内部私有的。可以通过调用其setProperties方法修改元数据;以及unregister方法取消服务注册,在Bundle停止时,框架会自动清理所有的该Bundle注册的服务,所以一般情况下不需要我们手动去取消服务注册。

 

获取服务方法

获取一个OSGI服务,需要两步 第一步通过上下文的getServiceReference方法 从注册中心获取注册引用:

public class ClientActivator implements BundleActivator {
    @Override
    public void start(BundleContext context) throws Exception {
        ServiceReference ref = context.getServiceReference(HelloService.class.getName());
        if(ref!=null){
            try{
                HelloService helloService = ((HelloService)context.getService(ref));
                if(helloService!=null){//调用服务方法
                    helloService.sayHello();
                }
            }catch (Exception e){
                System.out.println("服务调用异常");
            }
            finally {
                context.ungetService(ref);
            }
        }
        System.out.println("start client bundle");
    }
 
    @Override
    public void stop(BundleContext context) throws Exception {
 
    }
}

 

第二步,调用上下文对象的getService方法获取到真实的服务引用,然后就可以调用服务接口中定义的方法了:

HelloService helloService = ((HelloService)context.getService(ref));
helloService.sayHello();
 

 

由于在OSGI框架下所有的服务都有可能随时消失,注意开发时需要进行适当的空值判断。另外由于getService方法获取的是提供服务Bundle中的一个真实引用,尽量不要长期持有,否则服务Bundle在需要停止时,无法正常的进行拉结回收(因为还在被其他对象引用)。在使用完服务后最好手动调用ungetService方法告诉注册中心释放引用。

 

监听器相关方法

由于OSGI框架中的服务有可能随时消失或者更新,前面提到了尽量不要长期持有一个对象,这势必会导致每次在使用时都需要重复取服务。如果有一种方式能在服务发生变化是通知服务使用方,同步进行更新,这样就可以解脱出来了。OSGI服务层的服务层中定义了监听器注册机制(ServiceListener),来解决这个问题。

 

服务的状态变化一般有三种:注册、更新、注销。也就是说监听器 需要监听这三种变化,并通知服务使用者做出响应。首先来看一个简单的监听器实现:

public class HelloListener implements ServiceListener {
    private BundleContext context;
 
    public HelloListener(BundleContext context) {
        this.context = context;
    }
 
    public void serviceChanged(ServiceEvent event) {
        switch (event.getType()) {
            case ServiceEvent.REGISTERED:
                Object service = context.getService(event.getServiceReference());
                if(service instanceof HelloService){
                    ClientActivator.helloService = (HelloService) context.getService(event.getServiceReference());
                }
                break;
            case ServiceEvent.MODIFIED:
                break;
            case ServiceEvent.UNREGISTERING:
                service = context.getService(event.getServiceReference());
                if(service instanceof HelloService){
                    context.ungetService(event.getServiceReference());
                    ClientActivator.helloService = null;
                }
                break;
            default:
                break;
        }
    }
}

 

主要就是实现serviceChanged方法,它有一个ServiceEvent类型参数,用于接收当前的变更类型。每当接收到变化时就可以更新helloService对象,当服务消失时把helloService置为null。这个对象就可以被放到一个对象的成员变量里(本示例是一个静态常量里),方便复用。如果有多个可用的服务,可以把这些服务对象放到一个集合中,根据业务需要使用不同的服务对象。

 

监听器定义完成后,还需要把它注册到一个服务下,注册服务监听器一般在启动器中完成:

public class ClientActivator implements BundleActivator {
    public static volatile HelloService helloService;//服务对象
    private ExecutorService executorService;
 
    @Override
    public void start(BundleContext context) throws Exception {
        System.out.println("client开始启动");
 
        //注册监听器
        HelloListener listener = new HelloListener(context);
        context.addServiceListener(listener);
       
        //这里可以启动一个线程测试HelloService服务是否可用
        executorService = Executors.newSingleThreadExecutor();
        executorService.submit(new TestTask(context));
    }
 
    @Override
    public void stop(BundleContext context) throws Exception {
        executorService.shutdown();//优雅的关闭线程池
    }
}
//模拟测试helloService服务
public class TestTask implements Runnable{
    private BundleContext context;
 
    public TestTask(BundleContext context) {
        this.context = context;
    }
 
    @Override
    public void run() {
            boolean flag = true;
            while (flag){
                try {
                    if (ClientActivator.helloService != null) {
                        ClientActivator.helloService.sayHello();
                    } else {
                        System.out.println("没有可用的服务");
                    }
                }catch (Exception e){
                    System.out.println("业务异常");
                }
 
                //睡5秒重试
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("线程池关闭");
                    flag = false;
                }
            }
    }
}

 

注册一个服务监听器很简单,只需要调用Bundle上下文对象的addServiceListener方法即可。当服务提供方注册服务到注册中心时,就可以自动触发对静态的成员变量helloService进行赋值。当服务提供方取消服务注册时,会自动触发对静态的成员变量helloService置空。

 

可见自己实现服务监听器是件比较麻烦的工作,OSGI提供了服务追踪ServiceTracker对服务监听进行了封装,使用起来更简单,而且不容易出错。

 

服务追踪器ServiceTracker

 

使用ServiceTracker跟踪服务,无需自己实现服务监听器,使用方式很简单:ServiceTracker有两个构造函数,调用构造函数创建追踪器对象,并调用其open方法开启追踪即可:

public class TrackerActivator implements BundleActivator {
    private ServiceTracker serviceTracker;
    private ExecutorService executorService;
 
    @Override
    public void start(BundleContext context) throws Exception {
 
        serviceTracker = new ServiceTracker(context, HelloService.class.getName(), null);
        serviceTracker.open();//打开追踪器
 
        //新开一个线程,模拟调用服务
        executorService = Executors.newSingleThreadExecutor();
        executorService.submit(new TestTask(serviceTracker));
        System.out.println("start tracker bundle");
    }
 
    @Override
public void stop(BundleContext context) throws Exception {
    serviceTracker.close();//关闭追踪器
        executorService.shutdown();
    }
}
 
//模式测试服务
public class TestTask implements Runnable{
    public ServiceTracker serviceTracker;
 
    public TestTask(ServiceTracker serviceTracker) {
        this.serviceTracker = serviceTracker;
    }
 
    @Override
    public void run() {
            boolean flag = true;
            while (flag){
                try {
                    HelloService helloService = (HelloService)serviceTracker.getService();
                    if (helloService!= null) {
                        helloService.sayHello();
                    } else {
                        System.out.println("没有可用的服务");
                    }
                }catch (Exception e){
                    System.out.println("业务异常");
                }
 
                //睡5秒重试
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("线程池关闭");
                    flag = false;
                }
            }
    }
}
 

 

上面模拟代码比较长,但对于使用追踪器来说,只有三行代码:

ServiceTracker serviceTracker = new ServiceTracker(context, HelloService.class.getName(), null);
        serviceTracker.open();//打开追踪器
 
//获取服务
HelloService helloService = (HelloService)serviceTracker.getService();

 

创建追踪器对象-->开启追踪器-->调用最终器的getService()方法获取服务对象 即可。使用ServiceTracker追踪一个服务就这么简单。

 

另外ServiceTracker的构造方法第三个参数可以传入一个定制器对象,上述示例中传入的是null,即非定制模式。接下来看下定制模式,首先就要创建一个定制器 实现ServiceTrackerCustomizer接口即可,这个接口定义了三个方法:

public interface ServiceTrackerCustomizer<S, T> {
    //添加服务
    T addingService(ServiceReference<S> var1);
    //修改服务
    void modifiedService(ServiceReference<S> var1, T var2);
    //移除服务
    void removedService(ServiceReference<S> var1, T var2);
}
 

 

定制器的作用是在服务发生变化时(注册服务、修改服务、移除服务)可以打印一些日志,或者做一些保证等之定义操作,创建一个自己的定制器类,实现这个接口:

public class MyServiceTrackerCustomizer implements ServiceTrackerCustomizer {
    private BundleContext context;
 
    public MyServiceTrackerCustomizer(BundleContext context) {
        this.context = context;
    }
 
    @Override
    public Object addingService(ServiceReference serviceReference) {
        HelloService helloService = (HelloService)context.getService(serviceReference);
        //返回一个包装后的服务
        return new MyHelloService(helloService);
    }
 
    @Override
    public void modifiedService(ServiceReference serviceReference, Object o) {
        System.out.println("服务被修改了");
    }
 
    @Override
    public void removedService(ServiceReference serviceReference, Object o) {
        System.out.println("服务被移除了");
    }
}
 
class MyHelloService implements HelloService{
    private HelloService helloService;
 
    public MyHelloService(HelloService helloService) {
        this.helloService = helloService;
    }
 
    @Override
    public void sayHello() {
        System.out.println("包装服务开始");
        helloService.sayHello();
        System.out.println("包装服务结束");
    }
}
 

 

这个定制器,在发现有服务注册时,使用了一个装饰器对服务进行了包装;在服务修改或者移除时,打印一条日志信息。使用带定制器的 服务追踪器 很简单,把定制器传入ServiceTracker构造方法第三个参数即可:

ServiceTracker serviceTracker = new ServiceTracker(context, HelloService.class.getName(), 
new MyServiceTrackerCustomizer(context));
 

 

总的来说使用服务追踪器比自己定义服务监听器 使用起来更简单,而且不容易出错(但一定要启动opneclose追踪器)。

 

关于OSGI服务层相关的API就总结到这里,至此已经对OSGI的模块层、生命周期层、服务层分别进行了总结。但示例比较少,下次准备做一个完整的demo,并使用idea开发工具进行讲解。

 

 

  • 大小: 22.1 KB
0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics