dubbo SPI的自适应扩展机制

2 篇文章 0 订阅
2 篇文章 0 订阅

dubbo SPI的自适应扩展机制


上篇文章中我们已经讲解了dubbo SPI的简单应用,相应扩展类加载机制,并且介绍了dubbo AOP的实现方式。想要了解的可以参考 dubbo SPI应用与原理

本章中,我们将了解dubbo的IoC与SPI的自适应扩展机制的简单应用与原理。本文的源码是基于dubbo 2.7.5版本。

自适应扩展机制的作用

  • 有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。Dubbo是通过自适应扩展机制,根据URL参数配置动态加载指定的扩展点。

Adaptive注解

我们先来看一下与自适应扩展息息相关的一个注解,即@Adaptive。该注解定义如下

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

该注解的作用是决定哪个自适应扩展类被使用,该默认自适应扩展类是由dubbo URL中的参数决定(Dubbo在向外暴露服务的时候,会生成一个url,这个url包含有当前服务的配置项),当注解里的value值匹配URL中的参数key,该key的value作为目标拓展类名称。

  • 如果注解中有多个值,则根据下标从小到大去URL中查找有无对应的key,一旦找到就用该key的value作为默认自适应扩展类名称。
  • 如果这些值在url中都没有对应的key,使用SPI注解上的默认值,没有默认值则抛异常。

@Adaptive注解可以作用的类上与方法上,绝大部分情况下,该注解是作用在方法上,当 @Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理类Adaptive 注解在接口方法上,表示拓展的加载逻辑需由框架自动生成。注解在类上,表示拓展的加载逻辑由人工编码完成。下面我们来看下该注解的使用方法与原理。

Adaptive注解的使用

@Adaptive 加在类上

在 Dubbo 中,仅有两个类被@Adaptive注解了,分别是 AdaptiveCompilerAdaptiveExtensionFactory

在上一章SPI加载扩展类时我们分析到,loadClass方法会扫描带有@Adaptive注解的类并设置在缓存cacheAdaptiveClass中,该类就会成为默认的自适应扩展类。

下面是一个@Adaptive使用在类上的例子,接口还是一样,代码如下。

@SPI
public interface Car {
    String getBrand();
}

两个实现类分别如下,其中在BM类上加了@Adaptive注解,使其作为默认自适应扩展类。

public class Benz implements Car {
    @Override
    public String getBrand(URL url) {
        return "Benz";
    }
}

@Adaptive
public class BM implements Car {
    @Override
    public String getBrand(URL url) {
        return "BM";
    }
}

META-INF/services/下名为com.dubbo.dp.spi.Car的配置文件如下

benz=com.dubbo.dp.spi.Benz
bm=com.dubbo.dp.spi.BM

接下来验证是否BM会作为Car接口的自适应扩展类,代码如下

public static void main(String[] args) {
    Car car = ExtensionLoader.getExtensionLoader(Car.class).getAdaptiveExtension();
    System.out.println(car.getBrand(null));
}

输出

BM

ExtensionLoader.getExtensionLoader(Car.class)上个文章中已经分析过,用于获取接口对应的ExtensionLoader,这里不在赘述。getAdaptiveExtension()方法获取接口默认的自适应扩展点实例,因为是默认的,所以不需要参数。从结果可以看出,由于BM类上加了@Adaptive注解,所以BM类是作为Car接口的默认自适应扩展类。getAdaptiveExtension()方法的原理将在下面源码环节介绍。

@Adaptive 加在接口方法

@Adaptive注解加在方法上是通常的用法,注解里的value指定要从URL里面获取的参数key,所以自适应扩展类的方法里一定要含有dubbo的URL参数或可以获取url的对象实例,这是由dubbo加载自适应拓展类的逻辑决定的,稍后在源码环节分析。

下面是一个@Adaptive加在接口方法的例子,同时也是dubbo IOC的例子。
这是一个人拥有车的例子,首先还是Car接口,做的修改是在方法上加了@Adaptive,并且带有参数URL。

