Dubbo SPI与自适应机制

470 阅读24分钟

0.背景

​ 这两天在debug Dubbo的源码的时候,发现明明同一个对象,每次调用它的方法的时候居然跳到了不同的实现类中,属实让我晕了好久。最后去Dubbo的官网才发现,这个是Dubbo SPI的自适应机制。Dubbo根据Dubbo URL中的parameter中的指定的key的value,去自动使用对应类型的对象,来处理逻辑(变相的一种策略模式?)。

1.简介

SPI全称是Service Provider Interface,是一种服务发现机制。SPI的本质是将接口的实现类的全限定名配置在文件中。应用在运行的过程中,读取配置文件,加载指定的实现类。所以SPI机制至少给我们提供了两种功能:

  1. 应用运行时,动态替换接口的实现类。
  2. 对于第三方用户,赋予应用可扩展的能力。JDK

因为SPI机制的这些优点,很多第三方框架都使用了SPI机制。比如Springboot,它的无需配置,开箱即用的特点,是基于它的Auto-configuration机制实现的,而Auto-configuration机制是依赖于SPI机制的,Springboot通过SPI机制从Auto-configuration包中读取配置好的Auto-Configuration类,并自动加载他们,实现自动化配置。处此之外,本文的主角Dubbo,也是使用了SPI机制,来让自己变得更容易扩展,同时在SPI的基础上增加了自适应机制,让Dubbo变得更加的灵活。

2.Dubbo SPI加载过程分析

​ Dubbo没有直接使用Java提供的SPI,而是自己实现了一套功能更强的SPI。Java的SPI加载类是java.util.ServiceLoader,Dubbo的SPI加载类是org.apache.dubbo.common.extension.ExtensionLoader。

下面我们就以Dubbo的ExtensionLoader为入口,来看下Dubbo是如何实现自己的SPI机制。

2.1.Dubbo SPI 例子

​ 先准备一个待实现的接口:

package com.huang.dubboprovider.spi;

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

// Dubbo的SPI接口必须要加上@SPI注解,否则ExtesionLoader会无法加载
@SPI
public interface Database {

    String getDatabaseName();
}		

​ 然后基于这个接口实现3个实现类:

package com.huang.dubboprovider.spi;

public class MySQLDatabase implements Database{

    private static final String MYSQL_NAME = "MySQL";

    @Override
    public String getDatabaseName() {
        return MYSQL_NAME;
    }
}	
package com.huang.dubboprovider.spi;

public class PostgreSQLDatabase implements Database {

    public static final String PSQL_NAME = "PostgreSQL";

    @Override
    public String getDatabaseName() {
        return PSQL_NAME;
    }
}
package com.huang.dubboprovider.spi;

public class SQLServerDatabase implements Database {

    public static final String SQLSERVER_NAME = "SQLServer";

    @Override
    public String getDatabaseName() {
        return SQLSERVER_NAME;
    }
}

在项目的resource目录下创建目录META-INF.services,在META-INF.services下创建文件,文件名为Database接口的全限定名:com.huang.dubboprovider.spi.Database,如下图所示:

image-20201121113405187

com.huang.dubboprovider.spi.Database文件内容如下,在文件中我们给Database接口提供了三个实现类:

mysql=com.huang.dubboprovider.spi.MySQLDatabase
psql=com.huang.dubboprovider.spi.PostgreSQLDatabase
sqlserver=com.huang.dubboprovider.spi.SQLServerDatabase

接下来我们使用ExtensionLoader来加载Database的实现类,代码如下:

package com.huang.dubboprovider.spi;

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

public class SPITest {

    public static void main(String[] args) {
		// 获取Database接口的ExtensionLoader
        ExtensionLoader<Database> extensionLoader = ExtensionLoader.getExtensionLoader(Database.class);

        // 使用Database的ExtensionLoader获取指定的实现类
        Database mysql = extensionLoader.getExtension("mysql");
        System.out.println(mysql.getDatabaseName());
        Database psql = extensionLoader.getExtension("psql");
        System.out.println(psql.getDatabaseName());
        Database sqlServer = extensionLoader.getExtension("sqlserver");
        System.out.println(sqlServer.getDatabaseName());
    }
}

执行SPITest.main方法输出如下:

MySQL
PostgreSQL
SQLServer

如输出结果所示,SPI机制确实生效了。接下来我们就分析下,上文中调用到的ExtensionLoader的两个方法。

  1. org.apache.dubbo.common.extension.ExtensionLoader#getExtensionLoader
  2. org.apache.dubbo.common.extension.ExtensionLoader#getExtension

2.2.ExtensionLoader#getExtensionLoader分析

ExtensionLoader#getExtensionLoader的代码很简单,这是一个静态方法,入参是一个指定的接口,返回指定的接口的ExtensionLoader实例。

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!");
        }
    	// withExtensionAnnotation方法中,校验入参指定的接口是否有Dubbo的@SPI注解。如果没有的话就抛出异常。所以Dubbo的SPI接口必须要加上@SPI注解才能使用。
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }

    	// 获取Database.class的ExtensionLoader时,先去EXTENSION_LOADERS中获取,EXTENSION_LOADERS是ExtensionLoader持有的一个静态的ConcurrentMap,key是SPI接口的class对象,value是SPI接口对应的ExtensionLoader。如果EXTENSION_LOADERS中没有指定的ExtensionLoader,则创建并放入EXTENSION_LOADERS中。
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

