Dubbo 源码分析之 SPI 详解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/su20145104009/article/details/86221050

前言

不得不说 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

目录结构如下
在这里插入图片描述


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

首先说我们能看到的缺点

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

看不到的缺点

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

Dubbo SPI

Dubbo SPI除了修复 JDK SPI 的缺点外还增加了AOPIOC功能。


在提供者的入口类 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>();

cachedAdaptiveInstanceHolder 类创建的一个对象查看一下 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=classkey-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 的方式 放入缓存mapcachedNames

加载 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 注解的方法

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

        Method[] 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 扩展名为 dubbogetDefaultPortdestroy 方法无 @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 方法了,这个方法中也就是 dubboIOC 了主要是为了给该实例注入值

  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 变量中缓存可以包装的类。

比如 ProtocolFilterWrapperProtocolListenerWrapper 均可以包装 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);
        }
    }
展开阅读全文

没有更多推荐了,返回首页