@SPI
public interface Car {
    @Adaptive("car")
    String getBrand(URL url);
}

它的两个实现类如下所示

public class Benz implements Car {
    @Override
    public String getBrand(URL url) {
        return "Benz";
    }
}

@Adaptive
public class BM implements Car {
    @Override
    public String getBrand(URL url) {
        return "BM";
    }
}

该接口的配置文件不变,跟上述例子一样。

同时增加一个Human类,接口中的carBrand方法表示这个人拥有的车品牌。接口如下

@SPI
public interface Human {
    String carBrand(URL url);
}

添加一个配置文件com.dubbo.dp.spi.Human,配置如下

rich=com.dubbo.dp.spi.RichHuman

该接口的实现类RichHuman如下:它持有一个Car对象,通过调用Car对象的getBrand()获取车的品牌。

public class RichHuman implements Human{

    private Car car;

    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String carBrand(URL url) {
        System.out.println("a rich human has a car");
        return car.getBrand(url);
    }
}

那么到底这个Car是怎么注入的呢,持有的Car对象是哪个实现类是由什么因素决定的呢?带着这些疑问,我们进行如下测试

    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("car","bm");
        URL url = new URL("dobbo","localhost",8081, map); //这里的URL格式为dobbo://localhost:8081?car=bm
        Human human = ExtensionLoader.getExtensionLoader(Human.class).getExtension("rich");
        System.out.println(human.carBrand(url));
    }

输出结果如下

a rich human has a car
BM

这里Human的类型为RichHuman,原因就是SPI的加载机制,这里不多解释。可以看到,这个例子中,RichHuman中持有的Car对象是BM类型,原因是URL传入了一个key-value为car=bm的参数,dubbo会从Car接口方法上的@Adaptive("car")获取到参数car,并去传入的URL参数中查询key为car的,所以获取到的value为bm,对应配置文件里的BM类。下面从源码角度解释其中的原理。

源码分析

dubbo的IOC

上一篇文章中我们了解到,当缓存中没有接口扩展类实例缓存时,会通过createExtension(String name)方法创建扩展类实例对象。这里为了方便,再次给出代码

private T createExtension(String name) {
        // 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的map,再根据拓展项名称从map中取出相应的拓展类即可
        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, clazz.newInstance());
                //然后从缓存中获取对应实例
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // 向实例中注入依赖,通过setter方法自动注入对应的属性实例
            injectExtension(instance);
            //从缓存中取出所有的包装类,形成包装链
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                // 循环创建 Wrapper 实例,形成Wrapper包装链
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException(".....");
        }
    }

getExtensionClasses()方法执行流程完成后,通过get(name)从map中获取到对应的拓展类,再通过反射创建实例。然后执行injectExtension(instance)向实例中注入依赖,这也就是dubbo的IOC,本例中是向获得的RichHuman类实例中注入Car依赖。injectExtension(instance)的源码如下

