前言

不得不说 ​​Dubbo​​​的自定义 ​​spi​​ 减轻了扩展者的负担,但减轻负担的代价是使用大量晦涩难懂的代码,调用层次深,使阅读者步步维艰。为了避免后来者再像我当初那样看源码的吃力,特意把我的阅读见解分享下来,希望大家一起学习。

我使用的是 ​​maven​​​ 仓库最新的 ​​dubbo2.6.5​​​ 的源码,在社区看到要 ​​2.7​​ 版本就要出了。为了更方便的学习,我们可以等这个版本掌握差不多了再看更高的版本。

SPI

究竟什么是 ​​SPI​​​ 呢,​​SPI​​​ 的英文名为 ​​Service Provider Interface​​​ 顾名思义服务提供者接口,是面向开发者的。使用 ​​SPI​​​ 我们可以把接口实现的全限定名放在文件中,通常在 ​​META-INF/services​​​ 目录下,文件名为接口名,文件内容为接口的实现类。由服务器加载读取配置文件,加载实现类,这样在运行时动态为接口替换实现类。正因此特性,我们可以很容易的通过 ​​SPI​​ 机制为我们的程序提供拓展功能。

其实 ​​JDK​​​ 自带了 ​​SPI​​ 功能。

JDK SPI

接口及其实现类

public interface Echo {

void echo();
}
public class DubboEcho implements Echo {
@Override
public void echo() {
System.out.println("I am dubbo");
}
}
public class JdkEcho implements Echo {
@Override
public void echo() {
System.out.println("I am jdk");
}
}

资源文件目录为:​​META-INF/services/接口全限定名​​​ 比如我的:​​META-INF/services/com.dfire.spi.Echo​

com.dfire.spi.DubboEcho
com.dfire.spi.JdkEcho

测试类:

public class App {

public static void main(String[] args) {
ServiceLoader<Echo> says = ServiceLoader.load(Echo.class);
for (Echo echo : says) {
echo.echo();
}
}
}

执行后输出结果为

I am dubbo
I am jdk

目录结构如下

Dubbo 源码分析之 SPI 详解_自适应


完成这个例子,大家就能简单了解了 ​​JDK SPI​​​ 。如果想要新增接口只需写个实现类配置在文件中即可。那么 ​​Dubbo​​​ 为什么没有使用​​JDK SPI​​​而使用了自定义​​SPI​​呢?

首先说我们能看到的缺点

  • 一次性实例化所有的扩展点实现,即使该实现类没有用上,浪费资源

看不到的缺点

  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:​​JDK​​​ 标准的​​ScriptEngine​​​ ,通过​​getName​​​ 获取脚本类型的名称,但如果​​RubyScriptEngine​​​ 因为所依赖的​​jruby.jar​​​ 不存在,导致​​RubyScriptEngine​​​ 类加载失败,这个失败原因被吃掉了,和​​ruby​​​ 对应不起来,当用户执行​​ruby​​​ 脚本时,会报不支持​​ruby​​,而不是真正失败的原因。

Dubbo SPI

​Dubbo SPI​​​除了修复 ​​JDK SPI​​​ 的缺点外还增加了​​AOP​​​和​​IOC​​功能。


在提供者的入口类 ​​ServiceConfig​​ 中有这样一行代码

    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

