avatar

Dubbo源码分析之核心篇(三)

上篇我们说到ExtensionLoader.getExtensionLoader(Class).getExtension(name)方法内部原理,今天我们继续说一说ExtensionLoader的其他知识。这一篇内容如下:

  • ExtensionLoader.getAdaptiveExtension()源码分析
  • 补上上一篇留下的坑位

let’s go!

回忆

让我们先回忆一下ExstionLoder.getAdaptiveExtension()的使用。分下面几步:

  • 首先需要定义一个接口,在接口上加上@SPI注解。标识这是一个SPI

  • 然后我们可以在两个地方来加上@Adaptive注解

    • 在实现类上加上@Adaptive注解,代表该类是一个代理类。具体实现类是通过该类get到的。这种方式在Dubbo中很少见,只有少数的那么几个。
    • 在接口的方法上加上@Adaptive注解,这时候Dubbo内部会帮你动态生成代理类,同样,具体实现类是通过该类get到的。这种方式就很常见了。比如Protocol中的export()以及refer()方法。
  • 那么总结下吧。其实加载方法上和加在类上的区别就是,一个是要你自己手写定义代理,一个就是Dubbo在运行时动态生成。

  • 然后在Dubbo规定的目录下,创建一个以接口全限定名为文件名的文件,在里面定义name和实现类。

  • 最后,我们可以进行使用啦,代码一般类似下面这样写:

    1
    2
    3
    4
    5
    6
    7
    public static void main(String []args){
    ExtensionLoader extensionLoader = ExtensionLoader.getExtensionLoader(Xx.class);
    //获取AdaptiveExtension。其实这里获取的是一个代理类
    Xx adaptive = extensionLoader.getAdaptiveExtension();
    //这时候会根据参数再去获取真正的实现类。
    adaptive.export(url);
    }

好的,回忆完,我们继续往下走。

深入

上次我们已经分析了ExtensionLoader.getExtensionLoader(Xx.class);这部分,那么我就略过了。直接往下面看。也就是extensionLoader.getAdaptiveExtension();,首先我们来看下源码,一层一层的剥开它的心。

getAdaptiveExtension()

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public T getAdaptiveExtension() {
//以我们上一篇的经验来看,cachedAdaptiveInstance肯定是一个缓存。先从缓存中取,缓存中没有在创建
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
//再取一次。避免并发情况时,会多次生成实例的情况
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//创建一个AdaptiveExtension。关键都在这里面了。得到一个instance
instance = createAdaptiveExtension();
//缓存一下。
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
//返回实例。
return (T) instance;
}

上面做的事很简单,如下:

  • 先从缓存中get我们的AdaptiveExtension。如果get到了,就直接返回。get不到就往下走
  • 如果没有就调用createAdaptiveExtension()方法创建一个。
  • 把创建的实例,put到缓存中。
  • 返回实例。

下面我们接着往里面再深入。

createAdaptiveExtension()

源码如下:

