Dubbo SPI

353 阅读17分钟

前面在介绍Java SPI的时候,说了SPI是一种服务发现机制, 那么有了Java SPI,为什么还需要Dubbo SPI了?这里在开头先介绍一下Dubbo SPI相比较于Java SPI有哪些优点, 后面我们在结合Java SPI和Dubbo SPI的源代码来说明是怎么实现这些功能了。

1. Java SPI会一次性实例化所有服务拓展点实现, 如果有拓展点实现类初始化非常耗时,但是发现加载好了有没有用上,会比较浪费资源。

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

3. 增加了拓展点AOP和IOC支持, 拓展点可以通过setter方法注入其他拓展点实例

使用方式

Java SPI:(使用的是DriverManager中代码)

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
    while(driversIterator.hasNext()){
        driversIterator.next();
    }
} catch(Throwable t) {
// Do nothing
}

Java SPI从_classpath*:META-INF/services_中和拓展点全路径相同的文件中加载拓展点实现

Mysql驱动jar包中的文件META-INF/services/java.sql.Driver内容就是:

com.mysql.jdbc.Drvier

Dubbo SPI: (这里截取的是Dubbo中使用SPI机制加载Protocol拓展点的代码)

ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(Constants.REGISTRY_PROTOCOL);                 //or                 .getAdaptiveExtension()                 .getActivateExtension()

其实在Dubbo SPI获取到ExtensionLoader实例之后还可以通过getAdaptiveExtension()获取动态的拓展点实现; getActivateExtension()获取。。。。? 这2个功能在讲解完基础功能之后再来讲解。

Dubbo SPI从以下目录

classpath*:META-INF/dubbo/ META-INF/services/ META-INF/dubbo/internal

中加载拓展点实现

Dubbo SPI源码

在了解了Dubbo SPI相比较于Java SPI有哪些优点和Dubbo SPI和Java Spi的使用方式之后,再通过源码来了解Dubbo SPI是怎么实现这些功能的。我们在这里阅读源码的切入点就是 ExtensionLoader.getExtensionLoader(Protocol.class)

.getExtension(Constants.REGISTRY_PROTOCOL);

这里最开始是通过ExtensionLoader.getExtensionLoader(clazz)来获取一个ExtensionLoader实例,然后再通过这个实例来获取指定名称的拓展点实现getExtension。

1. ExntesionLoader.getExtensionLoader

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

这里做了一些检查,但是最关键的还是new ExtensionLoader(type) , EXTENSION_LOADERS这里是对EextensionLoader实例做缓存,避免重复创建实例.

new ExtensionLoader(type): 这里的构造方法是一个私有的构造方法

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

主要功能是记录这个ExtensionLoader是哪个拓展点药获取拓展点实现: Class<?> type。

再就是创建了一个objectFactory:ExtensionFactory成员变量(这个objectFactory就是后面Dubbo SPI实现IOC基础)。

这里还说明一下,objectFactory:ExtensionFactory这个成员变量也是通过ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtesion()来获取拓展点实现的。这里ExtensionFactory因为明确了不需要其他的注入,所以这里的objectFactory就是Null值。

上面说完了怎么创建extensionLoader实例,再拿到extensionLoader实例,我们就可以通过getExtension(name)去获取指定名称的拓展点实现。

2. extensionLoader.getExtension(name)

public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

 阅读上面的代码,忽略一些缓存的细节,发现createExtension(name)才是最关键的,在其中才是创建指定名称拓展点的实现代码。

3. createExtension(name)

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

阅读createExtesion(name)的内容, 发现主要有4个主要关注点

1. Classs clazz = getExtensionClasses().get(name)

2. T instance = Clazz.newInstance

3. injectExtension(instance)

4. wrapperClass.getConsturcotor(type).newInstance(instance)

上面的方法基本都可以做到见名知意。

getExtensionClasses().get(name)

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

可以看到getExtensionClasses()里面也是做了一些缓存的工作的,但是最主要的功能实现点还是_loadExtensionClasses()_。

// synchronized in getExtensionClasses
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;
}