2.3.ExtensionLoader#getExtension分析

public T getExtension(String name) {
    	// 实现类的别名为空,就报错
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
    	// 如果name为true就返回接口的默认实现,具体逻辑我们稍后分析。
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
    	//  每个ExtensionLoader都持有一个叫cachedInstances的ConcurrentHasnMap实例,这个map用来缓存对应的SPI接口实现类,如果对应的实现类还未创建,在创建了之后缓存到这个map中,等下次要用的时候直接获取,map的key是实现类的别名,value是一个Holder实例,Holder实例中保存着对应SPI接口的一个实现类。
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
    	// 通过double-check,来保证SPI实现类的创建过程是线程安全的。自然而然,我们也能明白,之所以用Holder来包装SPI实例,是为了拿holder对象来作为SPI实例的锁(因为SPI实例可能没被创建,是null,无法被拿来用作锁)。
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }


特别指出下,Holder对象中用value来保存被封装的实际对象,且value是被volatile所修饰的,volatile保证了对象在线程之间的可见性。

package org.apache.dubbo.common.utils;

/**
 * Helper Class for hold a value.
 */
public class Holder<T> {

    private volatile T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }

}

下面来继续看下ExtensionLoader#getDefaultExtension和ExtensionLoader#createExtension的具体实现。

2.3.1.ExtensionLoader#getDefaultExtension

getDefaultExtension接口时用来获取对应的SPI接口的默认实现。逻辑很简单,先执行getExtensionClasses方法,执行完毕之后,根据cachedDefaultName来返回对应的默认实现,可以看出,应该是在getExtensionClasses方法中会设置cachedDefaultName。

 public T getDefaultExtension() {
        getExtensionClasses();
        if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
            return null;
        }
        // 如果SPI接口的默认实现有配置,再调用getExtension方法,getExtension方法里再通过cachedInstances判断默认实现类的实例是否已经缓存,如果还没缓存,就调用createExtension方法,来创建实现类的实例。
        return getExtension(cachedDefaultName);
    }

接下来我们查看getExtensionClasses()方法

// getExtensionClasses方法会从指定的配置文件下,加载指定的SPI接口的所有实现类的Class对象
private Map<String, Class<?>> getExtensionClasses() {
    // private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
    // cachedClasses用来保存指定的SPI接口的所有实现类的Class对象
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        // 如果还没有加载指定的SPI接口的所有实现类的Class,就去开始加载。
        synchronized (cachedClasses) {
            // 拿holder对象来"上锁"(怪不得要封装一层Holder对象呢,原来是拿来当工具人加锁用的啊),线程安全。
            classes = cachedClasses.get();
            // 依旧是熟悉的double-check,来保证线程安全
            if (classes == null) {
                // 加载指定的SPI接口的所有实现类的Class对象
                classes = loadExtensionClasses();
                // 将指定的SPI接口的所有实现类的Class对象保存到cachedClasses中
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

// synchronized in getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
    // 获取并保存默认的SPI接口实现
    cacheDefaultExtensionName();
	
    // 从"META-INF/services/"、"META-INF/dubbo/"、"META-INF/services/"internal/"目录下加载指定的SPI接口的所有Class对象。
    Map<String, Class<?>> extensionClasses = new HashMap<>();
  
    // loadDirectory的逻辑和Dubbo的自适应机制相关,后文讲自适应机制的时候再分析这个方法。
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    return extensionClasses;
}

// 从指定的SPI接口获取默认的实现类配置,并保存到ExtensionLoader的cachedDefaultName字段上。
private void cacheDefaultExtensionName() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation != null) {
        // 默认实现类通过@SPI注解来配置。
        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));
            }
            if (names.length == 1) {
                cachedDefaultName = names[0];
            }
        }
    }
}