/**
     * Dubbo IOC 是通过 setter 方法注入依赖
     * 1. 遍历目标类中所有方法,找到set方法
     * 2. 然后利用objectFactory获取依赖的默认扩展类实例对象
     * 3. 利用反射调用set方法注入依赖
     * @param instance
     * @return
     */
    private T injectExtension(T instance) {
        //objectFactory为空就不执行属性注入,这个对象我将会在下面详细介绍,重点!!!
        if (objectFactory == null) {
            return instance;
        }
        try {
            // 遍历目标类的所有方法
            for (Method method : instance.getClass().getMethods()) {
                //如果不是setter方法就不注入,判断条件:1 public ,2 set开头 ,3 只有一个参数
                if (!isSetter(method)) {
                    continue;
                }
                //如果扩展点的setter方法上使用@DisableInject,在加载扩展点实例时就不会对这个方法的属性进行依赖注入
                if (method.getAnnotation(DisableInject.class) != null) {
                    continue;
                }
                // 获取 setter 方法参数类型
                Class<?> pt = method.getParameterTypes()[0];
                //该参数是否是java的基本数据类型,如String,Boolean,Number
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }

                try {
                    // 获取属性名,比如 setName 方法对应属性名 name
                    String property = getSetterProperty(method);
                    //通过 ObjectFactory 获取依赖对象,这里objectFactory 变量的类型为 AdaptiveExtensionFactory。
                    //AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory
                    //Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展
                    Object object = objectFactory.getExtension(pt, property);
                    if (object != null) {
                        // 通过反射调用 setter 方法设置依赖
                        method.invoke(instance, object);
                    }
                } catch (Exception e) {
                    logger.error(....);
                }

            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

Dubbo IOC 是通过 setter 方法注入依赖,这也是我们例子中有setCar(Car car)方法的原因。

  • 首先会判断objectFactory是否为空,这里objectFactory不为空,这是由于这里的objectFactory就是在new ExtensionLoader(Class<?> type)时获取,得到了默认自适应扩展类AdaptiveExtensionFactory,因为该类上加了@Adaptive注解。
  • 然后通过反射获取到该接口拓展类实例的所有方法,本例中就是RichHuman的实例,并遍历实例类方法列表。获取实例中的setter方法,并且没有@DisableInject注解,参数不是java的基本数据类型。对于符合条件的set方法,获取setter方法中的参数,作为需要注入的类型。通过objectFactory.getExtension获取自适应拓展类实例(注意此时方法传进的类型是setter方法上的类型,即Car接口),并调用set方法依赖设置到目标对象中,完成依赖注入。

由于这里的objectFactory就是AdaptiveExtensionFactoryAdaptiveExtensionFactory 内部维护了一个ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory,调用getExtension时其实就是遍历调用列表里其他类型的ExtensionFactorygetExtension方法。源码如下

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
    //内部维护了一个 ExtensionFactory 列表,,用于存储其他类型的 ExtensionFactory
    private final List<ExtensionFactory> factories;
  
   .........
   
   @Override
    public <T> T getExtension(Class<T> type, String name) {
        //遍历调用列表里其他类型的`ExtensionFactory`的`getExtension`方法
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}

Dubbo 目前提供了两种另外的 ExtensionFactory,分别是 SpiExtensionFactorySpringExtensionFactory。前者用于创建自适应的拓展实例,后者是用于从 Spring 的 IOC 容器中获取所需的拓展实例。3个ExtensionFactory类的关系如下图所示。
在这里插入图片描述

SpringExtensionFactory就是从Spring 的 IOC 容器中获取所需的拓展实例,这里我们重点看下SpiExtensionFactory,源码如下

public class SpiExtensionFactory implements ExtensionFactory {
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}

SpiExtensionFactorygetExtension方法首先就是判断一下Class是不是SPI接口,再获取接口的ExtensionLoader,并执行getAdaptiveExtension()方法获取自适应扩展对象。注意这里也用到了去获取接口的自适应扩展类实例的getAdaptiveExtension()方法。获取完接口的自适应扩展类实例后,将其注入目标对象中,那么依赖注入就完成了!至于getAdaptiveExtension是如何获取自适应扩展类的,下面进行分析。

获取自适应拓展实例

getAdaptiveExtension 方法是获取自适应拓展对象的入口方法,源码如下

    public T getAdaptiveExtension() {
        // 从缓存中获取自适应拓展
        Object instance = cachedAdaptiveInstance.get();
        //缓存未命中
        if (instance == null) {
            if (createAdaptiveInstanceError != null) {
                throw new IllegalStateException(...);
            }
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        // 创建自适应拓展实例
                        instance = createAdaptiveExtension();
                        // 设置实例到缓存中
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException(....);
                    }
                }
            }
        }
        return (T) instance;
    }

