阅读 136

Dubbo公共契约SPI|Java 开发实战

本文正在参加「Java主题月 - Java 开发实战」,详情查看 活动链接

Dubbo SPI是什么

Java SPI与Dubbo SPI的核心就是一个:**策略模式。**针对不同情况使用不同的实现。

Dubbo SPI功能

Dubbo SPI除了实现Java SPI,还增加了一些新特性。

  • 扩展点自动包装(AOP)
  • 扩展点自动装配(IOC)
  • 扩展点自适应
  • 扩展点自动激活

源码阅读

Dubbo SPI

如何使用

  1. 定义接口,编写实现类
// 定义接口,
@SPI // @SPI 注解标识这个接口是一个可以进行SPI扩展的接口 
public interface HelloService {
    String sayHello();
}


// 定义两个实现类,一个中文实现,一个英文实现
public class ChinaeseHelloService implements HelloService {
    @Override
    public String sayHello() {
        return "你好";
    }
}

public class EngishHelloService implements HelloService {
    @Override
    public String sayHello() {
        return "Hello";
    }
}
复制代码
  1. 定义实现类对应的key,让dubbo SPI可以查找到

dubbo SPI 通过文件方式指定接口不同实现,并且为每个实现指定一个key标识。方便在使用时指定具体实现。
文件的名称和位置需要符合dubbo定义的SPI规范
位置: resources/META-INF/dubbo 目录下
文件名称:接口的全类名,如 org.apache.dubbo.study.spi.HelloService
文件内容:实现类的key=实现类全类名,如

china=org.apache.dubbo.study.spi.ChinaeseHelloService
english=org.apache.dubbo.study.spi.EngishHelloService
复制代码
  1. 使用不同实现
public void spiTest() {
        // 获取china实现
        HelloService chinaHelloService = ExtensionLoader
                .getExtensionLoader(HelloService.class)
                .getExtension("china");
        System.out.println(chinaHelloService.sayHello());

        // 获取english实现
        HelloService englishHelloService = ExtensionLoader
                .getExtensionLoader(HelloService.class)
                .getExtension("english");
        System.out.println(englishHelloService.sayHello());

    }
复制代码

源码探索

根据以上功能使用,可以看出源码入口是在ExtensionLoader.getExtensionLoader(),看源码一定要带着目的去看,比如Dubbo SPI源码主要看,如何加载配置文件、如何获取实现类对象的源码.

  1. 获取ExtensionLoader
HelloService chinaHelloService = ExtensionLoader
                // 获取ExtensionLoader入口
                .getExtensionLoader(HelloService.class)
                // 根据name获取具体实现类
                .getExtension("china");
        System.out.println(chinaHelloService.sayHello());

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 an interface!");
        }
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }
        // 从缓存中获取 ExtensionLoader 对象,注意缓存key是接口的Class
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {// 缓存没有进行创建
            // 看下面 new ExtensionLoader 时的逻辑
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

// 创建 ExtensionLaoder
private ExtensionLoader(Class<?> type) {
        this.type = type;
        // ExtensionFactory 也是通过SPI加载的,自适应方式,这个地方可以先跳过,只要知道
        // ExtensionFactory 是用来创建ExtensionLoader对象的即可
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
复制代码

2.根据name获取具体实现

HelloService chinaHelloService = ExtensionLoader
                // 获取ExtensionLoader入口
                .getExtensionLoader(HelloService.class)
                // 根据name获取具体实现类入口
                .getExtension("china");
        System.out.println(chinaHelloService.sayHello());

// 进入getExtension("china")方法,这里面有段缓存对象的代码,单独拿出来探讨
public T getExtension(String name, boolean wrap) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        // 先从缓存中获取 Holder,没有则创建Holder,这个地方为什么要多封装一次Holder?
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();

        // 如果实现类没有实例化,则进行实例化,并放入holder中
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name, wrap);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

// getOrCreateHolder 的代码
private Holder<Object> getOrCreateHolder(String name) {
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<>());
            holder = cachedInstances.get(name);
        }
        return holder;
    }
复制代码

下面划一下getExtension流程图
image.png
在获取Holder时候,先通过name从缓存中获取,如果缓存中不存在对应Holder,则进行创建,然后设置到缓存中。
为编写文章方便,给上面这套操作定义个别名,"getOrCreateAndSet"。

下面回到问题,为什么需要再封装一层Holder呢?

这其实是对锁粒度的一个优化,按正常来说,如果让我写获取对象的getOrCreateAndSet操作,我肯定是搞个Map<String,Object>,然后用double check,然后synchronized(Map<String,Object>)。这个时候锁的是整个缓存的Map对象,会导致在获取其它name时候也会阻塞,这实际上是不需要的,因为我们只需要在获取同一个name时候进行加锁即可。而name获取到对应Holder后,加锁的目标就可以换成Holder对象,减小了锁的粒度。

