Dubbo SPI机制(下):自激活扩展与自适应扩展的加载

在上一篇文章中,我们了解了Dubbo中一个普通的扩展类是如何加载的。今天,我们继续Dubbo SPI的旅程,来看一看两个特殊的扩展:自激活扩展和自适应扩展。

自激活扩展

什么是自激活扩展?

如果一个扩展点有很多扩展实现,而我们需要根据不同的条件参数来动态选择可以使用的扩展时(可能需要同时使用多个扩展),就会使用到自激活扩展了。

这样说你可能还是不太清楚,我们举个例子:
你应该或多或少都听过Filter的概念吧?众多的语言概念中都有过滤器的概念,Dubbo中也有。通过各种各样的过滤器能够帮助我们实现请求的过滤、日志的打印、流量的统计等一系列功能。但是这么多的过滤器,并不是在每一种场景下都是要全部加载进来的。我们需要能够根据请求的参数,自动激活要加载的过滤器。这样的功能,就是通过自激活扩展来实现。

自激活扩展使用@Activate来标记。

@Activate的使用

@Activate注解可以使用在类、接口、枚举类和方法上。其注解源码如下

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

    String[] value() default {};

    String[] before() default {};

    String[] after() default {};

    int order() default 0;
}

通过源码,可以清楚的看到@Activate注解支持传入的参数类型。

  • group和value代表激活的条件,group代表URL中的分组,value代表URL中的key。如果匹配条件,则会激活扩展。
  • before、after和order指定了扩展使用的顺序。before和after可以指定在特定的扩展前/后加载使用;order为整型,表示的是直接的排序信息。

具体的@Activate使用示例,请直接移步官网,这里不再赘述

自激活扩展如何实现自激活

在真正了解自激活扩展的加载过程前,我们先来自己想一想,如何实现一个扩展类的自激活?或者说获取一个自激活扩展类,我们要做哪些事情?

根据上述的描述,你应该知道,自激活扩展加载最终结果会返回一个扩展类集合。
对于每个扩展类的加载,使用上一节我们了解的知识就可以加载。
所以,对于自激活扩展来说,我们额外要做的,就是对于扩展类集合的“激活”。

对于“激活”而言,我们主要需要:

  1. 能够根据条件,过滤筛选出需要的扩展类集合
  2. 能够根据条件,对筛选后的扩展类集合进行排序

这样“激活”过的扩展类集合,应当就是符合我们要求的自激活扩展了。
下面我们来看一下,Dubbo中是如何做的吧。

自激活扩展的加载原理

下面我们从源码角度来看一下自激活扩展是如何加载的。

首先,咱们还是从Dubbo中实际调用自激活扩展的一个实例说起(ProtocolFilterWrapper类)

List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

getExtensionLoader方法已经在上一篇《一个普通的扩展类加载》中讲解过了,这里就不再赘述了。主要看一下getActivateExtension是如何处理的。

public List<T> getActivateExtension(URL url, String key, String group) {
	String value = url.getParameter(key);
	return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
}

这里主要也是进行一个方法参数的透传,具体涉及到了Dubbo中的URL参数key的解析,这里先不用太关注,具体还是进入实际调用方法中来看。