getAdaptiveExtension 方法首先会检查缓存,缓存未命中,则调用 createAdaptiveExtension 方法创建自适应拓展实例并放入缓存。下面,我们看一下 createAdaptiveExtension 方法的代码。

    private T createAdaptiveExtension() {
        try {
            // 获取自适应拓展类,并通过反射实例化,再注入相应依赖
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException(...);
        }
    }

createAdaptiveExtension 方法的代码比较少,但却包含了三个逻辑,分别如下:

  1. 调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
  2. 通过反射进行实例化
  3. 调用 injectExtension 方法向拓展实例中注入依赖

前两个逻辑比较好理解,第三步用于向自适应拓展对象中注入依赖。这是由于Dubbo 中有两种类型的自适应拓展,一种是手工编码的(即加@Adaptive注解的类),一种是自动生成的(@Adaptive注解加在方法上)。手工编码的自适应拓展中可能存在着一些依赖,而自动生成的 Adaptive 拓展则不会依赖其他类。这里调用 injectExtension 方法的目的是为手工编码的自适应拓展注入依赖,这一点需要大家注意一下。关于 injectExtension 方法,前面已经分析过,这里不再赘述。

接下来分析getAdaptiveExtensionClass方法

    private Class<?> getAdaptiveExtensionClass() {
        // 通过 SPI 获取接口的所有拓展类,如果有类上有@Adaptive注解,那么cachedAdaptiveClass不为空
        getExtensionClasses();
        // 检查缓存,若缓存不为空,则直接返回缓存
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        // 创建自适应拓展类
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

getAdaptiveExtensionClass 方法同样包含了三个逻辑,

  1. 调用 getExtensionClasses 获取接口所有的拓展类,该方法在上一篇文章中已经分析过。
  2. 查缓存,若缓存不为空,则返回缓存
  3. 若缓存为空,则调用 createAdaptiveExtensionClass 创建自适应拓展类

在第一步中,如果接口拓展类上有@Adaptive注解,该类就会作为默认自适应扩展类被赋值给 cachedAdaptiveClass 变量,那么cachedAdaptiveClass不为空,可以直接返回。这也是注解在类上的@Adaptive可以直接作为默认自适应扩展类的原因

createAdaptiveExtensionClass 创建自适应拓展类的相关代码如下:

    private Class<?> createAdaptiveExtensionClass() {
        //根据接口和接口默认的扩展类名称来自动生成一个自适应扩展类的代码,产生String类型的java文件
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
        // 获取编译器实现类
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        //编译代码,将字符串形式的java文件编译为class
        return compiler.compile(code, classLoader);
    }

createAdaptiveExtensionClass 方法用于生成自适应拓展类,该方法首先会生成自适应拓展类的源码,然后通过 Compiler 实例(Dubbo 默认使用 javassist 作为编译器)编译源码,得到代理类 Class 实例。我们重点关注自适应扩展代理类是怎么生成的。

自适应拓展类代码生成

createAdaptiveExtensionClass 方法生成自适应扩展类的源码如下

    /**
     * 自动生成的拓展类代码
     * 前提:接口必须至少有一个方法被 Adaptive 注解修饰
     * 声明在Adaptive中的value的作用是从URL中获取key,value
     * @return 自适应拓展类代码
     */
    public String generate() {
        //对于要生成自适应拓展的接口,Dubbo 要求该接口至少有一个方法被 Adaptive 注解修饰
        if (!hasAdaptiveMethod()) {
            throw new IllegalStateException("...");
        }

        StringBuilder code = new StringBuilder();
        //生成 package 语句,package + type所在的包
        code.append(generatePackageInfo());
        //生成import语句,import + ExtensionLoader的全限定名
        code.append(generateImports());
        // 生成类模板代码:public class + type简单名称 + $Adaptive + implements + type全限定名 + {
        code.append(generateClassDeclaration());
        //生成类中的方法,接口中带有Adaptive注解的会生成代理方法,没有带Adaptive注解的,方法中仅会生成一句抛出异常的代码
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            code.append(generateMethod(method));
        }
        code.append("}");

        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        return code.toString();
    }