回到开头提到的问题,继续看对象如何创建的,入口方法是 createExtension。

private T createExtension(String name, boolean wrap) {
        // 获取name对应实现类的Class对象,从配置文件加载的
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null || unacceptableExceptions.contains(name)) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {// 反射创建实现类
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //... 省略了部分代码
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }
复制代码

从代码里可以明显看出是通过反射实例化对象。进入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;
    }
复制代码

进入 loadExtensionClasses

private Map<String, Class<?>> loadExtensionClasses() {
        // 缓存默认实现名称,@SPI("默认名称")
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        // LoadingStrategy 通过Java SPI加载,一共三个实现
        // 1.加载 META-INF/dubbo/internal/ 的SPI扩展
        // 2.加载 META-INF/dubbo/ 的SPI扩展
        // 3.加载 META-INF/services/ 的SPI扩展
        for (LoadingStrategy strategy : strategies) {
            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        }

        return extensionClasses;
    }
复制代码

查看loadDirectory过程

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                               boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
        //获取接口配置文件名称,如"META-INF/dubbo/internal/com.study.service.HelloService"
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls = null;
            // 查找resources下的文件都是使用 ClassLoader 获取
            // 不同类加载器可以加载不同范围的资源文件,比如AppClassLoader只能加载
            // 项目以及第三方jar中的资源文件,无法加载rt.jar中的资源文件,因为rt.jar
            // 包是有BootStrapClassLaoder加载
            ClassLoader classLoader = findClassLoader();

            // try to load from ExtensionLoader's ClassLoader first
            if (extensionLoaderClassLoaderFirst) {
                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
                if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                    urls = extensionLoaderClassLoader.getResources(fileName);
                }
            }

            if (urls == null || !urls.hasMoreElements()) {
                if (classLoader != null) {
                    urls = classLoader.getResources(fileName);
                } else {
                    urls = ClassLoader.getSystemResources(fileName);
                }
            }

            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    // 同个接口获取到多个配置文件时,会进行合并处理
                    loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
复制代码

上面已经获取到了接口的配置文件,下一步就需要读取配置文件进行解析了,进入 loadResource 方法

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                              java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
        try {
            // 读取IO流
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
                String clazz = 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(); // 获取key
                                clazz = line.substring(i + 1).trim(); // 获取到实现类全雷鸣
                            } else {
                                clazz = line;
                            }
                            if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) {
                                loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            }
        }
    }
复制代码

可以看到源码中是直接读取IO流,然后解析出key和class,再进行加载,进入loadClass方法

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                           boolean overridden) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz, overridden);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);// @Extension("实现类名称")
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }

            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);// 将加载的实现类加载进缓存中
                }
            }
        }
    }
复制代码

以上就是读取配置,反射实例化正常的Dubbo SPI扩展源码。

扩展点自动装配

即IOC,我们从源码找到一下几个问题的答案:

  1. 如何找到要注入的对象实例
  2. 注入方式是什么(对比Spring中的setter,构造器,注解注入)
  3. 如何处理循环依赖问题