2.3.2.ExtensionLoader#createExtension

    private T createExtension(String name) {
        // 如果指定的实现类不存在,则报错
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            // 创建指定的实现类的实例,并保存到EXTENSION_INSTANCES中
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // Dubbo的SPI机制,还提供了IOC的功能,会给被创建的实现类实例,进行依赖注入(DI),具体的逻辑我们稍后分析。
            injectExtension(instance);
            // 如果指定的SPI接口,有配置”包装类“,则将要创建的实现类包装进"包装类"中(装饰者模式),并对包装类进行依赖注入。"包装类"和SPI接口其他的实现类的配置方式全都一样,唯一不同的是,它有一个以对应的SPI接口为入参的构造函数,且包装类不能作为SPI接口的扩展实例,它只能去包装SPI接口下的其他扩展实例:"包装类"不会被保存到ExtensionLoader实例的cachedInstances中,而是保存在cachedWrapperClasses。后文将会专门演示列子。
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    // 将要创建的实现类包装进"包装类"中,并对包装类进行依赖注入
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

2.3.3.ExtensionLoader#injectExtension

在分析injectExtension方法前,先说明下ExtensionLoader实例的objectFactory字段(类型为ExtensionFactory),这个字段是Dubbo SPI依赖注入的核心,我们看下这个字段的值是如何创建的:

private ExtensionLoader(Class<?> type) {
        this.type = type;
    	// 入参type就是SPI接口的Class对象。如果type是ExtensionFactory.class,则后续创建对应的实现类实例的时候不会执行依赖注入的逻辑。如果不是ExtensionFactory.class,则objectFactory指向ExtensionFactory.class的@Adaptive类实例,即AdaptiveExtensionFactory.class实例。关于@Adaptive即Dubbo的自适应机制,将会在后文分析,这里就先不深入讨论。
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }			

AdaptiveExtensionFactory类如下:

package org.apache.dubbo.common.extension.factory;

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * AdaptiveExtensionFactory
 */
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;
	
    // AdaptiveExtensionFactory唯一的构造方法,会将ExtensionFactory.class接口的所有实现类(除了被@Adaptive注解的类,即AdaptiveExtensionFactory)的实例保存到自己的factories字段中,即factories中保存着SpiExtensionFactory和SpringExtensionFactory
    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }
	
    // 依次遍历SpiExtensionFactory和SpringExtensionFactory,直到获取到指定类型和名字的对象。
    // SpiExtensionFactory是通过Dubbo的SPI机制去获取指定SPI接口的Adaptive实例,详见SpiExtensionFactory的源码。
    // SpringExtensionFactory从Spring上下文中(通过ApplicationContextAware获取Spring上下文),获取指定类型和名字的bean,详见SpringExtensionFactory的源码。
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}

下面来分析Dubbo的SPI是如何进行依赖注入的。

private T injectExtension(T instance) {
        try {
            // injectExtension 依赖注入的核心是objectFactory
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    // 判断方法是否是以"set"开头,且方法的可见性为public,且入参有且仅有1个
                    if (isSetter(method)) {
          				// 如果方法上加了@DisableInject,则自动略过,不进行依赖注入。
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        // 获取set方法唯一入参的类型。
                        Class<?> pt = method.getParameterTypes()[0];
                        // 判断是否是原始数据类型(short、int、long等等)以及它们对应的包装类
                        // 或者是否是String类型
                        // 或者是否是Number或者Date的实现类
                        // 如果是,则忽略,不进行依赖注入。
                        if (ReflectUtils.isPrimitives(pt)) {
                            continue;
                        }
                        try {
                            // 获取set方法对应的property的名字,比如方法名为setVersion,则property的名字为version。
                            String property = getSetterProperty(method);
                            // 这里的objectFactory我们上文已经分析过了,是AdaptiveExtensionFactory.class实例,依次从Dubbo的SPI和Spring的容器中尝试获取指定的对象。
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                // 通过set方法完成注入。
                                method.invoke(instance, object);
                            }
                        } 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;
    }

2.3.4.Dubbo SPI"包装类"与""依赖注入"功能测试

​ 上文在分析ExtensionLoader#createExtension的过程中,发现Dubbo的SPI机制在创建实现类实例的时候,会对实现类实例进行依赖注入,依赖注入完毕之后,会用包装类封装实现类,并再对包装类进行依赖注入。现在我们就来测试下这两个功能。

​ 以上文"2.1.例子"章节中的代码为基础。

​ 在Database接口下添加包装类DatabaseNameUpperCaseWrapper:

package com.huang.dubboprovider.spi;

import org.springframework.util.StringUtils;

/**
 * Database的包装类
 * 将getDatabaseName()方法返回的字符串进行装饰,将小写字母全都转成大写字母。
 *
 */
public class DatabaseNameUpperCaseWrapper implements Database {

    public DatabaseNameUpperCaseWrapper(Database database) {
        this.delegate = database;
    }

    private final Database delegate;

    private UsingDatabaseRecord databaseRecord;

    public void setUsingDatabaseRecord(UsingDatabaseRecord databaseRecord) {
        this.databaseRecord = databaseRecord;
    }


    @Override
    public String getDatabaseName() {
        String databaseName = delegate.getDatabaseName();
        databaseName = databaseName == null ? null : databaseName.trim().toUpperCase();
        if (databaseName != null && databaseName.length() > 0 && databaseRecord != null) {
            databaseRecord.usingDatabaseName.add(databaseName);
        }
        return databaseName;
    }
}

在 META-INF/services/com.huang.dubboprovider.spi.Database 文件中配置DatabaseNameUpperCaseWrapper:

mysql=com.huang.dubboprovider.spi.MySQLDatabase
psql=com.huang.dubboprovider.spi.PostgreSQLDatabase
sqlserver=com.huang.dubboprovider.spi.SQLServerDatabase
databaseNameUpperCaseWrapper=com.huang.dubboprovider.spi.DatabaseNameUpperCaseWrapper

在Spring容器中添加bean,UsingDatabaseRecord:

package com.huang.dubboprovider.spi;

import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Component("databaseRecord")
public class UsingDatabaseRecord {

    // 记录正在被使用的 Database 的名字。
    public final List<String> usingDatabaseName = new ArrayList<>();

}

用单元测试验证Dubbo SPI的“包装类”和“依赖注入”功能,详见每个单元测试方法上的注释:

package com.huang.dubboprovider.spi;

import org.apache.dubbo.common.extension.ExtensionLoader;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeansException;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

@SpringBootTest
public class DubboSPITest implements ApplicationContextAware {

    private ApplicationContext springContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.springContext = applicationContext;
    }

    /**
     * 测试 Dubbo SPI 的包装类机制
     *
     * {@link Database} 接口存在包装类 {@link DatabaseNameUpperCaseWrapper},
     * 会包装{@link Database} 接口所有的实现类,将getDatabaseName()方法返回的字符串进行装饰,将小写字母全都转成大写字母。
     */
    @Test
    public void testWrapperClass() {
        Database mysql = ExtensionLoader.getExtensionLoader(Database.class).getExtension("mysql");
        assertEquals(mysql.getDatabaseName(), "MYSQL");
    }


    /**
     * 测试 无法直接获取Dubbo SPI的包装类
     */
    @Test
    public void testCantGetWrapperClassDirectly() {
        IllegalStateException exception = assertThrows(IllegalStateException.class, () ->
                ExtensionLoader.getExtensionLoader(Database.class)
                        .getExtension("databaseNameUpperCaseWrapper"));
        assertTrue(exception.getMessage().contains("No such extension com.huang.dubboprovider.spi.Database by name"));

    }

    /**
     * 测试 Dubbo SPI 的 从Spring上下文中获取依赖并注入的功能
     *
     * {@link DatabaseNameUpperCaseWrapper#databaseRecord} 通过Dubbo SPI 的依赖注入
     * 从Spring上下文中获取UsingDatabaseRecord的bean,
     * 每次通过{@link DatabaseNameUpperCaseWrapper}调用{@link Database#getDatabaseName()}方法时,
     * 只要{@link Database#getDatabaseName()}返回值trim之后不会null且不为空,就会保存到
     * {@link DatabaseNameUpperCaseWrapper#databaseRecord}中。
     *
     */
    @Test
    public void testDIFromSpringContext() {
        ExtensionLoader.getExtensionLoader(Database.class).getExtension("mysql").getDatabaseName();
        ExtensionLoader.getExtensionLoader(Database.class).getExtension("psql").getDatabaseName();
        ExtensionLoader.getExtensionLoader(Database.class).getExtension("sqlserver").getDatabaseName();

        UsingDatabaseRecord bean = springContext.getBean(UsingDatabaseRecord.class);
        assertEquals(bean.usingDatabaseName.size(), 3);
        assertTrue(bean.usingDatabaseName.contains("MYSQL"));
        assertTrue(bean.usingDatabaseName.contains("POSTGRESQL"));
        assertTrue(bean.usingDatabaseName.contains("SQLSERVER"));

    }
}