public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<T>();
    List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
    // $-- 如果扩展点名称列表中不含-default选项,则加载全部默认的自激活扩展点,筛选加入自激活扩展集合中
    if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
        // $-- 生成扩展类
        getExtensionClasses();
        // $-- 遍历缓存中的默认自激活扩展
        for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
            String name = entry.getKey();
            Activate activate = entry.getValue();
            // $-- 如果缓存中的自激活扩展类匹配条件,则加入自激活扩展集合中
            // $-- 1. group匹配
            if (isMatchGroup(group, activate.group())) {
                T ext = getExtension(name);
                // $-- 2. 该默认扩展点不在要获取的扩展点集合中(下面会专门处理要获取的扩展点) && 要获取的扩展点集合不存在该默认扩展点的自反 && 此扩展点的value与url匹配
                if (!names.contains(name)
                        && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
                        && isActive(activate, url)) {
                    exts.add(ext);
                }
            }
        }
        // $-- 按照@Activate配置的before、after、order等参数进行排序
        Collections.sort(exts, ActivateComparator.COMPARATOR);
    }
    // $-- 根据要获取的扩展点名称列表,获取扩展点,进行过滤筛选,加入自激活扩展集合中
    List<T> usrs = new ArrayList<T>();
    for (int i = 0; i < names.size(); i++) {
        String name = names.get(i);
        // $-- 如果扩展点名称不是以-开头 && 扩展点集合中没有该扩展点的“自反”(如:既含有name,又含有-name,这样自相矛盾的不处理)
        if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
                && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
            if (Constants.DEFAULT_KEY.equals(name)) {
                // $-- 如果扩展点名称为default,则将临时自激活扩展集合转移到自激活扩展集合首位(用于排序,这样default就排到后面了)
                if (!usrs.isEmpty()) {
                    exts.addAll(0, usrs);
                    usrs.clear();
                }
            } else {
                // $-- 加载扩展点,并放入临时自激活扩展集合中
                T ext = getExtension(name);
                usrs.add(ext);
            }
        }
    }
    if (!usrs.isEmpty()) {
        exts.addAll(usrs);
    }
    return exts;
}

此处的getActivateExtension方法是自激活扩展的主体逻辑,主要的处理逻辑是对自激活扩展点的获取、筛选和排序。

获取扩展点的逻辑,我们已经在上一篇文章中介绍了,这里不再赘述。另外说明一下,这里的自激活扩展点缓存cachedActivates是在文件读取加载扩展类时,通过loadClass方法“顺便”缓存下来的。

对于自激活扩展的筛选和排序,这里分为了两种场景。一种是系统默认的扩展,一种是通过URL参数传入的用户自定义扩展。

  1. 对于系统默认的扩展。
    • 筛选:如果参数中没有-default选项,则加载默认的@Activate扩展。这里得到的所有默认@Activate扩展点,均需要根据传入参数的group、url配置的key等进行匹配筛选。对此,注释中已经有很好的说明了。
    • 排序:默认@Activate扩展点的排序,是按照@Activate配置的before、after、order等参数进行排序,排序的逻辑在ActivateComparator中,这里不赘述
  2. URL指定的扩展
    • 筛选:由于扩展点名称是作为参数传递进来的,因此需要进行扩展是否加载的逻辑判断(如:“-”开头的扩展不被加载,如“-filter1”代表不加载默认filter1扩展)
    • 排序:对于URL参数传入的扩展点,用户可以通过修改URL中参数中扩展点顺序来调整扩展点的激活顺序。

    举个例子,如果URL为 dubbo://localhost/test?ext=filter1,default,则扩展点ext的激活顺序为先filter1再default(URL参数中的“default”代表默认的自激活扩展)

到这里处理完成,就能获取指定的条件下(URL)可以使用的自激活扩展列表了。不知道与你想象的实现有多少出入呢?😄

自适应扩展

下面,让我们来了解Dubbo中使用非常广泛的另一种扩展——自适应扩展。

什么是自适应扩展

一个扩展接口会有多种实现类,具体使用哪个实现类并不是写死在配置或代码中的,而是在运行时,通过URL参数来动态确定的。这种扩展就叫做自适应扩展。

自适应扩展使用@Adaptive来标注。

@Adaptive的使用

Adaptive可以用在类和接口方法上。

  • 标记在类上,则代表该类为默认的扩展点实现。如:AdaptiveCompiler为Compiler的默认实现
  • 标记在方法上,则代表扩展点需要根据参数动态生成。如Dispatcher接口:
SPI(AllDispatcher.NAME)
public interface Dispatcher {
    @Adaptive({Constants.DISPATCHER_KEY, "dispather", "channel.handler"})
    ChannelHandler dispatch(ChannelHandler handler, URL url);
}

Dispatcher接口的dispatch方法上标注了@Adaptive注解,代表这个方法调用时,会自动寻找系统中匹配的扩展类。匹配时会根据@Adaptive注解的value值进行选择,顺序依次为 常量 Constants.DISPATCHER_KEY,dispatcher,channel.handler。按照顺序,只要匹配上某一个扩展类就直接返回,调用该扩展类的dispatch方法。

