【Dubbo】Adaptive

这里指的Adaptive是dubbo中的一个注解:@Adaptive。从这个注解的定义上我们可以看到@Target({ElementType.TYPE, ElementType.METHOD}),它表明@Adaptive可以用在类、接口和方法上。
@Adaptive代表dubbo的SPI的动态适应能力,如果@Adaptive注解在扩展点实现类上那个该扩展点就是一个包装真实扩展点实例的装饰类;如果注解在方法上那么扩展点的实例就是一个动态代理类,例如Protocol$Adaptive对象。

package com.alibaba.dubbo.common.extension;
/**
 * Provide helpful information for {@link ExtensionLoader} to inject dependency extension instance.
 *
 * @see ExtensionLoader
 * @see URL
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    /**
     * Decide which target extension to be injected. The name of the target extension is decided by the parameter passed
     * in the URL, and the parameter names are given by this method.
     * <p>
     * If the specified parameters are not found from {@link URL}, then the default extension will be used for
     * dependency injection (specified in its interface's {@link SPI}).
     * <p>
     * For examples, given <code>String[] {"key1", "key2"}</code>:
     * <ol>
     * <li>find parameter 'key1' in URL, use its value as the extension's name</li>
     * <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li>
     * <li>use default extension if 'key2' doesn't appear either</li>
     * <li>otherwise, throw {@link IllegalStateException}</li>
     * </ol>
     * If default extension's name is not give on interface's {@link SPI}, then a name is generated from interface's
     * class name with the rule: divide classname from capital char into several parts, and separate the parts with
     * dot '.', for example: for {@code com.alibaba.dubbo.xxx.YyyInvokerWrapper}, its default name is
     * <code>String[] {"yyy.invoker.wrapper"}</code>. This name will be used to search for parameter from URL.
     *
     * @return parameter key names in URL
     */
    String[] value() default {};
}

dubbo为什么要设计adaptive?

adaptive设计的目的是为了识别固定已知类和扩展未知类。

注解在类上和注解在方法上的区别?

注解在扩展点实现类上:

代表人工实现,实现一个装饰类(设计模式中的装饰模式),它主要作用于固定已知类,目前整个系统只有2个,AdaptiveCompiler、AdaptiveExtensionFactory。

  • a. 为什么AdaptiveCompiler这个类是固定已知的?
    因为整个框架仅支持Javassist和JdkCompiler;
  • b. 为什么AdaptiveExtensionFactory这个类是固定已知的?
    因为整个框架仅支持2个objFactory,一个是spi,另一个是spring;
    ExtensionLoader.getAdaptiveExtension方法会直接返回这个类的实例

注解在扩展点接口方法上

代表自动生成和编译一个动态的Adpative类,含有@Adaptive的方法中都可以根据方法参数动态获取各自需要真实的扩展点。它主要是用于SPI,因为spi的类是不固定、未知的扩展类,所以设计了动态$Adaptive类;
ExtensionLoader.getAdaptiveExtension方法会返回动态编译生成的$Adaptive

例如: Protocol的spi类有injvm、dubbo、registry、filter、listener等很多未知扩展类,ExtensionLoader.getAdaptiveExtension会动态编译Protocol$Adaptive的类,再通过在动态累的方法中调用ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(spi类);来提取对象。

Adaptive加载流程

在dubbo中一般会首先通过ExtensionLoader.getAdaptiveExtension获取Adaptive扩展。这个方法会首先在扩展点接口的所有实现类中查找类上是否有含有@Adaptive注解,如果有这样的类直接返回该类的实例,如果没有则会查找扩展点接口的方法是否有@Adaptive注解并动态编译一个类实现该接口并扩展这些含有@Adaptive注解的方法。

  • 代码执行流程