3.Dubbo SPI自适应机制分析

自适应机制,就是调用Dubbo SPI接口方法的时候,能够根据接口的URL类型入参,或者入参中的URL成员变量(Dubbo服务调用全流程,所有的配置都聚合在当前调用的URL上,所以URL是Dubbo服务调用全流程的核心(相当于是"门面模式"了)),去获取URL中指定的parameter的value,根据这个value去自动使用相应的接口扩展实现。

举个具体的例子(引用自SPI 自适应拓展),大家就很好理解了:

为了让大家对自适应拓展有一个感性的认识,下面我们通过一个示例进行演示。这是一个与汽车相关的例子,我们有一个车轮制造厂接口 WheelMaker:

public interface WheelMaker {
 Wheel makeWheel(URL url);
}

WheelMaker 接口的自适应实现类如下:

public class AdaptiveWheelMaker implements WheelMaker {
 public Wheel makeWheel(URL url) {
     if (url == null) {
         throw new IllegalArgumentException("url == null");
     }
     
 	// 1.从 URL 中获取 WheelMaker 名称
     String wheelMakerName = url.getParameter("Wheel.maker");
     if (wheelMakerName == null) {
         throw new IllegalArgumentException("wheelMakerName == null");
     }
     
     // 2.通过 SPI 加载具体的 WheelMaker
     WheelMaker wheelMaker = ExtensionLoader
         .getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName);
     
     // 3.调用目标方法
     return wheelMaker.makeWheel(url);
 }
}

AdaptiveWheelMaker 是一个代理类,与传统的代理逻辑不同,AdaptiveWheelMaker 所代理的对象是在 makeWheel 方法中通过 SPI 加载得到的。makeWheel 方法主要做了三件事情:

  1. 从 URL 中获取 WheelMaker 名称
  2. 通过 SPI 加载具体的 WheelMaker 实现类
  3. 调用目标方法