这里简化了一下匹配的规则,方便理解。具体的规则,会在下方源码分析的时候介绍。

自适应扩展如何实现自适应

在正式的源码解读之前,我们不妨就自适应扩展的这种表现,思考一下,如何实现一个自适应扩展?
自适应扩展依然是一个扩展,所以我们理论上依然可以使用Dubbo SPI的普通扩展来加载各种各样的扩展。但是如何实现自适应呢?
自适应(这里主要讨论@Adaptive注解标注在方法上的自适应)要求能够根据不同的扩展点,实现不同的匹配规则,来进行扩展点的选择,最后再来调用选中扩展类的该方法。

拿上述Dispatcher为例,为了实现扩展点选择,应该有这样一段基本代码

ChannelHandler dispatch(ChannelHandler handler, URL url) {
	String extName = url.getParameter("dispatcher", url.getParameter("dispather", url.getParameter("channel.handler", "all")));;
	Dispatcher dispatcher = ExtensionLoader.getExtensionLoader(Dispatcher.class).getExtension(extName);
	return dispatcher.dispatch(handler, url);
}

很显然,这样的代码是特殊的,无法公用的。Dubbo显然不会为每一个扩展点写这样一段代码。那么如何生成这样的代码呢?
自然而然的,我们想到可以使用javassist、cglib、jdk动态代理来生成这样的“自适应代码”,从而实现公用、方便扩展。事实上,Dubbo确实也这么做了,接下来让我们从源码角度来看一下Dubbo自适应扩展是如何玩起来的。

自适应扩展的加载原理

按照惯例,我们依然从一个简单的调用开始我们的源码分析。

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

这行代码先是获取Protocol的扩展加载器,然后使用加载器去获得自适应的扩展。前半段不再赘述,让我们将目光转到getAdaptiveExtension方法上。

public T getAdaptiveExtension() {
    // $-- 缓存老套路,缓存的是扩展类实例。
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        if (createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        // $-- 创建自适应扩展并缓存
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        } else {
            throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
        }
    }

    return (T) instance;
}

这段代码主要还然是缓存的老套路,缓存的是自适应扩展类的实例。真正的创建自适应扩展要看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);
    }
}

创建自适应扩展的方法依然是一个嵌套调用。先通过getAdaptiveExtensionClass生成自适应扩展类Class,然后通过反射实例化,最后通过injectExtension方法进行setter扩展注入。
injectExtension方法我们已经在上一篇解读过了,这里不再赘述。来看一下getAdaptiveExtensionClass方法。