其中 ​​ExtensionLoader​​​ 类即是 ​​Dubbo SPI​​​ 的实现类。
我们就从这行代码逐步分析。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null)
throw new IllegalArgumentException("Extension type == null");
if(!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if(!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type +
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
}

//根据接口在缓存中获得ExtensionLoader对象
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
//创建ExtensionLoader对象
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
  • 在前两行首先是进行判空和该类是否为接口
  • 继续从缓存中根据接口类型来获得​​ExtensionLoader​​ 对象,如果为空就创建
  • 进入​​ExtensionLoader​​ 构造方法
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

哇,好复杂。
简单分析下
首先判断 ​​​type​​​ 是否为 ​​ExtensionFactory​​​类,如果是那么 ​​objectFactory​​​ 就为​​null​​​。如果不是就获得 ​​ExtensionFactory​​​ 的​​自适应扩展类​​​。​​ExtensionLoader.getExtensionLoader(T.class).getAdaptiveExtension()​​​ 这行代码其实就是我们刚刚进入分析的入口。下面继续分析 ​​getAdaptiveExtension​​ 方法。

获得所有的扩展类

public T getAdaptiveExtension() {
//检测缓存中是否存在自适应扩展类实现
Object instance = cachedAdaptiveInstance.get();
//double check
if (instance == null) {
if(createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//新建
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;
}

此方法,主要是为了获得自适应扩展类。首先从缓存的 ​​cachedAdaptiveInstance​​ 中检测是否已经存在。

private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();

​cachedAdaptiveInstance​​​ 是 ​​Holder​​​ 类创建的一个对象查看一下 ​​Holder​​ 类

public class Holder<T> {

private volatile T value;

public void set(T value) {
this.value = value;
}

public T get() {
return value;
}

}

发现 ​​Holder​​​ 类中持有一个 ​​value​​​ 对象并且以 ​​volatile​​ 关键字修饰,保证其可见性,指令有序性。

继续往下分析,常见的 ​​double check​​​ ,如果不存在就进入 ​​createAdaptiveExtension​​ 方法进行创建。

private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
}

​createAdaptiveExtension​​​ 方法主要做了:调用​​getAdaptiveExtensionClass​​​ 方法获得自适应扩展类,通过反射 ​​newInstance​​​ 新建一个对象,然后使用 ​​injectExtension​​ 方法进入属性注入操作。

根据调用顺序,首先进入 ​​getAdaptiveExtensionClass​​ 方法

private Class<?> getAdaptiveExtensionClass() {
//加载配置文件的所有的类
getExtensionClasses();
//如果cachedAdaptiveClass已经加载过,直接返回
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//无自适应扩展类,动态创建自适应扩展类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

在 ​​getAdaptiveExtensionClass​​​ 方法中主要做了两件事,首先调用 ​​getExtensionClasses​​​ 方法,该方法会加载所有配置在文件中的类,其中如果有自适应扩展类,那么就会为 ​​cachedAdaptiveClass​​​ 赋值。而 ​​createAdaptiveExtensionClass​​ 会在发现没有自定义的自适应扩展类时进行动态生成。

那么首先进入 ​​getExtensionClasses​​ 方法

private Map<String, Class<?>> getExtensionClasses() {
//首先从缓存中获得所有的实现类,如果发现为null 则进行初始化
Map<String, Class<?>> classes = cachedClasses.get();
//double check
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//为空 则进行初始化加载类
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}

同样是 ​​double check​​​ ,首先根据缓存 ​​map​​​ 判断是否已经加载过,如果未加载过那么进行第一次加载,并设置缓存。那么就进入加载扩展类的方法中 ​​loadExtensionClasses​

private Map<String, Class<?>> loadExtensionClasses() {
//首先获得接口类的注解@SPI
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
//如果接口类的@SPI注解不为null
if(defaultAnnotation != null) {
//获得接口注解值
String value = defaultAnnotation.value();
if(value != null && (value = value.trim()).length() > 0) {
//只能有一个
String[] names = NAME_SEPARATOR.split(value);
if(names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
//设置默认的实现类名称为接口类注释@SPI的value
//tip:这里要记住哦!
if(names.length == 1) cachedDefaultName = names[0];
}
}

Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
//从 "META-INF/dubbo/internal/" 目录下解析实现类
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
//从 "META-INF/dubbo/" 目录下解析实现类
loadFile(extensionClasses, DUBBO_DIRECTORY);
//从 "META-INF/services/" 目录下解析实现类
loadFile(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}

该方法大致内容是:

  • 查看该接口类是否具有​​SPI​​​ 注解,有的话则获得注解的值,并赋值给​​cachedDefaultName​​ 作为自适应扩展实现的默认值
  • 去指定的文件路径下解析该接口的实现类的配置信息

在看 ​​loadFile​​​ 方法之前首先附上 ​​Dubbo SPI​​​ 的一个资源文件 ​​META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol​​,方便大家理解。

registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol
injvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocol
rmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
com.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocol
thrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocol
memcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocol
redis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocol

我们可以明显发现和 ​​Java SPI​​​ 的区别,​​Dubbo​​​ 是使用 ​​name=class​​​ 的 ​​key-value​​​ 模式存储的实现类,至于这样有什么好处,我们继续进入 ​​loadFile​​ 方法分析

private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
//获得文件的路径名,比如 type 为Protocol.class,dir 为 META-INF/dubbo/internal/
//那么fileName = "META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol"
String fileName = dir + type.getName();
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
//根据是否存在类加载器来使用不同的资源加载文件
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
//遍历文件
while (urls.hasMoreElements()) {
java.net.URL url = urls.nextElement();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
try {
String line = null;
//读取文件内容
while ((line = reader.readLine()) != null) {
//过滤注释信息
final int ci = line.indexOf('#');
if (ci >= 0) line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
//根据 = 字符进行切分 左边为名称,右边为类名
int i = line.indexOf('=');
if (i > 0) {
//名称
name = line.substring(0, i).trim();
//类
line = line.substring(i + 1).trim();
}

if (line.length() > 0) {
//加载类
Class<?> clazz = Class.forName(line, true, classLoader);
if (! type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + "is not subtype of interface.");
}
//判断该加载类是否具有 @Adaptive 注解
if (clazz.isAnnotationPresent(Adaptive.class)) {
//如果当前加载类有 @Adaptive 注解并且缓存的自适应注解类为空 那么为缓存设置
if(cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
//如果 cachedAdaptiveClass 缓存已经设置 那么抛出异常 @Adaptive 放在所有接口实现类的其中一个类上面 即:自适应扩展类只能有一个
} else if (! cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getClass().getName()
+ ", " + clazz.getClass().getName());
}
//如果没有 @Adaptive 注解
} else {
try {
//首先判断该加载类是否具一个参数为自己的构造方法 如果不抛出 NoSuchMethodException 异常,那么将其加入包装(装饰)类集合中
//tip:这里要记住哦

clazz.getConstructor(type);
//可能有一个或多个
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
} catch (NoSuchMethodException e) {
//是否有默认(空)构造方法
clazz.getConstructor();
//判断配置文件中 name 是否为 null
if (name == null || name.length() == 0) {
//如果为 null 就根据 type 自动生成一个
name = findAnnotationName(clazz);
if (name == null || name.length() == 0) {
if (clazz.getSimpleName().length() > type.getSimpleName().length()
&& clazz.getSimpleName().endsWith(type.getSimpleName())) {
name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
} else {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
}
}
}
//分割 name
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
//判断类上是否有 @Activate 注解 如果有则以第一个 name 作为 key Activate 对象作为val 放入缓存中
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
//遍历所有 name
for (String n : names) {
//判断缓存的class -> name 映射中是否存在 不存在则添加
if (! cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
//判断 name -> class 的映射 map 中是否已经添加过,进行添加/抛异常操作
//tip:extensionClasses 是我们在 loadExtensionClasses 方法中创建的 map, 用来存放我们解析的结果
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
} // end of while read lines
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", class file: " + url + ") in " + url, t);
}
} // end of while urls
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " +
type + ", description file: " + fileName + ").", t);
}
}

代码很长,需要大家仔细去看,具体的注释我已经写的很清楚。
总结一下这段代码的作用

  • 解析在​​"META-INF/dubbo/internal/","META-INF/dubbo/" ,"META-INF/services/"​​​ 等目录下的​​SPI​​ 资源文件
  • 发现实现类具有​​@Adaptive​​​ 注解,即自定义自适应扩展类,则为缓存​​cachedAdaptiveClass​​​ 赋值,很重要,别忘记我们为什么在执行到​​getAdaptiveExtensionClass​​​ 方法时进入​​getExtensionClasses​​ 方法,就是为了得到自适应扩展类
  • 查看实现类是否具有一个参数为接口类的构造方法,有的话则把该实现类放入修饰类缓存中
  • 查看实现类是否具有​​@Activate​​ 注解,存在的话放入缓存
  • 将解析​​SPI​​​ 资源文件 以​​class -> name​​​ 的方式 放入缓存​​map​​​ 的​​cachedNames​​ 中

加载 ​​SPI​​​ 资源这一部分算是结束了,那么我们继续回到 ​​getAdaptiveExtensionClass​​ 方法中。

private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

我们通过上面分析知道 ​​getExtensionClasses​​​ 方法会在存在 ​​@Adaptive​​​ 注解的实现类时为 ​​cachedAdaptiveClass​​​ 赋值。在这里如果发现 ​​cachedAdaptiveClass != null​​​ 则直接返回,否则进入动态创建自适应扩展类方法 ​​createAdaptiveExtensionClass​​。

这里才是我们理解 ​​Dubbo SPI​​ 关键,大家要耐心、仔细往下看。

private Class<?> createAdaptiveExtensionClass() {
//动态生成自适应扩展类代码
String code = createAdaptiveExtensionClassCode();
//获得类加载器
ClassLoader classLoader = findClassLoader();
//获得 Compiler 自适应扩展类
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
//编译代码生成 class
return compiler.compile(code, classLoader);
}

这个方法内大概内容就是动态生成自适应扩展类代码,使用 ​​Compiler​​ 编译该代码。

我们主要看的在 ​​createAdaptiveExtensionClassCode​​​ 方法内,先多嘴一句,动态生成自适应扩展类代码需要保证接口内具有 ​​@Adaptive​​ 注解的方法

由于该方法内容较长,我决定一段一段分析,大家可以对照源码阅读。在下面文章中,如果存在 ​​/* */​​ 注释则表示省略了一段代码,注释内容为省略代码的主要功能,大家可以注意一下。

[] methods = type.getMethods();
//判断方法是否具有 @Adaptive 注解
boolean hasAdaptiveAnnotation = false;
for(Method m : methods) {
if(m.isAnnotationPresent(Adaptive.class)) {
hasAdaptiveAnnotation = true;
break;
}
}
// 完全没有 Adaptive 方法,则不需要生成 Adaptive 类
if(! hasAdaptiveAnnotation)
throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");

这部分主要是判断接口内是否存在 ​​@Adaptive​​ 注解的方法,如果不存在,那么直接抛异常。

//生成包名
codeBuidler.append("package " + type.getPackage().getName() + ";");
//导入ExtensionLoader
codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
//生成类名(比如: Protocol$Adpative) 并且 实现接口 该接口
codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adaptive" + " implements " + type.getCanonicalName() + " {");

很简单,看注释就好了

for (Method method : methods) {
//返回类型
Class<?> rt = method.getReturnType();
//参数类型
Class<?>[] pts = method.getParameterTypes();
//异常类型
Class<?>[] ets = method.getExceptionTypes();


Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
//如果当前方法没有 @Adaptive 注解,则在实现类的该方法体中抛出异常
if (adaptiveAnnotation == null) {
code.append("throw new UnsupportedOperationException(\"method ")
.append(method.toString()).append(" of interface ")
.append(type.getName()).append(" is not adaptive method!\");");
} else {
//其它操作
}

这段代码主要是判断当前接口方法是否存在 ​​@Adaptive​​ 注解,如果不存在,则在实现方法中添加抛异常代码

for (Method method : methods) {
/* 参数获取 */
if (adaptiveAnnotation == null) {
/*不存在 @Adaptive 注解*/
} else {
int urlTypeIndex = -1;
//判断该方法的参数中是否存在 URL 类型的 有的话进行标记
for (int i = 0; i < pts.length; ++i) {
if (pts[i].equals(URL.class)) {
urlTypeIndex = i;
break;
}
}
// 有类型为URL的参数
if (urlTypeIndex != -1) {
// Null Point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
urlTypeIndex);
code.append(s);

s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
code.append(s);
}
// 参数没有URL类型
else {
//...
}
//...
}
// 其它
}

这里主要是检测方法的参数中是否具有 ​​URL​​​ 类型的参数。
继续看参数没有 ​​​URL​​ 类型的时候处理方式

for (Method method : methods) {
/* 参数获取 */
if (adaptiveAnnotation == null) {
/*不存在 @Adaptive 注解*/
} else {
/*获得 URL 参数位置*/
if (urlTypeIndex != -1) {
/* 参数有URL*/
}
// 参数没有URL类型
else {
String attribMethod = null;

// 遍历所有参数 从参数类型的所有方法中 查看 get* 方法并且返回类型为 URL
LBL_PTS:
for (int i = 0; i < pts.length; ++i) {
Method[] ms = pts[i].getMethods();
for (Method m : ms) {
String name = m.getName();
if ((name.startsWith("get") || name.length() > 3)
&& Modifier.isPublic(m.getModifiers())
&& !Modifier.isStatic(m.getModifiers())
&& m.getParameterTypes().length == 0
&& m.getReturnType() == URL.class) {
//记录 URL 参数的位置
urlTypeIndex = i;
//记录返回 URL 类型的方法名
attribMethod = name;
break LBL_PTS;
}
}
}
//如果为空 则表示所有方法的参数类型中不具有 URL 参数
if(attribMethod == null) {
throw new IllegalStateException("fail to create adative class for interface " + type.getName()
+ ": not found url parameter or url attribute in parameters of method " + method.getName());
}

// 空指针检测 生成的代码如:if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
urlTypeIndex, pts[urlTypeIndex].getName());
code.append(s);
//url参数判空,生成代码如下:if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
code.append(s);

//获得url参数,生成代码如下:com.alibaba.dubbo.common.URL url = arg0.getUrl();
s = String.format("%s url = arg%d.%s();",URL.class.getName(), urlTypeIndex, attribMethod);
code.append(s);
}
//其它
}
// 其它
}

这部分代码主要就是从接口的方法中遍历所有参数,查看参数是否具有 ​​URL​​​ 属性。至于为什么 ​​Dubbo​​​ 一直在查找​​URL​​​ 参数,是因为我们的所有信息在 ​​Dubbo​​​ 中都是以 ​​URL​​​ 为总线的,通过 ​​URL​​ 可以获得我们需要的信息。

继续往下分析

for (Method method : methods) {
/* 参数获取 */
if (adaptiveAnnotation == null) {
/*不存在 @Adaptive 注解*/
} else {
/* 获得 URL 参数位置*/
if (urlTypeIndex != -1) {
/* 参数有URL*/
}
// 参数没有URL类型
else {
/*从方法参数类型的方法中查找URL*/
}
//获得方法上具有 @Adaptive 注解的值
String[] value = adaptiveAnnotation.value();
// 没有设置value,则使用“扩展点接口名的点分隔 作为value
if(value.length == 0) {
char[] charArray = type.getSimpleName().toCharArray();
StringBuilder sb = new StringBuilder(128);
for (int i = 0; i < charArray.length; i++) {
if(Character.isUpperCase(charArray[i])) {
if(i != 0) {
sb.append(".");
}
sb.append(Character.toLowerCase(charArray[i]));
}
else {
sb.append(charArray[i]);
}
}
value = new String[] {sb.toString()};
}
//其它
}
// 其它
}

这部分也很简单,从方法注解上获得注解的值,可能有多个。如果不存在的话,根据接口名获得,遇见大写改为小写如果不是第一个符号还需要加 ​​.​​ 符号。

for (Method method : methods) {
/* 参数获取 */
if (adaptiveAnnotation == null) {
/* 不存在 @Adaptive 注解 */
} else {
//获得 URL 参数位置
if (urlTypeIndex != -1) {
/* 参数有URL */
}
// 参数没有URL类型
else {
/*从方法参数类型的方法中查找URL*/
}
/*获得 @Adaptive 注解的值*/
boolean hasInvocation = false;
//判断是否具有 Invocation 类型的参数
for (int i = 0; i < pts.length; ++i) {
if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
// 空指针检测 生成的代码如:if (arg0 == null) throw new IllegalArgumentException("invocation == null");"
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
code.append(s);
//获得方法名 生成的代码如:String methodName = arg0.getMethodName();
s = String.format("\nString methodName = arg%d.getMethodName();", i);
code.append(s);
hasInvocation = true;
break;
}
}
//cachedDefaultName 还记得上面我们会加载 SPI 资源文件时会设置该值吗?忘了可以回去看看 该值是从接口类的 @Adaptive 注解中获得的
String defaultExtName = cachedDefaultName;
//tip:这段代码通过递归方式生成 仔细观察下面的 for 循环语句
String getNameCode = null;
// 由于在方法上 @Adaptive 注解的值可能有多个,从后往前遍历 以最左为准,主要是为了兼容老版本扩展名参数。越[老]的扩展名参数越靠右,会被新的覆盖。默认扩展名为defaultExtName
for (int i = value.length - 1; i >= 0; --i) {
if(i == value.length - 1) {
//如果默认扩展名不为空
if(null != defaultExtName) {
//至于为什么特判 protocol,是因为 url 可以直接获得
if(!"protocol".equals(value[i]))
if (hasInvocation)
//存在 Invocation 参数 生成的代码如: url.getMethodParameter(methodName, "loadbalance", "random")
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
//生成代码如:url.getParameter("proxy", "javassist")
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
else
//生成代码如:( url.getProtocol() == null ? "dubbo" : url.getProtocol() )
getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
}
else {
if(!"protocol".equals(value[i]))
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
else
getNameCode = "url.getProtocol()";
}
}
else {
if(!"protocol".equals(value[i]))
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
else
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
code.append("\nString extName = ").append(getNameCode).append(";");
// check extName == null?
String s = String.format("\nif(extName == null) " +
"throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
type.getName(), Arrays.toString(value));
code.append(s);
//其它
}
// 其它
}

代码有些复杂,建议大家 ​​debug​​​ 查看。主要是为了获得 ​​extName​​ 的参数。

for (Method method : methods) {
/* 参数获取 */
if (adaptiveAnnotation == null) {
/* 不存在 @Adaptive 注解 */
} else {
//获得 URL 参数位置
if (urlTypeIndex != -1) {
/* 参数有URL */
}
// 参数没有URL类型
else {
/*从方法参数类型的方法中查找URL*/
}
/*获得 @Adaptive 注解的值*/
/* 生成 extName 的代码*/
//根据 extName 从 SPI 中获得实例,具体代码如:com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
code.append(s);

// 如果非 void 返回类型则生成返回值语句
if (!rt.equals(void.class)) {
code.append("\nreturn ");
}

//下面就是生成返回值,内容如: return extension.export(arg0);
s = String.format("extension.%s(", method.getName());
code.append(s);
for (int i = 0; i < pts.length; i++) {
if (i != 0)
code.append(", ");
code.append("arg").append(i);
}
code.append(");");
}
// 其它
}

这部分代码主要是为了生成返回值。

继续看最后一部分了

// public + 返回值全限定名 + 方法名 + (
codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");
// 添加参数列表代码
for (int i = 0; i < pts.length; i ++) {
if (i > 0) {
codeBuidler.append(", ");
}
codeBuidler.append(pts[i].getCanonicalName());
codeBuidler.append(" ");
codeBuidler.append("arg" + i);
}
codeBuidler.append(")");
// 添加异常抛出代码
if (ets.length > 0) {
codeBuidler.append(" throws ");
for (int i = 0; i < ets.length; i ++) {
if (i > 0) {
codeBuidler.append(", ");
}
codeBuidler.append(pts[i].getCanonicalName());
}
}
codeBuidler.append(" {");
codeBuidler.append(code.toString());
codeBuidler.append("\n}");

大致就到这里了,动态生成自适应扩展类大概就这么多,简单举个例子。
大家可以看​​​com.alibaba.dubbo.rpc.Protocol​​​接口,该 接口的实现类中不存在 ​​@Adaptive​​ 注解的实现类,所以在使用

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()

时会动态生成,还记得我们的生成的原则吧:

  • 判断方法是否存在​​@Adaptive​​​ 注解
    – 不存在:自适应类的实现方法中抛异常
    – 存在:判断方法参数是否存在​​​URL​​ 类的参数
package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI;

@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​​​ 接口我们发现,该类的默认 ​​SPI​​​ 扩展名为 ​​dubbo​​​,​​getDefaultPort​​​ 和 ​​destroy​​​ 方法无 ​​@Adaptive​​​ 注解。export 和 refer 具有 ​​@Adaptive​​​ 注解。自动生成的​​Protocol​​自适应类如下

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
//无 @Adaptive 接口 直接抛异常
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!");
}
//无 @Adaptive 接口 直接抛异常
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.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
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);
}

public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
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);
}
}