可以看到loadExtesionClassess的工作就是从classpath*:META-INF/dubbo/ ; META-INF/services ; META-INF/dubbo/internal这3个目录中加载type对应Class的全路径相同的文件中加载文件内容,文件内容格式:

_name:_拓展点实现类全路径

具体的加载工作在loadFile中完成,(这里不具体讲解这个读取的细节,后面可能会带一点,这个内容其实也比较简单,不需要详细说), 加载完成的结果就会保存在Map<String,Class<?>> extensionClasses这个Map中,同时会在这个结果返回之后保存在Holder中做为一个缓存. (这个是在getExtensionClasses里面,回过头去看一下就知道了)

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();

public class Holder<T> {

    private volatile T value;

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

    public T get() {
        return value;
    }

}

这里的Holder就是包装类,里面使用volatile变量来保存数据,保证多线程环境下的数据可见性. (这里并不是我们讨论的主要关注点,轻轻带过)

在这里我们了解了getExtensionClasses()的主要内容,就是去dubbo指定的3个目录下去加载指定拓展点的有哪些拓展实现,并将这些拓展实现类Class加载进来。

这里说到加载拓展实现类的Class到JVM中来,就插入一下

浏览loadFile中的内容,可以看到是使用class.forName(line,true,classLoader)方式将拓展点实现的Class对象加载到JVM中来的。(还有种方式classLoader.loadClass)

clazz.newInstance

在通过getExtensionClassess().get(name)获取到指定名称拓展点实现的Class对象后,就可以通过clazz.newInstance的反射获取拓展点实现的实例.

这里的关于clazz.newInstance反射创建拓展点实现类实例没有什么好说的,需要注意的一点就是clazz.newInstance是使用的默认无参构造方法创建的实例,如果没有无参构造函数是会抛出异常的。

InjectExtension(instance)

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            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);
    }
    return instance;
}

injectExtension(instance)这里就是Dubbo SPI中增加的IOC功能的实现.

injectExtension要注入的对象也是带有setxxx方法的成员变量,说明它也是通过setxxx方法反射注入的。 对于注入进入的实例是通过objectFacotry.getExtension(pt,property)来获取的.

在injectExtension中要注入的对象明确了(带setxxx方法的成员变量需要注入),用什么方法注入也知道了(使用setxxx method反射注入实例到成员变量), 那么就来看看是怎么获取这个注入的实例的: objectFactory.getExtension(pt,proeprty)

这里的objectFactory就是在ExtensionLoader实例创建的时候初始化的ExtensionFactory实例: 

这里获取到的ExtensionFactory的adaptive实现就是AdaptiveExtensionFactory这个类(这里我们先跳过getAdaptiveExtension()这个方法实现,先直接得到结果,后面我们再讲getAdaptvieExtension()这个方法)

AdaptiveExtensionFactory

可以看到在AdaptiveExtensionFactory.getExtension中的行为也是遍历ExtensionFactory的实现类,调用它们的getExtension(type,name)方法来获取实例的。 这里AdaptiveExtensionFactory只是做了一个聚合作用。

在ExtensionFactory这个拓展点的所有实现中,只要SpringExtensionFactory和SpiExtensionFactory才是真正获取实例的实现。

SpringExtensionFactory

可以看到SpringExtensionFactory是使用ApplicationContext从Spring IOC容器中获取实例的,然后给Dubbo SPI完成injectExtension的。

SPIExtensionFactory

在SPIExtensionFactory中才是通过ExtensionLoader.getExtensionLoader(), 然后通过getAdaptiveExtension从Dubbo SPI自己的IOC容器中获取实例来完成injectExtension中的注入动作. (这里Dubbo SPI自己的IOC容器就是ExtensionLoader中的各种cache,对extensionLoader实例的cache,对拓展点实现类实例的cache等等,来完成和Spring beanFactory类似的功能)

wrapperClass.getConsturcotor(type).newInstance(instance)

在wrapperClass.getConstrutor(type).newInstance(instance)中其实是完成了Dubbo SPI中的AOP功能,接下来就了解一下适合怎么实现的.