private Class<?> getAdaptiveExtensionClass() {
	// $-- 加载所有扩展类
    getExtensionClasses();
    // $-- 如果缓存有默认的自适应扩展类,则直接返回(这里主要是@Adaptive标记在扩展类上的场景,该扩展类为该扩展点的默认扩展类)
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    // $-- 生成自适应扩展类
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

通过getExtensionClasses方法加载所有扩展类也已经在上一篇了解过了,这里不再赘述。

通过cachedAdaptiveClass缓存的判断,体现了@Adaptive标记在扩展类上即为默认扩展类的这一设计。cachedAdaptiveClass是在从文件读取加载扩展类,loadClass时赋值的。

下面还是像“套娃”一样,进入createAdaptiveExtensionClass方法

 private Class<?> createAdaptiveExtensionClass() {
    // $-- 生成自适应扩展类的代码
    String code = createAdaptiveExtensionClassCode();
    ClassLoader classLoader = findClassLoader();
    // $-- 获取Compiler的扩展点实现类,编译生成的代码为Class
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

从这段代码里我们已经可以大致验证自适应的猜想了。
首先通过createAdaptiveExtensionClassCode方法,生成该扩展点的“自适应代码”。
然后获取类加载器和编译器。利用编译器编译得到最终的自适应扩展类。

需要注意的是这里的编译器Compiler依然是一个自适应扩展类。那是否就构成了一个死循环了呢?
当然不可能,为了不打扰主体流程,具体原因放在最后讨论。这里先知道能否获取到Compiler,并且默认为javassist扩展。

让我们继续看一下代码是如何生成的吧!且看createAdaptiveExtensionClassCode方法

private String createAdaptiveExtensionClassCode() {
   StringBuilder codeBuilder = new StringBuilder();
    Method[] methods = type.getMethods();
    // $-- 校验是否有@Adaptive注解(至少应当有一个方法有Adaptive注解)
    boolean hasAdaptiveAnnotation = false;
    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!");

    // $-- 生成包、引用等代码
    codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
    codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
    codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");

    // $-- 遍历生成方法代码
    for (Method method : methods) {
        Class<?> rt = method.getReturnType();
        Class<?>[] pts = method.getParameterTypes();
        Class<?>[] ets = method.getExceptionTypes();

        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);
        if (adaptiveAnnotation == null) {
            // $-- 如果该方法没有@Adaptive注解,则生成抛异常代码
            code.append("throw new UnsupportedOperationException(\"method ")
                    .append(method.toString()).append(" of interface ")
                    .append(type.getName()).append(" is not adaptive method!\");");
        } else {
            // $-- 如果该方法有@Adaptive注解,即为自适应方法
            // $-- 判断参数中是否有URL
            int urlTypeIndex = -1;
            for (int i = 0; i < pts.length; ++i) {
                if (pts[i].equals(URL.class)) {
                    urlTypeIndex = i;
                    break;
                }
            }
            // found parameter in URL type
            if (urlTypeIndex != -1) {
                // $-- 生成校验URL参数不能为空代码
                // 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 {
                // $-- 方法参数中没有直接URL类型参数
                String attribMethod = null;

                // find URL getter method
                LBL_PTS:
                for (int i = 0; i < pts.length; ++i) {
                    // $-- 尝试遍历方法参数列表中每一个类中是否有满足要求的类(get开头,返回URL,无参数,公共静态)
                    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);
            }

            String[] value = adaptiveAnnotation.value();
            // value is not set, use the value generated from class name as the key
            if (value.length == 0) {
                // $-- "驼峰规则"匹配,如 YyyInvokerWrapper ==> yyy.invoker.wrapper
                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()};
            }

            // $-- 生成Invocation类空校验代码
            boolean hasInvocation = false;
            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;
                }
            }

            // $-- 生成获取扩展点名称的代码
            String defaultExtName = cachedDefaultName;
            String getNameCode = null;
            for (int i = value.length - 1; i >= 0; --i) {
                if (i == value.length - 1) {
                    if (null != defaultExtName) {
                        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], defaultExtName);
                        else
                            getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                    } 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\")", 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(");");
        }

        // $-- 补齐生成方法的头尾代码
        codeBuilder.append("\npublic ").append(rt.getCanonicalName()).append(" ").append(method.getName()).append("(");
        for (int i = 0; i < pts.length; i++) {
            if (i > 0) {
                codeBuilder.append(", ");
            }
            codeBuilder.append(pts[i].getCanonicalName());
            codeBuilder.append(" ");
            codeBuilder.append("arg").append(i);
        }
        codeBuilder.append(")");
        if (ets.length > 0) {
            codeBuilder.append(" throws ");
            for (int i = 0; i < ets.length; i++) {
                if (i > 0) {
                    codeBuilder.append(", ");
                }
                codeBuilder.append(ets[i].getCanonicalName());
            }
        }
        codeBuilder.append(" {");
        codeBuilder.append(code.toString());
        codeBuilder.append("\n}");
    }
    codeBuilder.append("\n}");
    if (logger.isDebugEnabled()) {
        logger.debug(codeBuilder.toString());
    }
    return codeBuilder.toString();
}

这里不得不吐槽一下,这个方法实在是太长了。。。😂
简单来说,为了拼接出自适应的代码,做了这样几件事情:

  1. 校验该扩展点的方法是否有@Adaptive注解。至少有一个方法有@Adaptive注解,这样才有生成自适应扩展的必要
  2. 通过拼接字符串的方式,生成包、引用等代码
  3. 遍历该扩展点所有的方法,生成方法代码
    3.1 如果该方法上没有@Adaptive注解,则不允许该方法自适应调用,生成抛异常代码
    3.2 如果方法上有@Adaptive注解,则需要生成自适应方法
  4. 类所有代码的拼接

