zoukankan      html  css  js  c++  java
  • [转]dubbo学习

    Comsumer:

     <dubbo:application name="dubbo_consumer"></dubbo:application>

       <!-- 使用zookeeper注册中心暴露服务地址 -->  

       <dubbo:registry address="zookeeper://192.168.74.129:2181" check="false"></dubbo:registry> 

         <!-- 要引用的服务 -->  

       <dubbo:reference interface="cn.test.dubbo.registry.service.TestRegistryService" id="testRegistryService"></dubbo:reference>

    Provider:

     <!-- 提供方应用名称信息,这个相当于起一个名字,我们dubbo管理页面比较清晰是哪个应用暴露出来的 -->

       <dubbo:application name="dubbo_provider"></dubbo:application>

       <!-- 使用zookeeper注册中心暴露服务地址 -->  

       <dubbo:registry address="zookeeper://127.0.0.1:2181" check="false" subscribe="false" register=""></dubbo:registry>

      <!-- 要暴露的服务接口 -->  

      <dubbo:service interface="cn.test.dubbo.registry.service.TestRegistryService" ref="testRegistryService" /> 

     

    实现Dubbo的机制:

    1, SPI

    SPI即service provider interface,为了去除代码注入,只用提供一个接口,因此,为了在程序中无需动态指明实现类,需要一种服务发现机制,SPI约定如下:

    当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件,该文件包含实现该服务接口的具体实现类的名字。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 

    示例:

    1)接口

    [java] view plain copy

     

    1. package com.unei.serviceloader;  
    2.   
    3. /** 
    4.  * Created by sun on 2015/7/25. 
    5.  */  
    6. public interface Command {  
    7.     public void execute();  
    8. }  

     

    2)实现类

    [java] view plain copy

     

    1. package com.unei.serviceloader;  
    2.   
    3. /** 
    4.  * Created by sun on 2015/7/25. 
    5.  */  
    6. public class ShutdownCommand implements Command{  
    7.     public void execute() {  
    8.         System.out.println("shutdown....");  
    9.     }  
    10. }  

     

    [java] view plain copy

     

    1. package com.unei.serviceloader;  
    2.   
    3. /** 
    4.  * Created by sun on 2015/7/25. 
    5.  */  
    6. public class StartCommand implements Command{  
    7.     public void execute() {  
    8.         System.out.println("start....");  
    9.     }  
    10. }  

     

    3)配置文件

    由于是使用maven构建的项目,所以就在resources下面新建目录META-INF/services,在该目录下新建文件com.unei.serviceloader.Command,即完整的接口名

    文件内容如下:

    [plain] view plain copy

     

    1. com.unei.serviceloader.ShutdownCommand  
    2. com.unei.serviceloader.StartCommand  

     

    4)main方法

    [java] view plain copy

     

    1. package com.unei.serviceloader;  
    2.   
    3. import java.util.ServiceLoader;  
    4.   
    5. /** 
    6.  * Created by sun on 2015/7/25. 
    7.  */  
    8. public class Main {  
    9.     public static void main(String[] args) {  
    10.         ServiceLoader<Command> serviceLoader=ServiceLoader.load(Command.class);  
    11.         for(Command command:serviceLoader){  
    12.             command.execute();  
    13.         }  
    14.     }  
    15.   
    16. }  

    Dubbo底层实现概述:

    http://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235

     

    SPI接口定义

    定义了@SPI注解

    public @interface SPI {

           Stringvalue() default ""; //指定默认的扩展点

    }  

    只有在接口打了@SPI注解的接口类才会去查找扩展点实现

    会依次从这几个文件中读取扩展点

    META-INF/dubbo/internal/   //dubbo内部实现的各种扩展都放在了这个目录了

    META-INF/dubbo/

    META-INF/services/

     

    我们以Protocol接口为例, 接口上打上SPI注解,默认扩展点名字为dubbo

    @SPI("dubbo")

    public interface Protocol{

    }

     

    dubbo中内置实现了各种协议如:DubboProtocol InjvmProtocolHessianProtocol WebServiceProtocol等等

     

     

     

    Dubbo默认rpc模块默认protocol实现DubboProtocol,key为dubbo

     

     

     

     

    下面我们来细讲ExtensionLoader类

     

    1.      ExtensionLoader.getExtensionLoader(Protocol.class)

    每个定义的spi的接口都会构建一个ExtensionLoader实例,存储在

    ConcurrentMap<Class<?>,ExtensionLoader<?>> EXTENSION_LOADERS 这个map对象中

     

    2.      loadExtensionClasses 读取扩展点中的实现类

    a)       先读取SPI注解的value值,有值作为默认扩展实现的key

    b)       依次读取路径的文件

    META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol

    META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol

    META-INF/services/ com.alibaba.dubbo.rpc.Protocol

    3.      loadFile逐行读取com.alibaba.dubbo.rpc.Protocol文件中的内容,每行内容以key/value形式存储的。

    a)     判断类实现(如:DubboProtocol)上有米有打上@Adaptive注解,如果打上了注解,将此类作为Protocol协议的设配类缓存起来,读取下一行;否则适配类通过javasisit修改字节码生成,关于设配类功能作用后续介绍

    如果类实现没有打上@Adaptive, 判断实现类是否存在入参为接口的构造器(就是DubbboProtocol类是否还有入参为Protocol的构造器),有的话作为包装类缓存到此ExtensionLoader的Set<Class<?>>集合中,这个其实是个装饰模式

    b)   如果类实现没有打上@Adaptive, 判断实现类是否存在入参为接口的构造器(就是DubbboProtocol类是否还有入参为Protocol的构造器),有的话作为包装类缓存到此ExtensionLoader的Set<Class<?>>集合中,这个其实是个装饰模式

     

     

    c)     如果即不是设配对象也不是wrapped的对象,那就是扩展点的具体实现对象

    查找实现类上有没有打上@Activate注解,有缓存到变量cachedActivates的map中

    将实现类缓存到cachedClasses中,以便于使用时获取

     

     

    4.      获取或者创建设配对象getAdaptiveExtension

    a)如果cachedAdaptiveClass有值,说明有且仅有一个实现类打了@Adaptive, 实例化这个对象返回

    b) 如果cachedAdaptiveClass为空, 创建设配类字节码。 

    为什么要创建设配类,一个接口多种实现,SPI机制也是如此,这是策略模式,但是我们在代码执行过程中选择哪种具体的策略呢。Dubbo采用统一数据模式com.alibaba.dubbo.common.URL(它是dubbo定义的数据模型不是jdk的类),它会穿插于系统的整个执行过程,URL中定义的协议类型字段protocol,会根据具体业务设置不同的协议。url.getProtocol()值可以是dubbo也是可以webservice, 可以是zookeeper也可以是redis。

    设配类的作用是根据url.getProtocol()的值extName,去ExtensionLoader. getExtension( extName)选取具体的扩展点实现。

    所以能够利用javasist生成设配类的条件

    1)接口方法中必须至少有一个方法打上了@Adaptive注解

    2)打上了@Adaptive注解的方法参数必须有URL类型参数或者有参数中存在getURL()方法

    下面给出createAdaptiveExtensionClassCode()方法生成javasist用来生成Protocol适配类后的代码

    import com.alibaba.dubbo.common.extension;

    public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {

    //没有打上@Adaptive的方法如果被调到抛异常

          public void destroy() {

    throw new UnsupportedOperationException(

      "methodpublic abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interfacecom.alibaba.dubbo.rpc.Protocol is not adaptive method!")

    //没有打上@Adaptive的方法如果被调到抛异常

          public int getDefaultPort() {

                 throw newUnsupportedOperationException(

                 "method public abstractint com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interfacecom.alibaba.dubbo.rpc.Protocol is not adaptive method!");

          }

     

    //接口中export方法打上@Adaptive注册

          publiccom.alibaba.dubbo.rpc.Exporter export(

                 com.alibaba.dubbo.rpc.Invokerarg0)  throws com.alibaba.dubbo.rpc.Invoker{

                 if (arg0 == null)

                        throw newIllegalArgumentException("com.alibaba.dubbo.rpc.Invokerargument == null");

                 //参数类中要有URL属性

    if(arg0.getUrl() == null) 

                        throw newIllegalArgumentException( "com.alibaba.dubbo.rpc.Invokerargument getUrl() == null");

                 //从入参获取统一数据模型URL

    com.alibaba.dubbo.common.URL url = arg0.getUrl();

               String extName =(url.getProtocol() == null ? "dubbo" : url.getProtocol());

               //从统一数据模型URL获取协议,协议名就是spi扩展点实现类的key

    if (extName == null) throw new IllegalStateException( "Fail to getextension(com.alibaba.dubbo.rpc.Protocol) name from url("  + url.toString() + ") usekeys([protocol])");

               

    //利用dubbo服务查找机制根据名称找到具体的扩展点实现

    com.alibaba.dubbo.rpc.Protocol extension =(com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);      

    //调具体扩展点的方法

    return extension.export(arg0);

     }

     

    //接口中refer方法打上@Adaptive注册

     publiccom.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0,

                        com.alibaba.dubbo.common.URLarg1) throws java.lang.Class {

          

    //统一数据模型URL不能为空

    if (arg1 == null)

                 throw newIllegalArgumentException("url == null");

          

     com.alibaba.dubbo.common.URL url =arg1;

    //从统一数据模型URL获取协议,协议名就是spi扩展点实现类的key

    String extName = (url.getProtocol() == null ?"dubbo" : url.getProtocol());

        if (extName == null)

           thrownewIllegalStateException("Failtogetextension(com.alibaba.dubbo.rpc.Protocol)name from url("+ url.toString() + ") use keys([protocol])");

     

       //利用dubbo服务查找机制根据名称找到具体的扩展点实现

    com.alibaba.dubbo.rpc.Protocol extension =(com.alibaba.dubbo.rpc.Protocol)  ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)

    .getExtension(extName);

        //调具体扩展点的方法

    return extension.refer(arg0, arg1);

    }

    }

     

    5. 通过createAdaptiveExtensionClassCode()生成如上的java源码代码,要被java虚拟机加载执行必须得编译成字节码,dubbo提供两种方式去执行代码的编译1)利用JDK工具类编译2)利用javassit根据源代码生成字节码。

     

    如上图:

    1)生成Adaptive代码code

    2)利用dubbo的spi扩展机制获取compiler的设配类

    3)编译生成的adaptive代码

    在此顺便介绍下 @Adaptive注解打在实现类上跟打在接口方法上的区别

    1)如果有打在接口方法上,调ExtensionLoader.getAdaptiveExtension()获取设配类,会先通过前面的过程生成java的源代码,在通过编译器编译成class加载。但是Compiler的实现策略选择也是通过ExtensionLoader.getAdaptiveExtension(),如果也通过编译器编译成class文件那岂不是要死循环下去了吗?

    ExtensionLoader.getAdaptiveExtension(),对于有实现类上去打了注解@Adaptive的dubbo spi扩展机制,它获取设配类不在通过前面过程生成设配类java源代码,而是在读取扩展文件的时候遇到实现类打了注解@Adaptive就把这个类作为设配类缓存在ExtensionLoader中,调用是直接返回

     

    6.  自动Wrap上扩展点的Wrap类

    这是一种装饰模式的实现,在jdk的输入输出流实现中有很多这种设计,在于增强扩展点功能。这里我们拿对于Protocol接口的扩展点实现作为实例讲解。

     

    如图Protocol继承关系ProtocolFilterWrapper, ProtocolListenerWrapper这个两个类是装饰对象用来增强其他扩展点实现的功能。ProtocolFilterWrapper功能主要是在refer 引用远程服务的中透明的设置一系列的过滤器链用来记录日志,处理超时,权限控制等等功能;ProtocolListenerWrapper在provider的exporter,unporter服务和consumer

     的refer服务,destory调用时添加监听器,dubbo提供了扩展但是没有默认实现哪些监听器。

     

    Dubbo是如何自动的给扩展点wrap上装饰对象的呢?

    1)在ExtensionLoader.loadFile加载扩展点配置文件的时候

    对扩展点类有接口类型为参数的构造器就是包转对象,缓存到集合中去

    2)在调ExtensionLoader的createExtension(name)根据扩展点key创建扩展的时候, 先实例化扩展点的实现, 在判断时候有此扩展时候有包装类缓存,有的话利用包转器增强这个扩展点实现的功能。如下图是实现流程

     

     

    7. IOC大家所熟知的ioc是spring的三大基础功能之一, dubbo的ExtensionLoader在加载扩展实现的时候内部实现了个简单的ioc机制来实现对扩展实现所依赖的参数的注入,         dubbo对扩展实现中公有的set方法且入参个数为一个的方法,尝试从对象工厂ObjectFactory获取值注入到扩展点实现中去。

     

         

          上图代码应该不能理解,下面我们来看看ObjectFactory是如何根据类型和名字来获取对象的,ObjectFactory也是基于dubbo的spi扩展机制的

     

     

    它跟Compiler接口一样设配类注解@Adaptive是打在类AdaptiveExtensionFactory上的不是通过javassist编译生成的。

    AdaptiveExtensionFactory持有所有ExtensionFactory对象的集合,dubbo内部默认实现的对象工厂是SpiExtensionFactory和SpringExtensionFactory,他们经过TreeMap排好序的查找顺序是优先先从SpiExtensionFactory获取,如果返回空在从SpringExtensionFactory获取。

    1) SpiExtensionFactory工厂获取要被注入的对象,就是要获取dubbo spi扩展的实现,所以传入的参数类型必须是接口类型并且接口上打上了@SPI注解,返回的是一个设配类对象。

     

     

    2) SpringExtensionFactory,Dubbo利用spring的扩展机制跟spring做了很好的融合。在发布或者去引用一个服务的时候,会把spring的容器添加到SpringExtensionFactory工厂集合中去, 当SpiExtensionFactory没有获取到对象的时候会遍历SpringExtensionFactory中的spring容器来获取要注入的对象

     

     

    8. 下面 给出整体活动图

     

  • 相关阅读:
    openresty + gor+minio 集成
    openresty docker 镜像集成gor
    goreplay v1.1.0 支持pro特性docker 镜像
    编译goreplay v1.1.0 minio 集成支持
    编译goreplay v1.1.0 支持二进制协议捕捉
    goreplay v1.1.0 发布
    super-expressive 可以基于js 直接编写正则
    monio 的一些安全实践
    minio+ nginx rewrite 实现saas租户的个性化管理
    LDAP概念和原理介绍
  • 原文地址:https://www.cnblogs.com/hzorac/p/5397025.html
Copyright © 2011-2022 走看看