-----------------------getAdaptiveExtension()
-->getAdaptiveExtension()//为cachedAdaptiveInstance赋值
  -->createAdaptiveExtension()
    -->getAdaptiveExtensionClass()
      -->getExtensionClasses()//为cachedClasses 赋值
        -->loadExtensionClasses()
          -->loadFile
      -->createAdaptiveExtensionClass()//自动生成和编译一个动态的adpative类,这个类是一个代理类
        -->ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension()
        -->compiler.compile(code, classLoader)
    -->injectExtension()//作用:进入IOC的反转控制模式,实现了动态入注
  • 每次只需要从缓存中取出AdaptiveInstance,如果没有命中则创建并加入缓存
     /**
     * @Author pengyunlong
     * @Description 每次只需要从缓存中取出AdaptiveInstance,如果没有命中则创建并加入缓存
     * @param
     * @Date 2018/6/14 17:03
     */
    @SuppressWarnings("unchecked")
    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;
    }
    /**
     * @Author pengyunlong 
     * @Description 获取AdaptiveExtensionClass字节码并创建实例然后IOC注入
     * @param 
     * @Date 2018/6/14 17:04
     */
    @SuppressWarnings("unchecked")
    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并缓存,getExtensionClasses()会将类上有@Adaptive注解的类缓存到cachedAdaptiveClass中,如果cachedAdaptiveClass没有值则需要动态编译一个AdaptiveExtension
    /**
     * @Author pengyunlong
     * @Description 扩展类上有@Adaptive注解的直接返回该类,如果没有则动态创建
     * @param
     * @Date 2018/6/14 16:28
     */
    private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
getExtensionClasses加载含有@Adaptive的扩展点实现类的过程
 /**
     * @Author pengyunlong
     * @Description 从指定目录读取Spi配置文件,分析class并加入缓存
     *              1.cachedAdaptiveClass   含有Adaptive注解的class
     *              2.cachedWrapperClasses  含有指定参数的构造方法的class
     *              3.cachedActivates       含有Activate注解的class
     *              4.cachedNames           其余的class
     * @param
     * @Date 2018/6/7 17:13
     */
    private void loadFile(Map<String, Class<?>> extensionClasses, String dir){
        String fileName = dir + type.getName();
      //遍历SPI文件的每一行配置
     while ((line = reader.readLine()) != null) {
              Class<?> clazz = Class.forName(line, true, classLoader);
         //扩展点实例类上有@Adaptive注解直接设置cachedAdaptiveClass
              if (clazz.isAnnotationPresent(Adaptive.class)) {
                        if(cachedAdaptiveClass == null) {
                           cachedAdaptiveClass = clazz;
             } else if (! cachedAdaptiveClass.equals(clazz)) {
                         throw new 
            }
            try {
               clazz.getConstructor(type);
               //如果扩展点实例的是含有type类型参数的构造方法则加入cachedWrapperClasses集合中
               Set<Class<?>> wrappers = cachedWrapperClasses;
                if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
              }
              wrappers.add(clazz);
           } catch (NoSuchMethodException e) {
              Activate activate = clazz.getAnnotation(Activate.class);
              if (activate != null) {
                  cachedActivates.put(names[0], activate);
               }
               //其余情况则将扩展点类名加入cachedNames,key为class
               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 
                        }
                  }
            }
      }
    }

关于loadfile的一些细节

  • cachedAdaptiveClass
    如果这个class含有adative注解就赋值,例如ExtensionFactory,而例如Protocol在这个环节是没有的。
  • cachedWrapperClasses
    只有当该class无adative注解,并且构造函数包含目标接口(type)类型,例如protocol里面的spi就只有ProtocolFilterWrapper和ProtocolListenerWrapper能命中
  • cachedActivates
    剩下的类,包含Activate注解
  • cachedNames
    剩下的类就存储在这里。
动态编译过程
  • 没有含有@Adaptive注解的扩展点实现类则调用createAdaptiveExtensionClassCode动态生成AdaptiveExtension的代码然后编译默认采用Javassist编译
/**
     * @Author pengyunlong 
     * @Description 动态生成代码然后编译
     * @param 
     * @Date 2018/6/14 16:38
     */
    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);
    }
  • createAdaptiveExtensionClassCode使用的模板文件adaptive-code-teamplet代码模板