然后就要回到 ​​createAdaptiveExtension​​ 方法了

private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
}

​getAdaptiveExtensionClass().newInstance()​​​ 通过反射创建一个实例就不用说了,就剩下 ​​injectExtension​​​ 方法了,这个方法中也就是 ​​dubbo​​​ 的 ​​IOC​​ 了主要是为了给该实例注入值

private T injectExtension(T instance) {
try {
//判断objectFactory是否为null 通过构造方法我们可以知道主要是判断 type 是否等于ExtensionFactory.class
if (objectFactory != null) {
//遍历所有方法查看是否具有 set 方法并且参数只有一个
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class<?> pt = method.getParameterTypes()[0];
try {
//获得变量名
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
//对象工厂新建值 这边可以继续看我就不往下分析了
Object object = objectFactory.getExtension(pt, property);
//反射注入
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}

写了很多,需要大家慢慢消化。其实我这里不可能把 SPI 的全部源码分析的,该类中还有一些其它方法,不过核心都是在这片文章里面。希望大家能够由点到面,逐个击破。


最后还想再说一句,有一点需要注意的是在 ​​loadFile​​​ 时,如果发现一个类无 ​​@Adaptive​​​ 注解并且该类中具有接口类的构造方法,那么则任务该类为包装类,则向 ​​cachedWrapperClasses​​ 变量中缓存可以包装的类。

比如 ​​ProtocolFilterWrapper​​​ 和 ​​ProtocolListenerWrapper​​​ 均可以包装 ​​Protocol​​ 的实现类。

public class ProtocolFilterWrapper implements Protocol {

private final Protocol protocol;

public ProtocolFilterWrapper(Protocol protocol){
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}
public ProtocolListenerWrapper(Protocol protocol){
if (protocol == null) {
throw new IllegalArgumentException("protocol == null");
}
this.protocol = protocol;
}

具体的包装实现是在​​ExtensionLoader​​​类的​​createExtension​​方法中

private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
//判断是否存在包装类
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for (Class<?> wrapperClass : wrapperClasses) {
//对实例进行包装
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
//返回包装后的实例
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}