private T injectExtension(T instance) {
        if (objectFactory == null) {
            return instance;
        }

        try {
            for (Method method : instance.getClass().getMethods()) {
                if (!isSetter(method)) {// 查找setter方法
                    continue;
                }
                /**
                 * Check {@link DisableInject} to see if we need auto injection for this property
                 */
                if (method.getAnnotation(DisableInject.class) != null) {
                    continue;
                }
                Class<?> pt = method.getParameterTypes()[0];// 获取set的值的Class类型
                if (ReflectUtils.isPrimitives(pt)) {// 参数类型是否是基本数据类型(八大数据类型+对应的包装类型,这些参数不需要进行IOC)
                    continue;
                }

                try {
                    String property = getSetterProperty(method);// 获取setter方法对应的属性名
                    Object object = objectFactory.getExtension(pt, property);// 从 SPI缓存中获取注入的对象
                    if (object != null) {
                        method.invoke(instance, object);// 调用setter方法进行注入
                    }
                } catch (Exception e) {
                    logger.error("Failed to inject via method " + method.getName()
                            + " of interface " + type.getName() + ": " + e.getMessage(), e);
                }

            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }
复制代码

通过以上代码可以看出,注入的对象实例也是通过ExtensionLoader生成的,循环依赖问题还需要进入 objectFactory.getExtension 方法查看,就不一一点击方法了,直接列出关键代码

private T createAdaptiveExtension() {
        try {
            // IOC注入时候,setter值也是反射创建并且也是调用 injectExtension 注入属性
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }
复制代码

说明Dubbo SPI实现的IOC没有处理循环依赖的情况,说明目前Dubbo的IOC使用常见也比较简单,没有循环依赖的情况,但可以预想,如果出现了循环依赖,就会死循环。忘记说了,IOC注入setter方法的值的Class中必须至少有一个方法打上**@Adaptive**注解,这部分代码会在扩展自适应说明。
进入getAdaptiveExtensionClass()

扩展点自动包装

自动包装即AOP,每个包装类的命名都是XXXXXXWrapper。
从源码查找一下问题:

  1. 如何标示需要包装的原对象
  2. 有JDK动态代理、ams生成字节码代理、静态代理,Dubbo SPI如何做的呢?
  3. 代理的顺序如何设置
private T createExtension(String name, boolean wrap) {
        // 获取name对应实现类的Class对象,从配置文件加载的
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null || unacceptableExceptions.contains(name)) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {// 反射创建实现类
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // ioc 注入
            injectExtension(instance);

            // aop 代理
            if (wrap) {

                List<Class<?>> wrapperClassesList = new ArrayList<>();
                if (cachedWrapperClasses != null) {
                    wrapperClassesList.addAll(cachedWrapperClasses);// 在从配置文件加载类时,如果是代理类会加载进cachedWrapperClasses列表
                    wrapperClassesList.sort(WrapperComparator.COMPARATOR);// 代理升序排序
                    Collections.reverse(wrapperClassesList);// 逆序
                }

                if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                    for (Class<?> wrapperClass : wrapperClassesList) {
                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                        if (wrapper == null
                                || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                            // 在真正执行时,order小的会限先执行
                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        }
                    }
                }
            }

            // 处理初始化
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }
复制代码

加载对应的代理

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                           boolean overridden) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz, overridden);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);// AOP 加载Clazz对应的Wrapper代理
        } else {
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);// @Extension("实现类名称")
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }

            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);// 将加载的实现类加载进缓存中
                }
            }
        }
    }
复制代码

扩展点自适应

通常在实际使用Dubbo SPI扩展,不会直接硬编码getExtension("china"),而是会动态的获取,如

String extensionName = "";// 可以通过rpc传递或者http传递或者注册中心传递
        HelloService service = ExtensionLoader
                .getExtensionLoader(HelloService.class)
                .getExtension(extensionName);
        System.out.println(englishHelloService.sayHello());
复制代码

而在dubbo中,在每次进行RPC调用,都会将扩展的name放在统一URL中包括开启注册中心。

URL

URL大家都不默认,全称为统一资源定位符,用于在互联网中唯一定位一个资源,其格式为:

// 以下 "[]" 中的为可选项,可填可不填,加粗为前后字符串连接符,只有前或者只有后无需该符号 [protocol]://[username]:[password]@ip:port/[path]?[a=1&b=2]

上面格式翻译过来就是 协议://用户名:密码@ip:port/资源路径?附加参数,比如以下例子都可以表示为一个url

  • 192.168.1.2:3306 只有ip和端口
  • www.baidu.com 通过http协议传输


在Dubbo中很巧妙的使用URL来作为传输元数据的数据结构,在使用的时候,只需要简单的拼接字符串或者分割字符串即可,不需要使用复杂低效的序列化方式传输元数据。比如:

  • 在使用注册中心的场景中,使用zookeeper作为注册中心 zookeeper://192.168.2.0:22
  • 在使用注册中心的场景中,使用redis作为注册中心 redis://192.168.5.4
  • 暴露服务的场景中,使用dubbo协议暴露服务 dubbo://192.168.5.4:25556/com.service.HelloService
  • 暴露服务的场景中,使用grpc协议暴露服务 grpc://192.168.22.4:232/com.service.HelloService
  • 引用服务的场景中,使用dubbo协议引用服务 dubbo://192.168.5.4:25556/com.service.HelloService

可以看到在上面的例子中,特意强调的在XXXX场景中,是因为某些URL可能会重复,比如暴露服务和引用服务都是用的dubbo协议,则URL是相同的。所以在分析URL各个参数的含义时,需要考虑当前场景。

Dubbo中URL的类模型定义如下:

class URL implements Serializable {
    protected String protocol;
    protected String username;
    protected String password;

    // by default, host to registry
    protected String host;

    // by default, port to registry
    protected int port;

    protected String path;

    private final Map<String, String> parameters;
}
复制代码

而扩展点自适应,就是先定义好一个接口,比如开头定义的HelloService,有一个sayHello方法,并且入参是URL,在方法上指定@Adaptive("hello")注解,用来标识这个方法是一个自适应方法,类上加上@SPI注解标识这个接口是一个扩展接口。并且在开头我们也定义了两个实现,一个中文实现一个英文实现,并且配置在了SPI的文件中。

@SPI
public interface HelloService{
    @Adaptive("hello")
    String sayHello(URL url);
}
复制代码