在生成代理类源码之前,hasAdaptiveMethod方法先校验接口必须至少有一个方法被 @Adaptive 注解修饰,实现方法较为简单,这里不展开。校验完毕后,开始生成代码,代码生成的顺序与 Java 文件内容顺序一致,首先会生成 package 语句,然后生成 import 语句,紧接着生成类名等代码,这块的逻辑简单,也不展开。生成的简单类代码如下:

package com.dubbo.dp.spi;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Car$Adaptive implements com.dubbo.dp.spi.Car {
    //省略方法代码
}

对于生成自适应类中方法的代码逻辑涉及到如何获取自适应代理类的逻辑,源码如下,我们进行分析

    private String generateMethod(Method method) {
        //方法返回类型
        String methodReturnType = method.getReturnType().getCanonicalName();
        //方法名称
        String methodName = method.getName();
        //方法内容
        String methodContent = generateMethodContent(method);
        //方法的参数
        String methodArgs = generateMethodArguments(method);
        //方法抛出异常
        String methodThrows = generateMethodThrows(method);
        //组装
        return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
    }

生成方法代码的逻辑主要包括获取方法返回类型,名称,参数,内容以及需要抛出的异常,这里主要介绍生成方法内容的逻辑,这里涉及到如何从URL参数中获取接口的自适应拓展类,比较重要。其他比较简单,暂不介绍。generateMethodContent方法如下:

    private String generateMethodContent(Method method) {
        //获取接口方法上的@Adaptive注解
        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);
        //Dubbo 不会为没有标注 Adaptive 注解的方法生成代理逻辑,对于该种类型的方法,仅会生成一句抛出异常的代码
        if (adaptiveAnnotation == null) {
            return generateUnsupported(method);
        } else {
            // 遍历参数列表,确定 URL 参数位置
            int urlTypeIndex = getUrlTypeIndex(method);

            // urlTypeIndex != -1,表示参数列表中存在 URL 参数
            if (urlTypeIndex != -1) {
                // 为 URL 类型参数生成判空代码
                code.append(generateUrlNullCheck(urlTypeIndex));
            } else {
                // 参数列表中不存在 URL 类型参数,遍历方法的参数列表(对每个参数获取相应类的所有方法,找到一个可以得到URL方法的参数则停止,都没有则抛出异常
                code.append(generateUrlAssignmentIndirectly(method));
            }
            //获取 Adaptive注解值,用于获取拓展类。如果有,使用这些值,如果没有将使用程序根据类名创建的值作为value值
            String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
            //方法列表中是否存在 Invocation 类型的参数
            boolean hasInvocation = hasInvocationArgument(method);
            //若存在Invocation,则为其生成判空代码
            code.append(generateInvocationArgumentNullCheck(method));
            //生成拓展类获取逻辑,根据 SPI 和 Adaptive 注解值生成“获取拓展名逻辑”,同时生成逻辑也受 Invocation 类型参数影响
            code.append(generateExtNameAssignment(value, hasInvocation));
            // 拓展类判空代码
            code.append(generateExtNameNullCheck(value));
            //生成拓展类加载代码
            code.append(generateExtensionAssignment());
            // 生成目标方法调用逻辑与返回
            code.append(generateReturnAndInvocation(method));
        }
        return code.toString();
    }

