什么是自适应机制
上篇讲解Dubbo SPI机制的文章中提到,Dubbo里可以通过ExtensionLoader.getExtensionLoader(XXXClass).getExtension(key)的形式来获取接口的某个实现类。
但这种形式本质上还是通过硬编码的形式在代码中固定的获取了接口的一个实现,诸如Protocol(实现有Dubbo、Redis、Thrift等),或者Transporter(实现有Netty、Mina等)这些接口,我们是可以在Dubbo服务声明时指定具体实现的。如图所示:
那么Dubbo是如何实现根据用户指定的参数来找到对应的接口实现类的呢?
先给出结论:Dubbo接口在拓展方法被调用时,根据运行时参数(URL中参数)动态生成接口的Adaptive自适应拓展类,在生成的拓展方法中根据URL中用户指定的参数key,调用
ExtensionLoader.getExtensionLoader(XXXClass).getExtension(key)来获取具体的实现类
那么上面提到的运行时动态生成的拓展类代码是什么样的呢?以Protocol接口为例
先看下包含自适应方法的Protocol接口定义,export,refer是2个自适应的方法(用 @Adaptive注解修饰)
@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(); }
Protocol接口生成的自适应扩展类,类名为Protocol$Adaptive
,代码如下
package com.alibaba.dubbo.rpc; import com.alibaba.dubbo.common.extension.ExtensionLoader; public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol { public void destroy() { throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy()" + " of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); } public int getDefaultPort() { throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol" + ".getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); } public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException { if (arg1 == null) throw new IllegalArgumentException("url == null"); com.alibaba.dubbo.common.URL url = arg1; String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); 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); } public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); com.alibaba.dubbo.common.URL url = arg0.getUrl(); String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); 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); } }
普通方法getDefaultPort
、destroy
在自适应拓展类的实现中,直接抛出异常
自适应方法,如refer方法,内部基本实现思路有两点
- 从URL中获取接口实现extName
- 根据extName调用ExtensionLoader.getExtension(extName)获取实现类
先从URL中获取拓展类实现的名称extName(Protocal的默认SPI实现为Dubbo协议)
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
然后根据参数中的extName加载最终实现类,调用实现类的refer方法
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName)
看到这,可能大家对自适应机制已经有个大概的了解了,其关键就是自适应类的生成和Dubbo SPI中ExtensionLoader的使用。下面先从注解入手,开始分析自适应机制的原理。
@Adaptive注解
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Adaptive { String[] value() default {}; }
@Adaptive注解可以作用在类上、接口上。
1.作用在类上:在Dubbo中只有2个类上有Adaptive注解修饰,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory,表示拓展的加载逻辑由人工编码完成,即硬编码实现,举个例子,AdaptiveCompiler的comiple方法不需要在运行时根据URL中参数动态生成,直接代码中固定写死
2.作用在接口上:如上文提到的,Adaptive注解作用在接口上时,能在程序运行时,根据参数(URL中参数)动态生成接口的Adaptive自适应拓展类,下面针对此种情况做具体分析
ExtensionLoader#getAdaptiveExtension
自适应拓展类加载时,调用的是ExtensionLoader#getAdaptiveExtension
方法
自适应类相关缓存定义
//缓存的自适应扩展类实例 private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>(); //缓存的自适应扩展类 private volatile Class<?> cachedAdaptiveClass = null;
代码如下,同样的先从缓存查询,没有再创建
public T getAdaptiveExtension() { //查询缓存 Object instance = cachedAdaptiveInstance.get(); //缓存中没有,双重检查锁定 if (instance == null) { if (createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { //调用createAdaptiveExtension创建自适应拓展类的实例 instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); } } } } else { throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; }
createAdaptiveExtension
方法如下
private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
做的事情比较简单,分为如下几步,重点是getAdaptiveExtensionClass
方法
- 调用getAdaptiveExtensionClass方法获取自适应扩展类Class
- 调用反射创建自适应扩展类实例
- 为实例调用injectExtension注入其依赖的对象
getAdaptiveExtensionClass方法如下
private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
分为两步
- getExtensionClasses获取所有接口的SPI扩展类实现,比如Protolcol接口的实现类有:
同时,如果Adaptive注解被修饰在类上,会将该类加入cachedAdaptiveClass缓存中,即上面提到的AdaptiveCompiler 和 AdaptiveExtensionFactory不会再动态生成自适应拓展类,直接返回缓存中的Class
2.第二步就是调用createAdaptiveExtensionClass
方法创建自适应拓展类,代码如下
private Class<?> createAdaptiveExtensionClass() {
//动态生成自适应拓展类代码(字符串拼装)
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
//调用JavaAssist,Jdk等编译器对生成的代码进行编译
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
如注释里说的,首先拼装出自适应拓展类的代码,再调用编译器编译代码,生成Class对象
自适应代码生成
createAdaptiveExtensionClassCode代码较长,但最终生成结果不复杂,Protocal接口生成的自适应拓展类代码Protocol$Adaptive已经在文章开头贴过了,就不重复贴了,只讲解代码中几个关键步骤
- 校验方法中是否有Adaptive注解,没有则抛出异常
- 导入package包信息
实现类只依赖ExtensionLoader - 普通方法不生成拓展方法
普通方法的拓展方法如下
public void destroy() {
throw new UnsupportedOperationException(“method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy()” +
" of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
4.else逻辑中的实现即为具体自适应拓展方法的实现,具体实现有几个关键,一是根据URL及Adaptive中注解定义的key值获取具体的实现类的key值,如果Adaptive注解中没有指定value,则获取SPI注解中的默认值,什么意思呢?以Transporter#connect的自适应实现为例
优先从URL中获取Adaptive中指定value的拓展实现,获取不到时,再获取SPI中指定的默认实现
从URL获得了extName时,拼装出
ExtensionLoader.getExtensionLoader(XXXClass).getExtension(extName),达到根据参数配置来调用具体实现的效果,即如图所示代码
自适应代码编译
到上面为止,动态生成的代码已经有了,但是要在JVM里运行,光有字符串形式的代码是不够的,需要对代码进行编译处理
ClassLoader classLoader = findClassLoader(); com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader);
Compiler本身也是一个自适应实现,默认是JavaAssist的实现,具体实现在JavassistCompiler中,有兴趣的自己看,JavaAssist相关内容不在本文讨论范围