Dubbo SPI之Adaptive详解

前期准备

一. 增加pom

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>dubbo</artifactId>
   <version>2.5.3</version>
</dependency>

二. 添加代码

1. shuqi.dubbotest.spi.adaptive.AdaptiveExt2 作为需要被扩展的接口,注意要加上@SPI注解

package shuqi.dubbotest.spi.adaptive;

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

/**
 * @author linyang on 18/4/20.
 */
@SPI
public interface AdaptiveExt2 {
    @Adaptive
    String echo(String msg, URL url);
}

2. 上面接口的三个实现类

a. shuqi.dubbotest.spi.adaptive.DubboAdaptiveExt2
package shuqi.dubbotest.spi.adaptive;

import com.alibaba.dubbo.common.URL;

/**
 * @author linyang on 18/4/20.
 */
public class DubboAdaptiveExt2 implements AdaptiveExt2 {

    public String echo(String msg, URL url) {
        return "dubbo";
    }
}
b. shuqi.dubbotest.spi.adaptive.SpringCloudAdaptiveExt2
package shuqi.dubbotest.spi.adaptive;

import com.alibaba.dubbo.common.URL;

/**
 * @author linyang on 18/4/20.
 */
public class SpringCloudAdaptiveExt2 implements AdaptiveExt2 {

    public String echo(String msg, URL url) {
        return "spring cloud";
    }
}
c. shuqi.dubbotest.spi.adaptive.ThriftAdaptiveExt2
package shuqi.dubbotest.spi.adaptive;

import com.alibaba.dubbo.common.URL;

/**
 * @author linyang on 18/4/20.
 */
public class ThriftAdaptiveExt2 implements AdaptiveExt2 {

    public String echo(String msg, URL url) {
        return "thrift";
    }
}

3. 在Resource目录下,添加/META-INF/dubbo/internal/shuqi.dubbotest.spi.adaptive.AdaptiveExt2文件,里面的内容

dubbo=shuqi.dubbotest.spi.adaptive.DubboAdaptiveExt2
cloud=shuqi.dubbotest.spi.adaptive.SpringCloudAdaptiveExt2
thrift=shuqi.dubbotest.spi.adaptive.ThriftAdaptiveExt2

上车 just do it!

测试一:SPI注解中有value值

@SPI("dubbo")
public interface AdaptiveExt2 {
    @Adaptive
    String echo(String msg, URL url);
}
    @Test
    public void test1() {
        ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class);
        AdaptiveExt2 adaptiveExtension = loader.getAdaptiveExtension();
        URL url = URL.valueOf("test://localhost/test");
        System.out.println(adaptiveExtension.echo("d", url));
    }
dubbo

测试二:SPI注解中有value值,URL中也有具体的值

@SPI("dubbo")
public interface AdaptiveExt2 {
    @Adaptive
    String echo(String msg, URL url);
}
    @Test
    public void test2() {
        ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class);
        AdaptiveExt2 adaptiveExtension = loader.getAdaptiveExtension();
        URL url = URL.valueOf("test://localhost/test?adaptive.ext2=cloud");
        System.out.println(adaptiveExtension.echo("d", url));
    }
spring cloud

测试三:SPI注解中有value值,URL中也有具体的值,实现类上有@Adaptive注解

为ThriftAdaptiveExt2类添加@Adaptive注解

@Adaptive
public class ThriftAdaptiveExt2 implements AdaptiveExt2 {
    public String echo(String msg, URL url) {
        return "thrift";
    }
}
@SPI("dubbo")
public interface AdaptiveExt2 {
    @Adaptive
    String echo(String msg, URL url);
}
    @Test
    public void test1() {
        ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class);
        AdaptiveExt2 adaptiveExtension = loader.getAdaptiveExtension();
        URL url = URL.valueOf("test://localhost/test?adaptive.ext2=cloud");
        System.out.println(adaptiveExtension.echo("d", url));
    }
thrift