其中,生成自适应方法主要逻辑如下:

  1. 生成获取默认扩展类的代码
  2. 生成判断扩展类为空的代码
  3. 生成扩展类方法调用的代码

整个逻辑还是拼接代码的套路,需要注意的就是扩展类的获取逻辑。自适应代码生成逻辑中大量的代码都是在处理这个逻辑。
扩展类依然还是通过ExtensionLoader.getExtension(extName)来生成的,唯一要注意的是这里扩展点名称extName的取值逻辑。
扩展点名称extName是从URL中的键值对获取的。那么URL中的键key又是如何获取的呢?这里简单总结如下:

  1. 先取@Adaptive注解中的key
  2. 如果@Adaptive注解未配置key,则会根据当前扩展点的名称,生成key(“驼峰规则”,如YyyInvokerWrapper ==> yyy.invoker.wrapper)
  3. 如果依然没有找到,则取扩展点@SPI注解中的key

走到这里,我们终于把自适应扩展关键的代码生成逻辑讲解完成了。剩下的就是使用编译器来进行编译了,编译过程就不赘述了。

这里我们遗留了一个问题,Compiler也是一个自适应扩展,那么走到这里会不会形成一个死循环呢?让我们继续看一下Compiler自适应扩展是如何加载的

Compiler自适应扩展类是如何加载的

Compiler自适应扩展会不会形成死循环呢?
答案当然是NO!因为Compiler扩展点的自适应扩展类并不是通过生成代码而获取的。

不知你是否还记得,如果@Adaptive标记在类上时,表示这个类是该扩展点的默认实现类。而Compiler就是利用这一点进行加载的。

在AdaptiveCompiler类上有@Adaptive标识,代表AdaptiveCompiler为Compiler默认的扩展实现

@Adaptive
public class AdaptiveCompiler implements Compiler {

    private static volatile String DEFAULT_COMPILER;

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

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

在我们的应用程序中未指定DEFAULT_COMPILER的情况下,默认会使用默认扩展(javassist)作为compiler。看loader.getDefaultExtension方法

public T getDefaultExtension() {
    getExtensionClasses();
    if (null == cachedDefaultName || cachedDefaultName.length() == 0
            || "true".equals(cachedDefaultName)) {
        return null;
    }
    return getExtension(cachedDefaultName);
}

getDefaultExtension方法会取cachedDefaultName作为默认扩展类名。那么这个cachedDefaultName又是从哪里获取的呢?

这就要追溯到文件中读取加载扩展类的过程了。在真正查找文件之前,会有一个SPI注解的判断,具体实现在loadExtensionClasses方法中

private Map<String, Class<?>> loadExtensionClasses() {
 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);
            // $-- @SPI注解只允许指定一个默认值
            if (names.length > 1) {
                throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                        + ": " + Arrays.toString(names));
            }
            // $-- @SPI指定了一个默认值,缓存起来
            if (names.length == 1) cachedDefaultName = names[0];
        }
    }

    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    // $-- 加载扩展类资源目录
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    ...
}

如果扩展点@SPI注解的value有且只有一个值,那么这个值就是默认扩展类。我们再看Compiler接口

@SPI("javassist")
public interface Compiler {
    Class<?> compile(String code, ClassLoader classLoader);
}

你看,这样就指定javassist为默认扩展类了。

在调用getAdaptiveExtension获取自适应扩展时,判断到如果有默认的自适应扩展,就会直接返回该扩展类。

private Class<?> getAdaptiveExtensionClass() {
    // $-- 加载所有扩展类
    getExtensionClasses();
    // $-- 如果缓存有默认的自适应扩展类,则直接返回(这里主要是@Adaptive标记在扩展类上的场景,该扩展类为该扩展点的默认扩展类)
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    // $-- 生成自适应扩展类
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

总结

到此为止,Dubbo SPI机制主要内容就已经介绍完成了。
Dubbo SPI机制是理解后续服务暴露、引用和调用过程的基石,也是整个Dubbo能够博采众长,吸纳众多开源解决方案的一个重要原因。
整个流程可能有点绕,但就我而言,通过实际Debug对于理解这套机制非常有用。

不知你对Dubbo SPI了解有多少了呢?😁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hober.z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值