接下来,我们来看看汽车制造厂 CarMaker 接口与其实现类。

public interface CarMaker {
    Car makeCar(URL url);
}

public class RaceCarMaker implements CarMaker {
    WheelMaker wheelMaker;
 
    // 通过 setter 注入 AdaptiveWheelMaker
    public setWheelMaker(WheelMaker wheelMaker) {
        this.wheelMaker = wheelMaker;
    }
 
    public Car makeCar(URL url) {
        Wheel wheel = wheelMaker.makeWheel(url);
        return new RaceCar(wheel, ...);
    }
}

上面的例子是手动实现自适应机制的方式。如果每一个Dubbo SPI都要手动实现的话,那可太麻烦了。所以Dubbo提供了@Adaptive注解。

3.1.@Adaptive

@Adaptive注解可以注解在Dubbo SPI接口的任意一个需要实现自适应机制的方法上,也可以注解在Dubbo SPI接口的实现类上。前者表示,由Dubbo通过java字节码技术(默认使用javassist)为对应的接口自动生成adaptive实例。后者表示,被注解的实现类的实例为对应Dubbo SPI接口的adaptive实例,具体的自适应逻辑,由用户自己编码实现。

下面是Adaptive注解的代码说明:

package org.apache.dubbo.common.extension;

import org.apache.dubbo.common.URL;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
   
    /**
     * value表示dubbo URL中指定的parameter的名字。
     * 可以指定多个,比如:
     * 		{"key1","key2"}
     * 		当URL中存在key1时,就以key1的value值为准,去加载url.getParameter("key1")对应的Dubbo SPI接口实现类。
     * 		当URL不存在key1时,就去找key2。
     * 		如果key1和key2都没有,就以找默认key,默认key的生成规则如下:
     * 			假如Dubbo SPI接口的类名(class的simpleName)叫 : YyyInvokerWrapper
     *			则默认key的值为: yyy.invoker.wrapper。
     *		如果最终这个没有找到目标的key的value,这个时候存在两种情况:
     *			1.@SPI的value中配置了默认使用的扩展类,直接用默认的扩展类。
     *			2.@SPI的value没配置,抛出异常IllegalStateException
     *
     * 		
     * 也可以不指定value,会默认去找默认key,如果默认key对应的值最终没有找到,也会抛出IllegalStateException
     */
    String[] value() default {};

}

3.2.自适应机制例子

先准备代码。

创建一个新的SPI接口 Song

package com.huang.dubboprovider.spi.adaptive;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.SPI;

/**
 * 歌曲
 */
@SPI
public interface Song {

    /**
     * 歌词
     *
     * @return
     */
    String getLyric(URL url);

    default void notAdaptiveMethod() {};

}

再给Song接口创建两个实现类:

package com.huang.dubboprovider.spi.adaptive;

import org.apache.dubbo.common.URL;

public class HappySong implements Song {

    private static final String LYRIC =
            "新年好啊,新年好啊。祝贺大家新年好,我们唱歌我们跳舞,祝贺大家新年好";
    @Override
    public String getLyric(URL url) {
        return LYRIC;
    }
}		
package com.huang.dubboprovider.spi.adaptive;

import org.apache.dubbo.common.URL;

public class SadSong implements Song {

    private static final String LYRIC = "我爱的人,不是我的爱人。";

    @Override
    public String getLyric(URL url) {
        return LYRIC;
    }
}

创建配置文件”META-INF/services/com.huang.dubboprovider.spi.adaptive.Song“:

happy=com.huang.dubboprovider.spi.adaptive.HappySong
sad=com.huang.dubboprovider.spi.adaptive.SadSong

下面来分别展示下@Adaptive自适应类实现的两种方式,分别是将@Adaptive注解放在SPI接口内部的方法上,由Dubbo帮我们自动生成自适应类。以及将@Adaptive放在我们自己写的SPI接口的实现类上面,我们在编写实现类中实现自SPI接口的方法时,自己在方法内部编写自适应的逻辑。

3.2.1.@Adaptive放到SPI接口的方法上

我们在Song接口的getLyric方法上添加@Adaptive注解,@Adaptive的value没有设置,根据规则,value默认为”song“

package com.huang.dubboprovider.spi.adaptive;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;

/**
 * 歌曲
 */
@SPI
public interface Song {

    /**
     * 歌词
     *
     * @return
     */
    @Adaptive
    String getLyric(URL url);

    default void notAdaptiveMethod() {};

}

然后我们写一个main方法测试下:

package com.huang.dubboprovider.spi.adaptive;

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

public class SPIAdaptiveTest {

    public static void main(String[] args) {

        final String happySongURL = "http://xxxxxx?song=happy";
        final String sadSongURL = "http://xxxxxx?song=sad";
        final String noSongTypeURL = "http://xxxxxx";

        ExtensionLoader<Song> extensionLoader = ExtensionLoader.getExtensionLoader(Song.class);
        Song adaptiveExtension = extensionLoader.getAdaptiveExtension();
        System.out.println(happySongURL + " : " + adaptiveExtension.getLyric(URL.valueOf(happySongURL)));
        System.out.println(sadSongURL + " : " + adaptiveExtension.getLyric(URL.valueOf(sadSongURL)));
        System.out.println(noSongTypeURL + " : " + adaptiveExtension.getLyric(URL.valueOf(noSongTypeURL)));

    }
}