首先来看看cachedWrapperClasses是哪里来的,跟踪cachedWrapperClasses成员变量的赋值, 最终在loadFile发现了它的赋值。(也就是前面getExtensionClasses()中涉及的从文件中读取拓展点实现的配置的地方)

loadFile

可以看到这里是只有存在clazz.getConstructor(type):也就是有带拓展点Class的构造函数的拓展点实现类才加入到cachedWrapperClasses里面, 如果没有的话就会抛出NoSuchMethodException,也就是下面这张图中的处理逻辑。

那么有带拓展点Class的构造函数拓展类的拓展实现类是什么了?拓展实现类实现拓展点这个接口,在其中还有带拓展点接口Class的构造函数,这想一下不就是静态代理(Proxy)设计模式的类结构么。

搞明白了是哪些是cachedWrapperClasses之后, 再来看Dubbo Spi的Aop功能

这里这里是使用的for循环来处理的wrapperClasses,那么也就是说明如果存在多个代理Proxy, 这里也是一层层的将它们套起来,完成多个代理的工作, 那么多个代理的顺序是怎么 样的了? Answer: 在loadeFile中是一行行的读取的配置,然后add到cachedWrapperClasses中的, 那么多个代理的顺序就是拓展点配置文件中的顺序。 那么如果是在多个文件中了?那么首先肯定是按照目录顺序来的,因为读取的时候时候这样的

META-INF/dubbo/internal -> META-INF/dubbo/ -> META-INF/services

那么这里还有个问题了:如果这里instance是代理本身实现类实例怎么做,是不是也会进行一遍遍的代理? Instance是通过clazz.newInstance方法也就是默认构造方法创建的实例,如果拓展点代理实现类本身也有默认的构造方法话,这里确实是也会一遍遍代理的,但是一般拓展点代理实现类是没有默认构造方法的,只有一个带拓展点接口类的有参构造函数,所以如果故意的使用extensionLoder.getExtension(proxyName)来获取代理拓展类实现是会抛出异常的, 可以看到在createExtension中也是对异常进行了处理的。

好了,到这里我们就对Dubbo Spi中的AOP功能实现了解的差不多了, Dubbo Spi中的AOP实现主要是使用的静态代理模式,在实现的时候是有Proxy实现类的,不像是spring aop中那样通过切点/切面等一系列配置来动态的生成一个Proxy实现类(动态的意思是,在编程类结构中没有Proxy实现类的,在运行的时候才将这个Proxy实现了加载到JVM中来)

阶段总结

这里再总结一下Dubbo SPI中都有哪些角色参与了,来一张类图

ExtensionFactory负责实现的是Dubbo Spi中的IOC功能.

Java SPI

这里也顺便说一下Java SPI中也都有哪些角色参与了,同样也是类图

在Java SPI中是通过serviceLoader.iterator()中的LazyIterator完成配置文件的读取(META-INF/serivces下面)和对应Class加载和对应的实例化的.

ServiceLoader.load(servcie,loader)

public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader)
{
    return new ServiceLoader<>(service, loader);
}

这里就是简单的创建了一个serviceLoader实例, 和Dubbo SPI 中的ExtensionLoader.getExtensionLoader一样的,只是简单的记录一下要加载的拓展点的class对象. 它的主要工作还是在serviceLoader.iterator()中返回的LazyIterator的hasNext/next方法中实现的.

serviceLoader.lterator

public Iterator<S> iterator() {
    return new Iterator<S>() {

        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();

        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }

        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}

可以看到这个返回的iterator基本就是lookupIterator:LazyIterator; knownProviders这里是指做的缓存的作用.

来看LazyIterator中的hasNext和next功能

LazyIterator.hasNext

public boolean hasNext() {
    if (acc == null) {
        return hasNextService();
    } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
            public Boolean run() { return hasNextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}

private boolean hasNextService() {
    if (nextName != null) {
        return true;
    }
    if (configs == null) {
        try {
            String fullName = PREFIX + service.getName();
            if (loader == null)
                configs = ClassLoader.getSystemResources(fullName);
            else
                configs = loader.getResources(fullName);
        } catch (IOException x) {
            fail(service, "Error locating configuration files", x);
        }
    }
    while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
            return false;
        }
        pending = parse(service, configs.nextElement());
    }
    nextName = pending.next();
    return true;
}

