dubbo中的spi 技术

我们从图中的

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

这句代码入手。

一、扩展加载器

我们先看getExtensionLoader(Protocol.class)这行代码,进入内部:

   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<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

这个type 是  interface com.alibaba.dubbo.rpc.Protocol;

ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);这个EXTENSION_LOADERS是一个currentHashMap.刚进入这个方法的时候 这个EXTENSION_LOADERS里面是没有值的,所以load ==null  所以进入了 下面的一个判断,

  EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));这串代码会调到上面的构造方法

我们刚才很明确的说了 type 是 interface com.alibaba.dubbo.rpc.Protocol;所以下面的三目运算符明显是false。于是会进入

ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()方法。这边继续调用了getExtensionLoader(ExtensionFactory.class)方法, 于是又进入了代码图上的流程, 只不过这个EXTENSION_LOADERS,放入了 一个type==interface com.alibaba.dubbo.common.extension.ExtensionFactory。 值是 一个ExtensionLoader的实例,只是这个实例的objectFactory==null;注意是interface com.alibaba.dubbo.common.extension.ExtensionFactory的objectFatory==null这里interface com.alibaba.dubbo.rpc.Protocol的obectFactory是运行getAdaptiveExtension()之后的值。

小结一

我看过了 ServiceConfig 里面也有这个扩展加载, 所以我想做一个初步总结(ps:老师说的,但是我加了一些自己的理解。如诺不对欢迎纠正)每一个dubbo:service或者dubbo:reference 标签被解析后,里面都有很多ExtensionLoader(cluster、protocol、proxyFactory),这些cluster、protocol 等等这些每一个类 或者说接口 都会对应一个ExtensionLoader,然后,ExtensionLoad中的objectFactory属性是不是空值, 得看type 是不是interface com.alibaba.dubbo.common.extension.ExtensionFactory,是则为空。否则不为空.

二、获取Protocol

我们继续进入getAdaptiveExtension()方法

    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        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.get()这个是从一个Holder的类型的类里面去缓存的值, Holder 其实就放了 一个属性(value)。在ExtensionLoad中Holder 被final 修饰, 意义为不可修改的(其实final修饰的字段是可以通过反射进行修改的但是要在修改之前运行 attr.setAcessible(ture))。具体的修改final 值的连接。Holder中value 又是被volatile修饰的(对于基本类型的修改可以在随后对多个线程的读保持一致,但是对于引用类型如数组,实体bean,仅仅保持引用的可见性,并不保证内容的可见性),

上个代码图中instance =null 执行两次是用了双检锁,提高了同步代码块的执行效率。

1)读取项目中对应的三个文件

下面调用getAdaptiveExtensionClass()->getExtensionClasses()->loadExtensionClasses();直接按照这个顺序。 我们可以看到

 private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        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));
                }
                if(names.length == 1) cachedDefaultName = names[0];
            }
        }
        
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }
    

关键方法就是loadFile();那么extensionClasses是一个hashMap 用来存放各个spi 解析出来扩展类的,感觉因为不管哪个reference或者service 调用这个他们的扩展工厂都是一样的所以可以用hashMap 而不用currentHashMap 等等线程安全的(理解不对望告知)。

我们再看看

DUBBO_INTERNAL_DIRECTORY、DUBBO_DIRECTORY、SERVICES_DIRECTORY三个变量到底是什么值, 请看下图。

------------------------------------------------------分割线-------------------------------------------------------------

2)读取文件,通过反射机制生成类

loadFile()方法我今天来写了,请看下面:

   private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        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.");
                                            }
                                            if (clazz.isAnnotationPresent(Adaptive.class)) {
                                                if(cachedAdaptiveClass == null) {
                                                    cachedAdaptiveClass = clazz;
                                                } else if (! cachedAdaptiveClass.equals(clazz)) {
                                                    throw new IllegalStateException("More than 1 adaptive class found: "
                                                            + cachedAdaptiveClass.getClass().getName()
                                                            + ", " + clazz.getClass().getName());
                                                }
                                            } else {
                                                try {
                                                    clazz.getConstructor(type);
                                                    Set<Class<?>> wrappers = cachedWrapperClasses;
                                                    if (wrappers == null) {
                                                        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                        wrappers = cachedWrapperClasses;
                                                    }
                                                    wrappers.add(clazz);
                                                } catch (NoSuchMethodException e) {
                                                    clazz.getConstructor();
                                                    if (name == null || name.length() == 0) {
                                                        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);
                                                            }
                                                        }
                                                    }
                                                    String[] names = NAME_SEPARATOR.split(name);
                                                    if (names != null && names.length > 0) {
                                                        Activate activate = clazz.getAnnotation(Activate.class);
                                                        if (activate != null) {
                                                            cachedActivates.put(names[0], activate);
                                                        }
                                                        for (String n : names) {
                                                            if (! cachedNames.containsKey(clazz)) {
                                                                cachedNames.put(clazz, n);
                                                            }
                                                            Class<?> c = extensionClasses.get(n);
                                                            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);
        }
    }

我们的type=META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol.开头的dir是要读取dubbo.jar中 的某个位置,具体的值 是上面三个值中的一个比如(META-INF/dubbo/internal/)所以

fileName=META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol,findClassLoad也是获取的ExtendFactory的一个类加载器,具体请自己看。首先这个classLoad不是null, 不管怎样获取BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream() "utf-8"));再之后我们正常的readline() 如果line 中有"#"号意思为注释我们就pass。之后按等于号分割,左边是key 右边是value,

这边会出现三种情况, 第一种如果类的注解有Adaptive.class。那么将会赋值给cachedAdaptiveClass,根据以下代码我们可以得知一个type 类型只能有一个子类有Adaptive注解,否则报错:

 (! cachedAdaptiveClass.equals(clazz)) {
                                                    throw new IllegalStateException("More than 1 adaptive class found: "
                                                            + cachedAdaptiveClass.getClass().getName()
                                                            + ", " + clazz.getClass().getName());
                                                }

第二如果读取的类的构造函数里面单放type类型的则放入cachedWrapperClasses中根据getConstructor(type)这句代码可以看出,

第三如果以上都不行则运行下面:如果一行读取后name 无值,则取默认值:


                                                            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);
                                                            }
                                                        

如果有值, 我们先按,号分割, 这说明同一个子类可以有多个名字。然后将之存入传进来的extesionClasses,而且读取的三个文件中如若相同的父类有相同的name即key 则报错,因为这三个文件的extesionClasses是同一个:

  Class<?> c = extensionClasses.get(n);
                                                            if (c == null) {
                                                                extensionClasses.put(n, clazz);
                                                            }

我们回到调取getExtensionClasses()方法的地方。:

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

我们来看看createAdaptiveExtensionClass()方法, 这个方法很总要,是另外一种proxy 的实现(不同于spring中), 很多人看不懂dubbo 就是因为这里。

3)编译动态代理类

private Class<?> createAdaptiveExtensionClass() {
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

这里用字符串拼接成一个code 然后通过编译器生成class 文件。这里我会把code 拿出来。

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.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);
}
}

这下如果debugProtocol 类型的时候, 看到Protocol$Adaptive 就不要好奇在哪出现的,而且dubbo 到底走哪个类是根据url 的protocol属性的值。。看上面代码就知道了,伏笔在getExtension(extName);正好利用了spi 技术。溜了12点(夜里)了睡觉。

猜你喜欢

转载自my.oschina.net/u/4042146/blog/2978861