在用SPI获取实现类并且调用带有Adaptive注解的方法时,会根据URL中"hello"对应的参数加载对应的实现类执行。

HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class)
                .getAdaptiveExtension();
        URLBuilder urlBuilder = new URLBuilder();
        urlBuilder.addParameter("hello","china");// 指定china实现类
        URL url = urlBuilder.build();
        System.out.println(adaptiveExtension.sayHello(url));
复制代码

原理也很简单,使用getAdaptiveExtension获取自适应扩展时候,对于有@Adaptive注解的方法会进行代理,根据注解上指定的key从URL参数中获取对应的实现类的name,然后在使用SPI加载实现类执行对应方法。而Dubbo中代理方法是通过生成类实现的。下面看核心生成方法体代码。

private String generateMethodContent(Method method) {
        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);
        if (adaptiveAnnotation == null) {
            return generateUnsupported(method);
        } else {// 有 Adaptive注解
            int urlTypeIndex = getUrlTypeIndex(method);

            // found parameter in URL type
            if (urlTypeIndex != -1) {
                // 从方法的参数中查找是否有URL入参
                code.append(generateUrlNullCheck(urlTypeIndex));
            } else {
                // did not find parameter in URL type
                code.append(generateUrlAssignmentIndirectly(method));// 从方法参数中找是否有获取Url的方法
            }
            // 通过注解指定的key,注解key可以指定多个,所有value是数组类型
            String[] value = getMethodAdaptiveValue(adaptiveAnnotation);

            // 方法的参数是否是 Invocation 类型
            boolean hasInvocation = hasInvocationArgument(method);
            // 生成获取 Invocation 方法名称的代码
            code.append(generateInvocationArgumentNullCheck(method));
            //生成通过注解指定key,从URL中获取对应值的方法,值会存在 extName 变量中
            code.append(generateExtNameAssignment(value, hasInvocation));
            // 生成判断 extName 是否为null的代码
            code.append(generateExtNameNullCheck(value));
            // 生成通过 SPI和extName 获取扩展对象的代码
            code.append(generateExtensionAssignment());

            // 生成执行扩展对象的代码
            code.append(generateReturnAndInvocation(method));
        }

        return code.toString();
    }
复制代码

这个生成代码的逻辑看起来相当的头疼,但这种方式的优点是不使用反射性能更高。可以通过断点查看生成的代码:

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

public class HelloService$Adaptive implements org.apache.dubbo.study.spi.HelloService {
    public java.lang.String sayHello(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("hello");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.study.spi.HelloService) name from url (" + url.toString() + ") use keys([hello])");
        org.apache.dubbo.study.spi.HelloService extension = (org.apache.dubbo.study.spi.HelloService) ExtensionLoader.getExtensionLoader(org.apache.dubbo.study.spi.HelloService.class).getExtension(extName);
        return extension.sayHello(arg0);
    }
}
复制代码

上面的代码可以看到代理是直接调用实现类的方法,没有经过类似**method.invoke()**反射的调用,性能更高一些。

扩展点自动激活

扩展点自动激活是用在Dubbo的集合类扩展实现上,比如Filter,如果某个Filter实现类打上了@Activate("xxx")注解,并且"XXX"在URL中指定,就会自动激活对应的实现类,通过ExtensionLoader.getActivateExtension方法可以获取到所有激活的实现类列表。

// 删掉了一些非核心代码,注意names如果传值就相当于一个白名单,通常都会穿null
public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> activateExtensions = new ArrayList<>();
        TreeMap<Class, T> activateExtensionsMap = new TreeMap<>(ActivateComparator.COMPARATOR);
        List<String> names = values == null ? new ArrayList<>(0) : asList(values);
        // names 大多数情况都是 Empty
        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            getExtensionClasses();
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Object activate = entry.getValue();

                String[] activateGroup, activateValue;

                if (activate instanceof Activate) {
                    activateGroup = ((Activate) activate).group();
                    activateValue = ((Activate) activate).value();
                }  else {
                    continue;
                }
                if (isMatchGroup(group, activateGroup)// 判断是否匹配group
                        && !names.contains(name) //
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        && isActive(activateValue, url)) { // 注解上的值是否在URL中包含
                    activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
                }
            }
            if(!activateExtensionsMap.isEmpty()){
                activateExtensions.addAll(activateExtensionsMap.values());
            }
        }
        return activateExtensions;
    }
复制代码

总结

Dubbo中所有的扩展设计都是围绕URL+SPI+IOC+AOP实现的,虽然实现的代码很复杂,但不得不说使用起来非常的顺畅,这也是在设计框架时候一定要注意的地方,不论技术实现多么复杂多么抽象,多么奇怪。在设计API的时候一定要注意简洁、易用、好用。

文章分类
后端
文章标签