执行结果:

http://xxxxxx?song=happy : 新年好啊,新年好啊。祝贺大家新年好,我们唱歌我们跳舞,祝贺大家新年好

http://xxxxxx?song=sad : 我爱的人,不是我的爱人。

Exception in thread "main" java.lang.IllegalStateException: Failed to get extension (com.huang.dubboprovider.spi.adaptive.Song) name from url (http://xxxxxx) use keys([song])
	at com.huang.dubboprovider.spi.adaptive.Song$Adaptive.getLyric(Song$Adaptive.java)
	at com.huang.dubboprovider.spi.adaptive.SPIAdaptiveTest.main(SPIAdaptiveTest.java:18)

因为"http://xxxxxx"中没有名称为”song“的parameter,所以Dubbo的自适应机制无法找到对应的扩展类,就报错了。为了容错,我们可以给@SPI设置默认的扩展类,我们给Song接口上的@SPI注解的value设置成happy,再来执行下上面的main方法:

​```
@SPI("happy")
public interface Song {
	......
}
http://xxxxxx?song=happy : 新年好啊,新年好啊。祝贺大家新年好,我们唱歌我们跳舞,祝贺大家新年好
http://xxxxxx?song=sad : 我爱的人,不是我的爱人。
http://xxxxxx : 新年好啊,新年好啊。祝贺大家新年好,我们唱歌我们跳舞,祝贺大家新年好

可以看到,当自适应机制无法找到指定的扩展类时,就会使用@SPI注解中给目标接口配置的默认扩展类。

下面我们来看下,如果用自适应类实例执行没有被添加@Apdative的接口方法时,会怎么样:

package com.huang.dubboprovider.spi.adaptive;

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

public class SPIAdaptiveTest {

    public static void main(String[] args) {
        ExtensionLoader<Song> extensionLoader = ExtensionLoader.getExtensionLoader(Song.class);
        Song adaptiveExtension = extensionLoader.getAdaptiveExtension();
        adaptiveExtension.notAdaptiveMethod();
    }
}
Exception in thread "main" java.lang.UnsupportedOperationException: The method public default void com.huang.dubboprovider.spi.adaptive.Song.notAdaptiveMethod() of interface com.huang.dubboprovider.spi.adaptive.Song is not adaptive method!
	at com.huang.dubboprovider.spi.adaptive.Song$Adaptive.notAdaptiveMethod(Song$Adaptive.java)
	at com.huang.dubboprovider.spi.adaptive.SPIAdaptiveTest.main(SPIAdaptiveTest.java:16)

直接抛出UnsupportedOperationException,提示Song接口的notAdaptiveMethod方法不是自适应方法。

3.2.2.@Adaptive放到SPI接口的实现类上

将Song接口上的getLyric方法上的@Adaptive注解删除。然后创建我们自定义的自适应类,实现Song接口,如下:

package com.huang.dubboprovider.spi.adaptive;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.common.extension.SPI;
import org.apache.dubbo.common.utils.StringUtils;

@Adaptive
public class AdaptiveSong implements Song {

    private static final String PARAM_KEY = "song";

    @Override
    public String getLyric(URL url) {

        if (url == null) {
            throw new IllegalStateException("URL shouldn't be null");
        }

        // 先从URL中获取key为“song”的parameter的value
        String songType = url.getParameter(PARAM_KEY);

        if (StringUtils.isEmpty(songType)) {
            // 如果url中没有设置song,则去获取Song接口上,在@SPI注解中设置的默认扩展类
            SPI spi = Song.class.getAnnotation(SPI.class);
            songType = spi.value();
        }

        if (StringUtils.isEmpty(songType)) {
            // 自适应扩展最终没有获取到要使用的扩展类的名字,就抛出异常。
            throw new IllegalStateException(
                    String.format("Failed to get extension (%s) name from url (" + url.toString() + ") use keys(%s)",
                    Song.class.getName(), PARAM_KEY));
        }

        // 自适应扩展最终获取到要使用的扩展类的名字,通过ExtensionLoader中获取扩展类实例,并执行目标方法。
        Song extension = ExtensionLoader.getExtensionLoader(Song.class).getExtension(songType);

        return "使用了手动编写的自适应类:" + extension.getLyric(url);
    }
}

在”META-INF/services/com.huang.dubboprovider.spi.adaptive.Song“配置文件中配置我们刚刚编写的自适应类:

happy=com.huang.dubboprovider.spi.adaptive.HappySong
sad=com.huang.dubboprovider.spi.adaptive.SadSong
adaptive=com.huang.dubboprovider.spi.adaptive.AdaptiveSong

编写一个main方法,测试下我们手动编码的自适应类:

package com.huang.dubboprovider.spi.adaptive;

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

public class SPIAdaptiveTest {

    public static void main(String[] args) {
        ExtensionLoader<Song> extensionLoader = ExtensionLoader.getExtensionLoader(Song.class);
        Song adaptiveExtension = extensionLoader.getAdaptiveExtension();
        System.out.println(adaptiveExtension.getLyric(URL.valueOf("http://xxxxxx?song=sad")));
        System.out.println(adaptiveExtension.getLyric(URL.valueOf("http://xxxxxx")));

    }
}

执行结果(因为Song接口上的@SPI注解中指定了默认的扩展类为"happy",所以当在URL中找不到指定的扩展类的时候,就使用默认的扩展类HappySong去执行getLyric方法):

使用了手动编写的自适应类:我爱的人,不是我的爱人。
使用了手动编写的自适应类:新年好啊,新年好啊。祝贺大家新年好,我们唱歌我们跳舞,祝贺大家新年好

3.2.3.两种@Adaptive配置方式的优先级

如果我们在Song接口的getLyric方法上添加@Adaptive注解,同时配置了自定义的@Adaptive类,那么最终使用自适应机制的时候,使用的时Dubbo为我们动态生成的自适应类还是我们自己手动编写的自适应类?,下面我们来测试下。在上文3.2.2章节的代码基础上,在Song接口的getLyric接口上,重新加上@Adaptive注解,然后执行3.2.2章节中的main方法,输出结果如下:

使用了手动编写的自适应类:我爱的人,不是我的爱人。
使用了手动编写的自适应类:新年好啊,新年好啊。祝贺大家新年好,我们唱歌我们跳舞,祝贺大家新年好

由上可知,当配置了我们手动编写的自适应类,以我们手动编写的自适应类为准。

3.2.4.自适应机制如何获取URL

上文3.2.1章节中,我们演示Dubbo使用动态创建的自适应类例子中,Song接口中被@Adaptive注解的getLyric

方法的入参是URL,但是在Dubbo的代码中,@Adaptive方法也是可以加到其他入参不是URL类型的方法上,比如Protocol接口:

package org.apache.dubbo.rpc;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;

/**
 * Protocol. (API/SPI, Singleton, ThreadSafe)
 */
@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();

}

Protocol的exprot方法上有@Adaptive注解,但是它的入参中是没有URL参数的。那Dubbo给Protocol动态创建自适应类的方法的时候该如何处理呢?可以看下org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#generateUrlAssignmentIndirectly方法:

  private String generateUrlAssignmentIndirectly(Method method) {
        Class<?>[] pts = method.getParameterTypes();
        
        // find URL getter method
        for (int i = 0; i < pts.length; ++i) {
            for (Method m : pts[i].getMethods()) {
                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) {
                    // 添加了@Adaptive的方法中,只要存在这样的入参:入参对应的类中存在public、非static、get开头、没有入参、返回类型是URL的方法就行。Dubbo会记录这个入参的index,以及从这个入参对象中获取URL的get方法,在动态创建被@Adaptive注解的方法时,直接对入参调用被记录的get方法来获取URL。
                    return generateGetUrlNullCheck(i, pts[i], name);
                }
            }
        }
        
        // getter method not found, throw
        throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName()
                        + ": not found url parameter or url attribute in parameters of method " + method.getName());

    }

3.3.ExtensionLoader.getAdaptiveExtension()加载自适应类过程源码分析

ExtensionLoader.getAdaptiveExtension():

// 获取指定@SPI接口的自适应类
public T getAdaptiveExtension() {
    	// 自适应类被创建后会缓存在ExtensionLoader实例的cachedAdaptiveInstance字段中
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            // 如果自适应类之前尝试创建过且创建失败了,会把失败的信息保存在createAdaptiveInstanceError中
            if (createAdaptiveInstanceError == null) {
                // 如果自适应类不存在,且没有曾尝试创建且失败了,则去创建自适应类实例。
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        // 老规矩,为了线程安全,使用double-check,内层check时需持有cachedAdaptiveInstance即Hodler实例的对象锁。
                        try {
                            // 创建自适应对象
                            instance = createAdaptiveExtension();
                            // 将创建的自适应对象保存到cachedAdaptiveInstance中。
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

下面来看看创建自适应实例的具体方法 ExtensionLoader#createAdaptiveExtension:

  private T createAdaptiveExtension() {
        try {
            // getAdaptiveExtensionClass()获取自适应类的Class对象,下面分析这个方法。
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

ExtensionLoader#getAdaptiveExtensionClass

	// 获取自适应类的Class对象
	private Class<?> getAdaptiveExtensionClass() {
        // getExtensionClasses方法我们在2.3.1章节中已经讲了一部分了,从getExtensionClasses方法到调用loadExtensionClasses,再到调用loadDirectory方法,这个过程之间的逻辑都已经讲了,loadDirectory方法内部的代码还没分析。所以我们接下来分析getExtensionClasses方法的时候,就从loadDirectory方法开始。
        getExtensionClasses();
        // cachedAdaptiveClass用来保存被@Adaptive方法注解的类的Class对象,即我们自己手动编码实现的自适应类的Class对象。
        if (cachedAdaptiveClass != null) {
            //如果在当前的SPI接口下,我们有手动编码实现自适应类,则使用我们自己编写的自适应类,优先级高于Dubbo帮我们自动生成的自适应类,我们在3.2.3章节中已经验证测试过这个逻辑。
            return cachedAdaptiveClass;
        }
        // 如果在当前的SPI接口下,我们没有手动编码实现自适应类,则由Dubbo通过字节码技术帮我们自动生成自适应类,具体逻辑在createAdaptiveExtensionClass方法内实现。
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
	}

ExtensionLoader.loadDirectory:

    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        String fileName = dir + type;
        try {
            // 保存指定的SPI接口的配置文件,配置文件可能有多个。
            Enumeration<java.net.URL> urls;
            // 获取当前的ClassLoader
            ClassLoader classLoader = findClassLoader();
            // 加载指定的SPI接口的配置文件
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    // 加载SPI接口的配置文件
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

ExtensionLoader.loadResource:

    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
       			// 一行行的读取指定SPI接口配置文件
                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) {
                                // 每读一行,加载一个配置在SPI配置文件中的扩展类。
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } 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);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

ExtensionLoader#loadClass:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // 在SPI配置文件中配置的扩展类必须时class不能是其他类型,你如说enum或者interface
    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)) {
        // 如果当前类被添加了@Adaptive,则保存到ExtensionLoader实例的cachedAdaptiveClass字段上,一个SPI接口下,不能有多个类上面被添加了@Adaptive注解,否则会抛出IllegalStateException异常。
        cacheAdaptiveClass(clazz);
    } else if (isWrapperClass(clazz)) {
        // 如果是包装类,则保存到ExtensionLoader实例的cachedWrapperClasses字段中
        cacheWrapperClass(clazz);
    } else {
        // 检查有没有默认的构造方法,且是public的
        clazz.getConstructor();
        // 设置扩展类的名字
        if (StringUtils.isEmpty(name)) {
            // 加入SPI配置文件中扩展类的名字为空,就去对应的Class上面的@Extension中去找
            name = findAnnotationName(clazz);
            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)) {
            // 如果扩展类上面有@Activate注解,就将其保存到ExtensionLoader的cachedActivates字段中
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // 将每个扩展类,和他的名字保存到ExtensionLoader的cachedNames中,如果这个扩展类有多个名字,只保存第一个,详见cacheName方法。
                cacheName(clazz, n);
                // 将解析到的扩展类放入extensionClasses,最后extensionClasses会在getExtensionClasses()方法中被设置到ExtensionLoader实例的cachedClasses字段中,注意cachedClasses字段中保存的扩展类,不包括自适应类和包装类。
                saveInExtensionClass(extensionClasses, clazz, n);
            }
        }
    }
}

如果在当前的SPI接口下,我们没有手动编码实现自适应类,则由Dubbo通过字节码技术帮我们自动生成自适应类,具体逻辑在createAdaptiveExtensionClass方法内实现。

ExtensionLoader#createAdaptiveExtensionClass

private Class<?> createAdaptiveExtensionClass() {
    	// dubbo中很好的遵循了面向对象原则以及单一职责原则,将创建动态的自适应的逻辑都封装在了AdaptiveClassCodeGenerator的generate方法中。generate方法返回自动生成的自适应类代码。
    	// generate方法的代码就不分析了,感兴趣的好兄弟们可以自己去看看(我太懒了,呜呜呜~)
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
    	// 通过SPI机制获取Compile接口的手动编写的自适应类AdaptiveCompiler,AdaptiveCompiler判断用户有无指定java代码编译器,如果没有,则使用默认的javassist来编译dubbo动态生成的自适应类的源码。
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    	// 将源码编译成class,并返回。
        return compiler.compile(code, classLoader);
    }

4.总结

通过分析Dubbo SPI机制的源码,除了对Dubbo SPI机制的细节更加的熟悉,同时也学习到了很多对编码有用的思想以及编码手段:

  1. 使用SPI + 微内核的方式,可以构建易于扩展的应用。
  2. 加载资源的时候,可以使用 懒加载 & 缓存 的方式。
  3. 多线程的情况下创建一个对象,可以通过 "double-check & volatile关键字"来保证线程安全。
  4. 多线程的情况下创建一个对象,可以通过给被创建对象包装一层Holder对象,由Holder对象来作为对象锁,来保证线程安全。
  5. 多考虑"约定大于配置"原则,对于每个配置点,都给出一个默认的配置,方便第三方的使用,Springboot的Auto-configuration就很好的秉持了"约定大于配置的原则",所以才能做到开箱即用。
  6. 编码的时候牢记"面向接口编码"的原则,明确划分类的职责,保证类职责分明,高内聚低耦合,这样的代码才好维护和扩展。
  7. 使用”模板方法模式“、”命令模式“等设计方式给代码逻辑中预留”钩子“,来达到易于扩展的目的。

参考

Dubbo SPI

SPI 自适应拓展