测试四:SPI注解中有value值,实现类上没有@Adaptive注解,在方法上打上@Adaptive注解,注解中的value与链接中的参数的key一致,链接中的key对应的value就是spi中的name,获取相应的实现类。

@SPI("dubbo")
public interface AdaptiveExt2 {   
    @Adaptive({"t"})
    String echo(String msg, URL url);
}
    @Test
    public void test1() {
        ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class);
        AdaptiveExt2 adaptiveExtension = loader.getAdaptiveExtension();
        URL url = URL.valueOf("test://localhost/test?t=cloud");
        System.out.println(adaptiveExtension.echo("d", url));
    }
cloud

结论:

从上面的几个测试用例,可以得到下面的结论:1. 在类上加上@Adaptive注解的类,是最为明确的创建对应类型Adaptive类。所以他优先级最高。2. @SPI注解中的value是默认值,如果通过URL获取不到关于取哪个类作为Adaptive类的话,就使用这个默认值,当然如果URL中可以获取到,就用URL中的。3. 可以再方法上增加@Adaptive注解,注解中的value与链接中的参数的key一致,链接中的key对应的value就是spi中的name,获取相应的实现类。

源码分析

下面我们带着上面的结论,看一下源码。首先我们从这句话开始讲起

ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.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!");
        }
        /**
         * 这个类型必须加上SPI注解,否则报错
         */
        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中
             */
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

先检查有没有带SPI的注解,没有带,直接报错,从缓存中根据这个类型查询对应的ExtensionLoader,查不到就创建一个,再放入缓存中。dubbo中的spi部分大量利用了本地缓存,后续出现,不再着重讲解了。我们可以看一下他的创建该类型的ExtensionLoader的方法。

private ExtensionLoader(Class<?> type) {
        this.type = type;
        /**
         * type如果是ExtensionFactory类型,那么objectFactory是null,否则是ExtensionFactory类型的适配器类型
         */
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

关注点有两个,第一个构造方法是私有的,说明不想通过外部实例化,将实例化的过程统一收紧。第二个是objectFactory这个在后面的ioc部分会发挥它的作用,敬请期待。好了,目前为止,ExtensionLoader<AdaptiveExt2> loader = ExtensionLoader.getExtensionLoader(AdaptiveExt2.class) 说的差不多了,下面进入我们的大头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;
    }

第一次从缓存中获取就创建

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

获取到适配器类的Class,利用反射创建适配器类的实例。injectExtension是dubbo的DI,依赖注入。如果适配器类有属性的set方法,会自动注入,这个后续会开一个章节进行解释。看来我们最终要关注的是getAdaptiveExtensionClass方法。大家跟紧了,大片开始了。

    private Class<?> getAdaptiveExtensionClass() {
        /**
         * 触发SPI流程的扫描
         */
        getExtensionClasses();
        /**
         * 如果通过上面的步骤可以获取到cachedAdaptiveClass直接返回,如果不行的话,就得考虑自己进行利用动态代理创建一个了
         */
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        /**
         * 利用动态代理创建一个扩展类
         */
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

按照顺序来吧,看下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 Map<String, Class<?>> loadExtensionClasses() {
        /**
         * 获取到类型的SPI注解,所以利用SPI扩展点的地方,需要加入SPI注解
         */
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if ((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));
                }
                /**
                 * 如果注解中有value,说明有默认的实现,那么将value放到cachedDefaultName中
                 */
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }

        /**
         * 从下面的地址中加在这个类型的数据的extensionClasses中,地址包括
         * META-INF/dubbo/internal/
         * META-INF/dubbo/
         * META-INF/services/
         */
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