在hasNext其实就是去读取META-INF/services/下和拓展点全路径相同的文件里面的配置,配置了有哪些实现类。

LazyIterator.next

public S next() {
    if (acc == null) {
        return nextService();
    } else {
        PrivilegedAction<S> action = new PrivilegedAction<S>() {
            public S run() { return nextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}
private S nextService() {
    if (!hasNextService())
        throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
             "Provider " + cn + " not found");
    }
    if (!service.isAssignableFrom(c)) {
        fail(service,
             "Provider " + cn  + " not a subtype");
    }
    try {
        S p = service.cast(c.newInstance());
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

在LazyIterator.next中就将hasNext中读取的配置的实现类Class加载到Jvm中来,可以看到的是也是使用的Class.forName(cn,false,loader)来加载的Class到JVM中,和Dubbo SPI中保持的是一直的。 下面就是紧接着的c.newInstance 来通过反射方式创建实现类的实例。

通过阅读ExtensionLoader和ServcieLoader的源码实现,我们发现了ExtensionLoader在获取指定名称的拓展点实现的时候只创建了指定实现类的实例,没有创建其他实现类的实例。(在最开是的时候,我还是以为Dubbo SPI比较与Java SPI是没有加载多余的Class对象了,其实是没有创建多余的实例)

Question: 如果这里不加载多余的Class对象进入JVM是否有必要?

@Adaptive

Dubbo Spi中的@Adaptive注解是用来做什么的?在实际场景中,一个拓展点接口往往有多种实现。因为Dubbo是基于URL驱动的,所以在运行时,可以通过传入的URL中某些参数来动态的控制具体实现,这就是@Adaptive要实现的Dubbo拓展点自适应实现。

(这里不是某些文章中说的动态代理实现, 而是一种动态选择功能实现,算是一种策略模式吧… 也不是很准确)

关于@Adaptive的使用

@Adaptive看这个注解的注释,可以知道这个注释一般用在类和方法上面。 在整个Dubbo中,@Adaptive使用在类上的也就那么几个类: AdaptiveExtensionFactory和AdaptiveCompiler。其他的都是使用在方法上。

@Adaptive用在类上面,表示拓展点子类会在里面进行Adaptive机制的自定义实现,算是个标记作用,说我这里是一个Adaptive机制的实现。

像前面介绍的AdaptiveExtensionFactory中所谓的Adaptive机制实现就是使用SpringExtensionFactory和SPIExtensionFactory来获取要注入的实例。 这里的Adaptive(自适应)就体现在可以通过Spring IOC容器和Dubbo IOC容器来获取bean来完成注入。

(上面的都是个人理解,有好的阐述理解请告诉我哈,让我也知道一下哈)

@Adaptive使用在方法上,(这里我们先将用在方法上dubbo会怎么做,然后就知道它是用来做什么的了), 使用extensionLoader.getAdaptiveExtension()就会动态的生成一个子类拓展点实现。这个动态生成的拓展点子类里面的方法都是什么内容了?

没有@Adaptive注解的方法,就在方法体里面抛出UnsupportedOperationException.

有@Adaptive注解的方法,就会按照一定格式重写该方法:主要就是获取方法中的URL参数,然后通过URL获取某个参数值,再就是通过ExtensionLoader找和这个参数值匹配的实现类的实例,最后就是拿这个实例来调用对应的方法实现了。

这里以Dubbo中的拓展点Protocol为例子

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

这里有两个方法标记了@Adaptive,那么在使用extensionLoader.getAdaptiveExtension的时候生成的动态子类Protocol$Adaptive的内容就是

public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");

    }

    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");

        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");

        org.apache.dubbo.common.URL url = arg0.getUrl();

        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());

        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        org.apache.dubbo.rpc.Protocol extension =
                (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }

    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null)
            throw new IllegalArgumentException("url == null");

        org.apache.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");

        org.apache.dubbo.rpc.Protocol extension =
                (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}

可以看到getDefaultPort和destory方法都是抛出了UnsupportOperatorException. exprot和refer方法也按照一定规则override了。

