Dubbo 源码分析之 SPI 详解

网友投稿 596 2022-09-01

Dubbo 源码分析之 SPI 详解

Dubbo 源码分析之 SPI 详解

前言

不得不说 ​​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.DubboEchocom.dfire.spi.JdkEcho

测试类:

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

执行后输出结果为

I am dubboI am jdk

目录结构如下

完成这个例子,大家就能简单了解了 ​​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 ExtensionLoader getExtensionLoader(Class 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 loader = (ExtensionLoader) EXTENSION_LOADERS.get(type); if (loader == null) { //创建ExtensionLoader对象 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type)); loader = (ExtensionLoader) 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 cachedAdaptiveInstance = new Holder();

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

public class Holder { 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> getExtensionClasses() { //首先从缓存中获得所有的实现类,如果发现为null 则进行初始化 Map> 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> 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> extensionClasses = new HashMap>(); //从 "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.RegistryProtocoldubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocolfilter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapperlistener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrappermock=com.alibaba.dubbo.rpc.support.MockProtocolinjvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocolrmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocolhessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocolcom.alibaba.dubbo.rpc.protocol.​​Java SPI​​​ 的区别,​​Dubbo​​​ 是使用 ​​name=class​​​ 的 ​​key-value​​​ 模式存储的实现类,至于这样有什么好处,我们继续进入 ​​loadFile​​ 方法分析

private void loadFile(Map> 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 urls; ClassLoader classLoader = findClassLoader(); //根据是否存在类加载器来使用不同的资源加载文件 if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if (urls != null) { //遍历文件 while (urls.hasMoreElements()) { java-.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> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet>(); 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 = (%

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

继续看最后一部分了

// 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 Exporter export(Invoker invoker) throws RpcException; @Adaptive Invoker refer(Class 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> 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); } }

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:使用 Shell 在多服务器上批量操作(使用时间最悠久的能源形式是什么能源)
下一篇:程序员求职找工作的4条实用建议!(程序员10月份找工作难找)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~