获取type上的SPI注解,如果里面有值赋给cachedDefaultName这个变量,相当于是个默认的值。随后从META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/这三个路径下搜索对应的文件,什么是对应的?就是命名是这个type类的全限定名称。下面这个方法有点长,希望不要看吐,还好加了注解,希望看起来会通畅点。


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);
                                            /**
                                             * 判断是新加载clazz是否是type的子类,不是报错
                                             */
                                            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)) {
                                                if (cachedAdaptiveClass == null) {
                                                    /**
                                                     * 将此类作为cachedAdaptiveClass
                                                     */
                                                    cachedAdaptiveClass = clazz;
                                                }
                                                /**
                                                 * 多个adaptive类的实例,报错
                                                 */
                                                else if (!cachedAdaptiveClass.equals(clazz)) {
                                                    throw new IllegalStateException("More than 1 adaptive class found: "
                                                            + cachedAdaptiveClass.getClass().getName()
                                                            + ", " + clazz.getClass().getName());
                                                }
                                            }
                                            /**
                                             * 如果这个类,没有Adaptive注解
                                             */
                                            else {
                                                try {
                                                    /**
                                                     * 看看这个类,有没有此类型的构造方法,主要是Wrapper类使用,如果有,说明是个wrapper类
                                                     */
                                                    clazz.getConstructor(type);
                                                    Set<Class<?>> wrappers = cachedWrapperClasses;
                                                    if (wrappers == null) {
                                                        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                        wrappers = cachedWrapperClasses;
                                                    }
                                                    /**
                                                     * 将此wrapper放入wrappers容器里面,也就是cachedWrapperClasses里面
                                                     */
                                                    wrappers.add(clazz);
                                                } catch (NoSuchMethodException e) {
                                                    /**
                                                     * 如果不是wapper类型的数据,肯定会抛异常,被捕获,到了这里,说明是个正常的类
                                                     */
                                                    clazz.getConstructor();
                                                    /**
                                                     * 如果name没有写的话,就使用默认的规则生成一个
                                                     */
                                                    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 activate = clazz.getAnnotation(Activate.class);
                                                        if (activate != null) {
                                                            //存在,就往cachedActivates里面添加名称与注解
                                                            cachedActivates.put(names[0], activate);
                                                        }
                                                        for (String n : names) {
                                                            if (!cachedNames.containsKey(clazz)) {
                                                                //将此类型的实例与name放入cachedNames
                                                                cachedNames.put(clazz, n);
                                                            }
                                                            Class<?> c = extensionClasses.get(n);
                                                            if (c == null) {
                                                                //最后往extensionClasses添加名称与class
                                                                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);
        }
    }

路径+文件名,组成了一个具体的文件名,根据 classLoader.getResources 方法获取到Enumeration<java.net.URL> 类型的对象,随后就是遍历,文件里面的格式是这样的。

dubbo=shuqi.dubbotest.spi.adaptive.DubboAdaptiveExt2
cloud=shuqi.dubbotest.spi.adaptive.SpringCloudAdaptiveExt2
thrift=shuqi.dubbotest.spi.adaptive.ThriftAdaptiveExt2

如果有#,按照#分割,前面的是我们需要的业务数据,后面是注释。我们所需要的数据中如果有=,那么就按照=进行分割,前面是这个扩展的名称,后面是这个扩展的全限定类名,方便利用反射加载进来。加载进来之后会判断下是不是传入类(type)类型的实例,不是的话,报错!如果有的类上带有@Adaptive注解,那么将这个类赋值给cachedAdaptiveClass,注意这个点,查询type类型适配器类的时候会优先寻找cachedAdaptiveClass,因为是系统指定的适配器类,优先级最高,可以看下我们上面的测试三,说的就是这种情况。如果有多个实现再类上都打上了@Adaptive注解,会报错:标准的适配器类只能有一个。如果这个扩展类没有打上@Adaptive注解就更有意思了。首先第一步会验证下有没有type这个类型作为入参的构造方法,为什么要这么做?因为Wrapper,有的类型需要包装一下,例如type=Protocol.class 就会看到有DubboProtocol真实的Protocal类,还会有ProtocolFilterWrapper和ProtocolListenerWrapper这种Wrapper类,这种Wrapper类的共同点就是构造函数的入参是type类型,所以在解析的时候有这么一步。如果有这种构造函数的就是Warpper类,将这些Warpper类型的数据放到cachedWrapperClasses这个集合中缓存。如果没有这种类型的构造函数,就是正常的type类型的实例了,如果在文件中没有声明这个扩展的名称(=左边的部分),就会根据这个类名创建一个名称。然后进入下一个环节@Activate数据的解析,这个本来是下一节的内容,我们提前了解下吧。查看type类上有没有@Activate注解,如果有的话,将名称与注解放到cachedActivates这个Map中进行缓存。将扩展类和名称放入cachedNames这个Map中进行缓存,将名称和扩展类的class放入传递进来的extensionClasses中,最后这个extensionClasses会被返回出来被使用。OK,到目前为止我们结束了getExtensionClasses方法的讲解,是不是很绕,东西很多。再回来我们看下剩下的。

private Class<?> getAdaptiveExtensionClass() {
        /**
         * 触发SPI流程的扫描
         */
        getExtensionClasses();
        /**
         * 如果通过上面的步骤可以获取到cachedAdaptiveClass直接返回,如果不行的话,就得考虑自己进行利用动态代理创建一个了
         */
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        /**
         * 利用动态代理创建一个扩展类
         */
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

如果cachedAdaptiveClass不为空就返回,什么情况下不为空?当扩展类上打上@Adaptive注解的时候,就会将这个类直接返回。如果没有上注解,怎么办,就得自己生成了,也就是createAdaptiveExtensionClass

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

思路很简单,将类以字符串的形式拼接出来,然后利用编译器进行编译,返回编译后的class对象。寻找编译器的过程和具体编译的过程不是我们此次所要关心的,我们关心的是这个createAdaptiveExtensionClassCode方法创建的字符串格式的数据是啥样的,用到了哪些数据。又是一个大方法,也是要走起的,come on

 /**
     * 创建适配器的扩展类的String
     * <p>
     * <p>
     * 创建这个适配器的扩展类,有几个前提:
     * 1. 必须有SPI的注解
     * 2. 被SPI声明的接口中至少一个方法有Adaptive注解。
     * ProxyFactory
     * 下面是他的说明:
     * 当声明再方法上的Adaptive中的value的作用就是,从URL中获取key,value,例如ProxyFactory
     *
     * @return
     * @Adaptive({Constants.PROXY_KEY})==========="proxy" <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException;
     * <p>
     * 如果URL中是dubbo:xxxx?proxy=jdk,而SPI中的值是javassist,那么就是
     * <p>
     * String extName = url.getParameter("proxy", "javassist");  //结果是jdk
     */
    private String createAdaptiveExtensionClassCode() {
        StringBuilder codeBuidler = new StringBuilder();
        /**
         * 获取这个类型的所有的方法
         */
        Method[] methods = type.getMethods();
        boolean hasAdaptiveAnnotation = false;
        /**
         * 遍历所有方法,至少有一个方法打了Adaptive的注解,否则报错
         */
        for (Method m : methods) {
            if (m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // no need to generate adaptive class since there's no adaptive method found.
        if (!hasAdaptiveAnnotation)
            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");

        codeBuidler.append("package " + type.getPackage().getName() + ";");
        codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
        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的注解
             */
            Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
            StringBuilder code = new StringBuilder(512);
            /**
             * 如果这个方法没有注解,添加不支持调用此方法的异常
             */
            if (adaptiveAnnotation == null) {
                code.append("throw new UnsupportedOperationException(\"method ")
                        .append(method.toString()).append(" of interface ")
                        .append(type.getName()).append(" is not adaptive method!\");");
            } else {
                int urlTypeIndex = -1;
                /**
                 * 寻找列表中的类型是URL.class,记录他的位置,数据放到urlTypeIndex中
                 */
                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);
                }
                // did not find parameter in URL type
                else {
                    String attribMethod = null;

                    // find URL getter method
                    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) {
                                urlTypeIndex = i;
                                attribMethod = name;
                                break LBL_PTS;
                            }
                        }
                    }
                    if (attribMethod == null) {
                        throw new IllegalStateException("fail to create adaptive class for interface " + type.getName()
                                + ": not found url parameter or url attribute in parameters of method " + method.getName());
                    }

                    // Null point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
                            urlTypeIndex, pts[urlTypeIndex].getName());
                    code.append(s);
                    s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
                            urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
                    code.append(s);

                    s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
                    code.append(s);
                }

                /**
                 * 获取adaptive注解的value
                 */
                String[] value = adaptiveAnnotation.value();
                /**
                 * 如果value没有设置,那么将使用类的名称作为key
                 */
                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()};
                }

                boolean hasInvocation = false;
                /**
                 * 如果参数列表中有Invocation的实例
                 */
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
                        // Null Point check
                        String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
                        code.append(s);
                        s = String.format("\nString methodName = arg%d.getMethodName();", i);
                        code.append(s);
                        hasInvocation = true;
                        break;
                    }
                }

                /**
                 * defaultExtName为spi注解中的value
                 */
                String defaultExtName = cachedDefaultName;
                String getNameCode = null;
                for (int i = value.length - 1; i >= 0; --i) {
                    if (i == value.length - 1) {
                        /**
                         * 如果defaultExtName 存在,spi注解中的value存在
                         */
                        if (null != defaultExtName) {
                            /**
                             * value[i]的值不等于"protocol"
                             */
                            if (!"protocol".equals(value[i])) {
                                if (hasInvocation) {
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);

                                } else {
                                    /**
                                     *     value[i]="proxy"
                                     *     defaultExtName="javassist"
                                     *
                                     *    String extName = url.getParameter("proxy", "javassist");
                                     */
                                    getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);

                                }
                            } else {
                                getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                            }
                        } else {
                            /**
                             * 如果defaultExtName 不存在,spi注解中的value不存在
                             */
                            if (!"protocol".equals(value[i])) {
                                if (hasInvocation) {
                                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                                } else {
                                    /**
                                     * url.getParameter("proxy") 没有默认值
                                     */
                                    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);

                s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                        type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
                code.append(s);

                // return statement
                if (!rt.equals(void.class)) {
                    code.append("\nreturn ");
                }

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

            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(ets[i].getCanonicalName());
                }
            }
            codeBuidler.append(" {");
            codeBuidler.append(code.toString());
            codeBuidler.append("\n}");
        }
        codeBuidler.append("\n}");
        if (logger.isDebugEnabled()) {
            logger.debug(codeBuidler.toString());
        }
        return codeBuidler.toString();
    }