该方法的执行逻辑如下,这里各个子方法里面的内容就不一一列出

  1. 获取接口方法上的@Adaptive注解
  2. 对于没有标注 @Adaptive 注解的方法,生成的代理逻辑仅会生成一句抛出异常的代码。这也是需要在接口自适应类方法上加@Adaptive注解的原因
  3. 标注了 @Adaptive 的方法,会遍历方法参数列表,获取URL参数的位置。所以接口方法中一定要存在URL参数或者可以通过getter方法获取到URL的参数。这也是我们在例子中加入URL参数的原因。
    • 如果参数列表中存在URL参数,生成URL参数判空代码。
    • 如果不存在URL参数,通过generateUrlAssignmentIndirectly方法,遍历参数列表,对每个参数获取相应类中的所有方法,找到一个可以通过getter方法得到URL参数,都没有则抛出异常。比如Invoker参数就存在可以获取URL的getter方法。对于获取到的参数再校验通过getter方法获取的url是否为空,并生成url获取代码。
  4. 获取@Adaptive注解里的value值,没有的话通过接口类名生成默认value,比如 LoadBalance 接口 经过处理后,得到 [load.balance]的value。
  5. 检测方法列表中是否存在 Invocation 类型的参数,若存在,则为其生成判空代码。
  6. 生成获取自适应扩展类的逻辑,主要是由后向前遍历@Adaptive注解里的value值,根据每个value值是否是"protocol"以及是否有Invocation类型的参数,决定拓展类获取逻辑,这里暂不列举,有兴趣的可以参考dubbo官网。我们仍以上述例子分析,value值不包含"protocol",且无Invocation类型的参数。下面是几种不同情况下的获取逻辑, 可以看出 @Adaptive注解上,靠前的value优先级高, 前面的值在URL中取不到再逐个向后取
@SPI
public interface Car {
    @Adaptive("car1")
    String getBrand();
}
情况1.  @Adaptive注解上只有一个值,@SPI上没有默认值。生成的获取逻辑是 
String extName = url.getParameter("car1"); //url上获取不到car1作为key对应的value,extName就为null

@SPI("bm")
public interface Car {
    @Adaptive("car1")
    String getBrand();
}
情况2. @Adaptive注解上只有一个值,@SPI上有默认值。生成的获取逻辑是
String extName = url.getParameter("car1", "bm");//url上获取不到car1作为key对应的value,extName就默认为bm

@SPI("bm")
public interface Car {
    @Adaptive({"car1","car2"})
    String getBrand(URL url);
}
情况3. @Adaptive注解上有多个值,@SPI上有默认值。生成的获取逻辑是
String extName = url.getParameter("car1", url.getParameter("car2", "bm"));
//嵌套获取,首先取car2,并将bm作为默认值,将该内层方法取出来的值作为取不到car1时的默认值。

......
  1. 获取到自适应拓展类的名称之后,首先对拓展类名称判空,然后自然是生成拓展类加载代码,根据名称去获取自适应拓展类对象,利用ExtensionLoadergetExtension(String name)方法。所以生成拓展类加载代码如下
com.dubbo.dp.spi.Car extension = (com.dubbo.dp.spi.Car)ExtensionLoader.getExtensionLoader(com.dubbo.dp.spi.Car.class).getExtension(extName);
  1. 获取到自适应拓展类之后,就是生成目标方法调用逻辑并返回执行结果。

至此,整个自适应拓展类代理方法代码的生成逻辑介绍完毕,整个自适应拓展代理类的自动生成代码流程也介绍完毕了。例子中生成自适应拓展类代理类如下:

package com.dubbo.dp.spi;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class Car$Adaptive implements com.dubbo.dp.spi.Car {

    public java.lang.String getBrand(org.apache.dubbo.common.URL arg0)  {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("car");
        if(extName == null) 
            throw new IllegalStateException("Failed to get extension (com.dubbo.dp.spi.Car) name from url (" + url.toString() + ") use keys([car])");
        com.dubbo.dp.spi.Car extension = (com.dubbo.dp.spi.Car)ExtensionLoader.getExtensionLoader(com.dubbo.dp.spi.Car.class).getExtension(extName);
        return extension.getBrand(arg0);
    }
}
自适应扩展源码流程总结

总结getAdaptiveExtension()获取接口自适应扩展类的流程如下:
在这里插入图片描述

总结

到此,关于dubbo的IOC,自适应拓展的原理,实现就分析完了。如果文章中有错误不妥之处,希望大家指正。

参考

http://dubbo.apache.org/zh-cn/docs/source_code_guide/adaptive-extension.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值