package <扩展点接口所在包>;
 
public class <扩展点接口名>$Adpative implements <扩展点接口> {
    public <有@Adaptive注解的接口方法>(<方法参数>) {
        if(是否有URL类型方法参数?) 使用该URL参数
        else if(是否有方法类型上有URL属性) 使用该URL属性
        # <else 在加载扩展点生成自适应扩展点类时抛异常,即加载扩展点失败!>
         
        if(获取的URL == null) {
            throw new IllegalArgumentException("url == null");
        }
 
              根据@Adaptive注解上声明的Key的顺序,从URL获致Value,作为实际扩展点名。
               如URL没有Value,则使用缺省扩展点实现。如没有扩展点, throw new IllegalStateException("Fail to get extension");
 
               在扩展点实现调用该方法,并返回结果。
    }
 
    public <有@Adaptive注解的接口方法>(<方法参数>) {
        throw new UnsupportedOperationException("is not adaptive method!");
    }
}
  • ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();因为Compiler.class的扩展点实现类中AdaptiveCompiler又@Adaptive注解所以的返回值为AdaptiveCompiler,默认使用JavassistCompiler来解析并编译上一步生成好的代码;
@Adaptive
public class AdaptiveCompiler implements Compiler {

    private static volatile String DEFAULT_COMPILER;

    public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }

    public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        String name = DEFAULT_COMPILER; // copy reference
        if (name != null && name.length() > 0) {
            compiler = loader.getExtension(name);
        } else {
            compiler = loader.getDefaultExtension();
        }
        return compiler.compile(code, classLoader);
    }
}
package com.alibaba.dubbo.common.compiler.support;
/**
 * JavassistCompiler. (SPI, Singleton, ThreadSafe)
 */
public class JavassistCompiler extends AbstractCompiler {
    ......
    private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\s+([\\w\\.\\*]+);\n");
    @Override
    public Class<?> doCompile(String name, String source) throws Throwable {
       ......
        String[] packages = importPackages.toArray(new String[0]);
        matcher = EXTENDS_PATTERN.matcher(source);
        CtClass cls;
        if (matcher.find()) {
            String extend = matcher.group(1).trim();
            String extendClass;
            if (extend.contains(".")) {
                extendClass = extend;
            } else if (fullNames.containsKey(extend)) {
                extendClass = fullNames.get(extend);
            } else {
                extendClass = ClassUtils.forName(packages, extend).getName();
            }
            cls = pool.makeClass(name, pool.get(extendClass));
        } else {
            cls = pool.makeClass(name);
        }
        ......
        return cls.toClass(ClassHelper.getCallerClassLoader(getClass()), JavassistCompiler.class.getProtectionDomain());
    }
}
动态编译结果

Protocol所有扩展实现类上都没有@Adaptive注解,且扩展接口含有两个 @Adaptive 注解的方法:exporter() refer(),所以dubbo会生成一个动态类Protocol$Adaptive,且它实现Protocol接口来扩展这两个Adaptive方法。扩展点接口和最终动态生成Protocol$Adaptive类如下:

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

}
public class Protocol$Adaptive 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.Invoker refer(Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        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);
    }
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        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);
    }
}

解释一下export(com.alibaba.dubbo.rpc.Invoker arg0)方法:

  1. String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
    从arg0中解析出扩展点名称extName,extName的默认值为@SPI的value。这是adaptive的精髓:每一个方法都可以根据方法参数动态获取各自需要的扩展点。
  2. Protocol extension = (Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
    根据extName重新获取指定的Protocol.class扩展点。如果所有扩展点中含有Wrapper(listener,fiter)则ExtensionLoader.getExtension()会将真正的实现类通过Wrapper(listener,fiter)包装后返回。
  3. extension.export(arg0)
    执行目标类的目标方法

这样就实现了每一个方法都可以根据方法参数动态获取各自需要真实的扩展点。

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

推荐阅读更多精彩内容