首先寻找这个类中所有的方法,查看方法中有没有打@Adaptive注解的,一个没有,直接报错!对于那些没有加@Adaptive注解的方法,直接在要创建的Adaptive类上增加此方法不支持操作的异常。在方法中的@Adaptive是可以加上value值的,如果用户填了,使用此值,没有填将使用程序根据类名创建的值作为value值,这个value值通URL中的参数名保持一致。defaultExtName是SPI中的value值,这里可以看一下我们的测试四的方法。最后我们看一下,他生成的String是什么样子的

package shuqi.dubbotest.d;

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

public class AdaptiveExt2$Adpative implements shuqi.dubbotest.spi.adaptive.AdaptiveExt2 {
    public java.lang.String echo(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = url.getParameter("t", "dubbo");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(shuqi.dubbotest.spi.adaptive.AdaptiveExt2) name from url(" + url.toString() + ") use keys([t])");
        shuqi.dubbotest.spi.adaptive.AdaptiveExt2 extension = (shuqi.dubbotest.spi.adaptive.AdaptiveExt2) ExtensionLoader.getExtensionLoader(shuqi.dubbotest.spi.adaptive.AdaptiveExt2.class).getExtension(extName);
        return extension.echo(arg0, arg1);
    }
}

将上面这个字符串编译成Class对象,作为适配器类,返回,然后实例化后,进行依赖注入需要的属性,随后缓存,备下次使用。

适用场景

基本上所有类型的动态导入都是使用adaptive,使用范围极广。

测试源码

dubbo-test测试源码

预告,看这里

下一篇: Dubbo SPI 之Activate详解

END

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容