在大致了解了@Adaptive使用的方法上会做了哪些事情之后,我们来看看是怎么生成这个$Adaptive子类的。

最开始的入口当然是: ExtensionLoader.getExtensionLoader(type).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;
}

可以看到这里也是和getExtension中一样的,有缓存之类的处理呀,我们通通跳过,直奔具体实现_createAdaptiveExtension_.

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

这里也有对getAdptiveExtensionClass().newInstance()的adaptive动态生成子类实例的注入动作。(和前面的getExtension里面差不多, 就不讲了,还是回到怎么生成子类上来)

那么很显然了,生成子类这个动作肯定就在_getAdaptiveExtensionClass_()这里面完成了.

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

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

这里在getAaptiveExtensionClass中调用一下getExtensionClasses是干啥??

生成子类的动作看到了在createAdaptiveExtensionClass()里面,createAdaptiveExtensionClassCode()负责生成子类的原始代码,然后由Compiler编译加载成Class对象.

Compiler的内容就暂时不聊了,来看看_createAdaptiveExtensionClassCode_()生成的原始代码是什么样的. (emm,这个写的很长,我们就一段段的解释,不全贴上来了)

这校验了一下,如果在所有方法上都没有@Adaptive的话,就不用生成子类了;如果生成了类名就是Protocol$Adaptive。(这里只是举例说明,具体的类就不同了)

这里对于拓展点接口中没有@Adaptive标记的方法,在生成的子类方法中是抛出了UnsupportOperationnException.

下面的else就是处理的有@Adaptive注解标记的方法了:

这里就是在方法体中找有没有URL类型的参数,如果有就是这了,顺便判断一下是否为空,并将值赋给url变量。这个URL就是用来获取参数的,然后动态获取实现类调用方法的。

如果是在方法体中没有找到URL类型的参数就在方法体参数找有没有参数的get方法是返回URL类型的。

这里找到了的话也加了一下对method的返回值的null的判断,毕竟是通过get method给的返回值。

上面就是加入的判null的代码,下面的就是给url赋值的代码。(和前面从方法体中直接获取URL参数赋值对应上)

这里我们有了URL参数了,那么我们到底是去获取URL中的哪个参数的值,然后用来动态调用实现类的方法了?

可以看到获取参数,是用到的value[i]来指定去获取哪个参数的值的。

那么我回过头来上去看看这个value中是写什么值.

这里可以看到value默认的是@Adaptive注解中给定的值

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
  
    String[] value() default {};
}

可以看到这个@Adaptive的value是可以为空的,如果为空的话,就是用拓展点接口simpleName转换后的一个结果作为参数。

blog.csdn.net/weixin_3396… 这里面举例说明的拓展点接口FruitGranter, 那么这个url参数就是 fruit.granter ; 如果是Protocol接口,这个url参数就是protocol 。

那么知道是获取URL中的哪个参数之后, 就是那一段获取这个url参数值的代码了:

这里很好理解,就是判断这个url参数该用什么方法去获取,然后生成相应的代码。

那么这个url参数值的获取的响应的代码也生成完了之后,那么它用来干啥。

这里在得到url参数值后,给extName这个变量.

这个变量用在ExtensionLoader.getExtensionLoader.getExtension(extName)中,也就是去获取指定名称的实现类。拿到实现类extension实例之后,就调用@Adaptive标记的这个方法,执行具体的实现操作。

所以看到这里,@Adaptive的功能就是根据URL中的参数来动态的调用拓展点的不同子类的实现。

这里的URL参数要么是@Adaptive中指定的,要么就是拓展点接口变换来的.

这里的动态体现在可以根据url中的参数来选择不同的实现。

举例说明,如果URL是_dubbo://192.168.0.101:20880?protocol=dubbo_的话,那么在Protocol$Adaptive这个子类中选择的实现就是DubboProtocol中实现的方法。

如果是_dubbo://192.168.0.101:20880?protocol=http_话就是时候用的HttpProtocol中的实现的方法。

到最后,也基本明白了@Adaptive注解在方法到底是实现了个什么功能了.