1
2
3
4
5
6
7
8
9
10
11
private T createAdaptiveExtension() {
try {
//这里有三步,分别如下
//1. 先调用getAdaptiveExtensionClass()获取我们的代理类
//2. 调用newInstance()方法创建实例对象
//3. 调用injectExtension方法实现依赖注入
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}

代码做的事,我注释中已经很清楚了。我们现在只需要分析下getAdaptiveExtensionClass()的代码就行了,依赖注入的代码我们上一篇已经分析了,这里就不再多说。让我们继续往深里造。

getAdaptiveExtensionClass()

代码如下:

1
2
3
4
5
6
7
8
9
10
private Class<?> getAdaptiveExtensionClass() {
//这个方法是不是有一点点熟悉?在上一篇的分析中,有出现他的身影,而且是重中之重。
getExtensionClasses();
if (cachedAdaptiveClass != null) {
//类级别的自适应扩展点
return cachedAdaptiveClass;
}
//方法级别的自适应扩展点
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

该方法做了如下几件事:

  • 调用getExtesionClasses()加载我们的扩展点(如果没有加载的话),在这个里面,上篇文章里有几个地方留有坑位没讲,这里会涉及到一个,且听我分析。

    • 上次我们在loadClass()方法中,有如下判断:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      //判断配置文件中的类
      if (clazz.isAnnotationPresent(Adaptive.class)) {
      //缓存类级别自适应扩展点
      cacheAdaptiveClass(clazz);
      } else if (isWrapperClass(clazz)) {
      ....//无关代码先省略
      } else {
      ....//无关代码先省略
      }
      • 如上,首先我们拿到class后,会调用isAnnotationPresent()方法判断实现类上是否有加@Adaptive注解。还记得上面我们回忆部分说的规则么。如果实现类上加了@Adaptive注解,表示什么?没错,表示该类是一个代理类,具体调用的实现类,是通过该类get到的。

      • 如果该类上确实有@Adaptive注解,就会进入if体,执行cacheAdaptiveClass()。该方法源码如下:

        1
        2
        3
        4
        5
        6
        7
        8
        private void cacheAdaptiveClass(Class<?> clazz) {
        if (cachedAdaptiveClass == null) {
        cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
        throw new IllegalStateException("More than 1 adaptive class found: "
        + cachedAdaptiveClass.getClass().getName()+ ", " +clazz.getClass().getName());
        }
        }
        • 上面的代码清晰明了,如果cachedAdaptiveClass为空的话,就会赋值。如果cachedAdaptiveClass有值,则会比较当前传过来的类是否相等,如果不相等就会报错。所以从这里可以看到,我们的cachedAdaptiveClass只能有一个,换句话说,我们一个接口只能有一个在类上加@Adaptive注解的实现类。
  • ok。坑位已经填完,我们继续往下说。执行完getExtensionClasses()以后,这时候如果有一个在类上加@Adaptive注解的实现类,那么cachedAdaptiveClass必定不为空,然后就会直接返回该类,接着就是newInstance()和依赖注入了。到这里,@Adaptive注解加在类上的解析过程,以及含义,你是否已经有点数了?

  • 那么如果该SPI没有一个实现类在类声明上加上@Adaptive注解,而是在声明的接口的方法上加了这个注解呢?毕竟我们在回忆中说过,有两种情况。上面是一种情况,那么另一种情况呢?

  • 我们一起来看看 return cachedAdaptiveClass = createAdaptiveExtensionClass();这段代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    private Class<?> createAdaptiveExtensionClass() {
    //这里会根据接口动态生成代理类
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    //找ClassLoader
    ClassLoader classLoader = findClassLoader();
    //可以看到,这里也是通过getAdaptiveExtension()方法获取自适应扩展点
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    //编译成我们的Class对象。
    return compiler.compile(code, classLoader);
    }

    从上面代码看,里面无非就是做了下面几件事情

    • 调用方法生成我们的代理类的代码。
    • 获取classloader。
    • 把代码编译成一个Class对象

    emm.生成代码部分我们就不看了。但是我们可以尝试下在这一行下断点进行调试,看它生成的代码是什么内容。这里我拿Protocol这个接口来举例说明。接口声明代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @SPI("dubbo")
    public interface Protocol {

    int getDefaultPort();

    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    void destroy();

    }

    我们先下个断点,让dubbo跑起来。对了,Dubbo源码中带有demo,也就是dubbo-demo模块。我们就拿它来跑一下。跑起来断点后的结果如下:

    这样看上去并不美观,我们把它copy下,新建一个java文件。最终美化的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    import org.apache.dubbo.common.extension.ExtensionLoader;
    public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {

    public void destroy() {
    throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
    throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    //refer方法代理
    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
    if (arg1 == null) throw new IllegalArgumentException("url == null");
    org.apache.dubbo.common.URL url = arg1;
    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
    if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
    org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
    return extension.refer(arg0, arg1);
    }

    //export方法代理
    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
    if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
    if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
    org.apache.dubbo.common.URL url = arg0.getUrl();
    String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
    if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
    org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
    return extension.export(arg0);
    }
    }

    我们暂时不深究生成代码的含义,等后面分析Dubbo的服务注册功能时,再回过头来看一眼。到这里,上面的createAdaptiveExtensionClass()最重要的地方我们已经明确了,下面就是获取classLoader,并且编译下生成的文本为Class对象,这个方法就算是完了。然后我们的getAdaptiveExtensionClass()也执行完毕了。整个@Adaptive激活扩展点的流程和代码我们都梳理完了,下面总结一下。

总结

整个@Adaptive注解的加载做了下面这些事情。

  • 调用getAdapitveExtension()方法时,会先加载一次该接口所有的实现类。这点在上一篇中已经详细分析了。在加载的过程中,如果有实现类的类签名上加上了@Adaptive注解,就把他缓存起来,也就是缓存一下我们手写的代理类。注意:该代理类在一个接口中只会有一个,如果有多个会抛出异常。

  • 把我们的实现类全部都load到内存后,就调用createAdaptiveExtension()方法,在该方法中做了以下几件事:

    • 获取我们的代理类Class对象
    • 创建一个该对象实例
    • 依赖注入
  • 下面我们总结下它是怎么获取代理类的Class对象的

    • 首先会判断是否有缓存过我们手写的代理类,也就是类签名上标有@Adaptive的类。如果有就直接返回。
    • 如果上面的步骤中缓存为null,也就是该类没有一个类签名上标有@Adaptive的实现类,所以Dubbo会在内存中为我们自动生成一个代理类。也就是调用new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate()生成代码。
    • 寻找Classloader,编译我们上面的代码为Class对象。

好了,到这里整个流程基本上是完了。虽然没有一行一行代码进行讲解,但是我觉得到这里,你至少应该会明白@Adaptive整体的流程以及两种@Adaptive的定义,从代码层面上是怎么体现的,有何不同的。

文章到此就结束了,感谢观看。欢迎提bug ^_^


评论