阅读 334

你一定要知道的Dubbo SPI

Dubbo为了更好的达到OCP原则(即"对扩展开放,对修改封闭"的原则),采用了"微内核+插件"的架构。那么什么是微内核架构呢?微内核架构也被称为插件化架构(Plug-in Architecture),这是一种面向功能进行拆分的可扩展性架构。内核功能是比较稳定的,只负责管理插件的生命周期,不会因为系统功能的扩展而不断进行修改。功能上的扩展全部封装到插件之中,插件模块是独立存在的模块,包含特定的功能,能拓展内核系统的功能。

微内核架构中,内核通常采用 Factory、IoC、OSGi 等方式管理插件生命周期,Dubbo 最终决定采用 SPI 机制来加载插件,Dubbo SPI 参考 JDK 原生的 SPI 机制,进行了性能优化以及功能增强。因此,在讲解 Dubbo SPI 之前,我们有必要先来介绍一下 JDK SPI 的工作原理。

JDK SPI

SPI(Service Provider Interface)主要是被框架开发人员使用的一种技术。例如,使用 Java 语言访问数据库时我们会使用到 java.sql.Driver 接口,不同数据库产品底层的协议不同,提供的 java.sql.Driver 实现也不同,在开发 java.sql.Driver 接口时,开发人员并不清楚用户最终会使用哪个数据库,在这种情况下就可以使用 Java SPI 机制在实际运行过程中,为 java.sql.Driver 接口寻找具体的实现。

1. JDK SPI 机制

当服务的提供者提供了一种接口的实现之后,需要在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,此文件记录了该 jar 包提供的服务接口的具体实现类。当某个应用引入了该 jar 包且需要使用该服务时,JDK SPI 机制就可以通过查找这个 jar 包的 META-INF/services/ 中的配置文件来获得具体的实现类名,进行实现类的加载和实例化,最终使用该实现类完成业务功能。

image.png 首先我们需要创建一个 Log 接口,来模拟日志打印的功能:

public interface Log { 

    void log(String info); 

} 

复制代码

接下来提供两个实现—— Logback 和 Log4j,分别代表两个不同日志框架的实现,如下所示:

public class Logback implements Log { 

    @Override 

    public void log(String info) { 

        System.out.println("Logback:" + info); 

    } 

} 

public class Log4j implements Log { 

    @Override 

    public void log(String info) { 

        System.out.println("Log4j:" + info); 

    } 

} 
复制代码

在项目的 resources/META-INF/services 目录下添加一个名为 com.zzz.Log 的文件,这是 JDK SPI 需要读取的配置文件,具体内容如下:

com.zzz.impl.Log4j 
com.zzz.impl.Logback 
复制代码

最后创建 main() 方法,其中会加载上述配置文件,创建全部 Log 接口实现的实例,并执行其 log() 方法,如下所示:

public class Main { 

    public static void main(String[] args) { 

        ServiceLoader<Log> serviceLoader =  

                ServiceLoader.load(Log.class); 

        Iterator<Log> iterator = serviceLoader.iterator(); 

        while (iterator.hasNext()) { 

            Log log = iterator.next(); 

            log.log("JDK SPI");  

        } 

    } 

} 

// 输出如下: 
// Log4j:JDK SPI 
// Logback:JDK SPI 
复制代码

下面我们通过一个简单的示例演示下 JDK SPI 的基本使用方式:

2. JDK SPI 源码分析

通过上述示例,我们可以看到 JDK SPI 的入口方法是 ServiceLoader.load() 方法,接下来我们就对其具体实现进行深入分析。

在 ServiceLoader.load() 方法中,首先会尝试获取当前使用的 ClassLoader(获取当前线程绑定的 ClassLoader,查找失败后使用 SystemClassLoader),然后调用 reload() 方法,调用关系如下图所示:

image.png 在 reload() 方法中,首先会清理 providers 缓存(LinkedHashMap 类型的集合),该缓存用来记录 ServiceLoader 创建的实现对象,其中 Key 为实现类的完整类名,Value 为实现类的对象。之后创建 LazyIterator 迭代器,用于读取 SPI 配置文件并实例化实现类对象。

ServiceLoader.reload() 方法的具体实现,如下所示:

// 缓存,用来缓存 ServiceLoader创建的实现对象 

private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); 

public void reload() { 

    providers.clear(); // 清空缓存 

    lookupIterator = new LazyIterator(service, loader); // 迭代器 

} 
复制代码

在前面的示例中,main() 方法中使用的迭代器底层就是调用了 ServiceLoader.LazyIterator 实现的。Iterator 接口有两个关键方法:hasNext() 方法和 next() 方法。这里的 LazyIterator 中的next() 方法最终调用的是其 nextService() 方法,hasNext() 方法最终调用的是 hasNextService() 方法,调用关系如下图所示:

image.png 首先来看 LazyIterator.hasNextService() 方法,该方法主要负责查找 META-INF/services 目录下的 SPI 配置文件,并进行遍历,大致实现如下所示:

private static final String PREFIX = "META-INF/services/"; 

Enumeration<URL> configs = null; 

Iterator<String> pending = null; 

String nextName = null; 

private boolean hasNextService() { 

    if (nextName != null) { 

        return true; 

    } 

    if (configs == null) { 

        // PREFIX前缀与服务接口的名称拼接起来,就是META-INF目录下定义的SPI配 

        // 置文件(即示例中的META-INF/services/com.xxx.Log) 

        String fullName = PREFIX + service.getName(); 

        // 加载配置文件 

        if (loader == null) 

            configs = ClassLoader.getSystemResources(fullName); 

        else 

            configs = loader.getResources(fullName); 

    } 

    // 按行SPI遍历配置文件的内容 

    while ((pending == null) || !pending.hasNext()) {  

        if (!configs.hasMoreElements()) { 

            return false; 

        } 

        // 解析配置文件 

        pending = parse(service, configs.nextElement());  

    } 

    nextName = pending.next(); // 更新 nextName字段 

    return true; 

}   
复制代码

在 hasNextService() 方法中完成 SPI 配置文件的解析之后,再来看 LazyIterator.nextService() 方法,该方法负责实例化 hasNextService() 方法读取到的实现类,其中会将实例化的对象放到 providers 集合中缓存起来,核心实现如下所示:

private S nextService() { 

    String cn = nextName; 

    nextName = null; 

    // 加载 nextName字段指定的类 

    Class<?> c = Class.forName(cn, false, loader); 

    if (!service.isAssignableFrom(c)) { // 检测类型 

        fail(service, "Provider " + cn  + " not a subtype"); 

    } 

    S p = service.cast(c.newInstance()); // 创建实现类的对象 

    providers.put(cn, p); // 将实现类名称以及相应实例对象添加到缓存 

    return p; 
} 
复制代码

以上就是在 main() 方法中使用的迭代器的底层实现。最后,我们再来看一下 main() 方法中使用ServiceLoader.iterator() 方法拿到的迭代器是如何实现的,这个迭代器是依赖 LazyIterator 实现的一个匿名内部类,核心实现如下:

public Iterator<S> iterator() { 

    return new Iterator<S>() { 

        // knownProviders用来迭代providers缓存 

        Iterator<Map.Entry<String,S>> knownProviders 

            = providers.entrySet().iterator(); 

        public boolean hasNext() { 

            // 先走查询缓存,缓存查询失败,再通过LazyIterator加载 

            if (knownProviders.hasNext())  

                return true; 

            return lookupIterator.hasNext(); 

        } 

        public S next() { 

            // 先走查询缓存,缓存查询失败,再通过 LazyIterator加载 

            if (knownProviders.hasNext()) 

                return knownProviders.next().getValue(); 

            return lookupIterator.next(); 

        } 

        // 省略remove()方法 

    }; 

} 
复制代码

以上就是在 main() 方法中使用的迭代器的底层实现。最后,我们再来看一下 main() 方法中使用ServiceLoader.iterator() 方法拿到的迭代器是如何实现的,这个迭代器是依赖 LazyIterator 实现的一个匿名内部类,核心实现如下:

public Iterator<S> iterator() { 

    return new Iterator<S>() { 

        // knownProviders用来迭代providers缓存 

        Iterator<Map.Entry<String,S>> knownProviders 

            = providers.entrySet().iterator(); 

        public boolean hasNext() { 

            // 先走查询缓存,缓存查询失败,再通过LazyIterator加载 

            if (knownProviders.hasNext())  

                return true; 

            return lookupIterator.hasNext(); 

        } 

        public S next() { 

            // 先走查询缓存,缓存查询失败,再通过 LazyIterator加载 

            if (knownProviders.hasNext()) 

                return knownProviders.next().getValue(); 

            return lookupIterator.next(); 

        } 

        // 省略remove()方法 

    }; 

} 
复制代码

3. JDK SPI 在 JDBC 中的应用

了解了 JDK SPI 实现的原理之后,我们再来看实践中 JDBC 是如何使用 JDK SPI 机制加载不同数据库厂商的实现类。

JDK 中只定义了一个 java.sql.Driver 接口,具体的实现是由不同数据库厂商来提供的。这里我们就以 MySQL 提供的 JDBC 实现包为例进行分析。

在 mysql-connector-java-*.jar 包中的 META-INF/services 目录下,有一个 java.sql.Driver 文件中只有一行内容,如下所示:

com.mysql.cj.jdbc.Driver 
复制代码

在使用 mysql-connector-java-*.jar 包连接 MySQL 数据库的时候,我们会用到如下语句创建数据库连接:

String url = "jdbc:xxx://xxx:xxx/xxx"; 
Connection conn = DriverManager.getConnection(url, username, pwd); 
复制代码

DriverManager 是 JDK 提供的数据库驱动管理器,其中的代码片段,如下所示:

static { 

    loadInitialDrivers(); 

    println("JDBC DriverManager initialized"); 

} 
复制代码

在调用 getConnection() 方法的时候,DriverManager 类会被 Java 虚拟机加载、解析并触发 static 代码块的执行;在 loadInitialDrivers() 方法中通过 JDK SPI 扫描 Classpath 下 java.sql.Driver 接口实现类并实例化,核心实现如下所示:

private static void loadInitialDrivers() { 

    String drivers = System.getProperty("jdbc.drivers") 

    // 使用 JDK SPI机制加载所有 java.sql.Driver实现类 

    ServiceLoader<Driver> loadedDrivers =  

           ServiceLoader.load(Driver.class); 

    Iterator<Driver> driversIterator = loadedDrivers.iterator(); 

    while(driversIterator.hasNext()) { 

        driversIterator.next(); 

    } 

    String[] driversList = drivers.split(":"); 

    for (String aDriver : driversList) { // 初始化Driver实现类 

        Class.forName(aDriver, true, 

            ClassLoader.getSystemClassLoader()); 

    } 

} 

复制代码

在 MySQL 提供的 com.mysql.cj.jdbc.Driver 实现类中,同样有一段 static 静态代码块,这段代码会创建一个 com.mysql.cj.jdbc.Driver 对象并注册到 DriverManager.registeredDrivers 集合中(CopyOnWriteArrayList 类型),如下所示:

static { 

   java.sql.DriverManager.registerDriver(new Driver()); 

} 
复制代码

在 getConnection() 方法中,DriverManager 从该 registeredDrivers 集合中获取对应的 Driver 对象创建 Connection,核心实现如下所示:

private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException { 

    // 省略 try/catch代码块以及权限处理逻辑 

    for(DriverInfo aDriver : registeredDrivers) { 

        Connection con = aDriver.driver.connect(url, info); 

        return con; 

    } 

} 
复制代码

Dubbo SPI

在开始介绍 Dubbo SPI 实现之前,我们先来统一下面两个概念。

  • 扩展点:通过 SPI 机制查找并加载实现的接口(又称“扩展接口”)。前文示例中介绍的 Log 接口、com.mysql.cj.jdbc.Driver 接口,都是扩展点。
  • 扩展点实现:实现了扩展接口的实现类。

通过前面的分析可以发现,JDK SPI 在查找扩展实现类的过程中,需要遍历 SPI 配置文件中定义的所有实现类,该过程中会将这些实现类全部实例化。如果 SPI 配置文件中定义了多个实现类,而我们只需要使用其中一个实现类时,就会生成不必要的对象。例如,org.apache.dubbo.rpc.Protocol 接口有 InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol、ThriftProtocol 等多个实现,如果使用 JDK SPI,就会加载全部实现类,导致资源的浪费。

Dubbo SPI 不仅解决了上述资源浪费的问题,还对 SPI 配置文件扩展和修改。

首先,Dubbo 按照 SPI 配置文件的用途,将其分成了三类目录。

  • META-INF/services/ 目录:该目录下的 SPI 配置文件用来兼容 JDK SPI 。
  • META-INF/dubbo/ 目录:该目录用于存放用户自定义 SPI 配置文件。
  • META-INF/dubbo/internal/ 目录:该目录用于存放 Dubbo 内部使用的 SPI 配置文件。

然后,Dubbo 将 SPI 配置文件改成了 KV 格式,例如:

dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
复制代码

其中 key 被称为扩展名(也就是 ExtensionName),当我们在为一个接口查找具体实现类时,可以指定扩展名来选择相应的扩展实现。例如,这里指定扩展名为 dubbo,Dubbo SPI 就知道我们要使用:org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类,只实例化这一个扩展实现即可,无须实例化 SPI 配置文件中的其他扩展实现类。

使用 KV 格式的 SPI 配置文件的另一个好处是:让我们更容易定位到问题。假设我们使用的一个扩展实现类所在的 jar 包没有引入到项目中,那么 Dubbo SPI 在抛出异常的时候,会携带该扩展名信息,而不是简单地提示扩展实现类无法加载。这些更加准确的异常信息降低了排查问题的难度,提高了排查问题的效率。

下面我们正式进入 Dubbo SPI 核心实现的介绍。

1. @SPI 注解

Dubbo 中某个接口被 @SPI注解修饰时,就表示该接口是扩展接口,前文示例中的 org.apache.dubbo.rpc.Protocol 接口就是一个扩展接口:

image.png @SPI 注解的 value 值指定了默认的扩展名称,例如,在通过 Dubbo SPI 加载 Protocol 接口实现时,如果没有明确指定扩展名,则默认会将 @SPI 注解的 value 值作为扩展名,即加载 dubbo 这个扩展名对应的 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类,相关的 SPI 配置文件在 dubbo-rpc-dubbo 模块中,如下图所示:

image.png 那 ExtensionLoader 是如何处理 @SPI 注解的呢?

ExtensionLoader 位于 dubbo-common 模块中的 extension 包中,功能类似于 JDK SPI 中的 java.util.ServiceLoader。Dubbo SPI 的核心逻辑几乎都封装在 ExtensionLoader 之中(其中就包括 @SPI 注解的处理逻辑),其使用方式如下所示:

Protocol protocol = ExtensionLoader 

   .getExtensionLoader(Protocol.class).getExtension("dubbo");
复制代码

这里首先来了解一下 ExtensionLoader 中三个核心的静态字段。

  • strategies(LoadingStrategy[]类型): LoadingStrategy 接口有三个实现(通过 JDK SPI 方式加载的),如下图所示,分别对应前面介绍的三个 Dubbo SPI 配置文件所在的目录,且都继承了 Prioritized 这个优先级接口,默认优先级是
 DubboInternalLoadingStrategy > DubboLoadingStrategy > ServicesLoadingStrateg
复制代码

image.png

  • EXTENSION_LOADERS(ConcurrentMap<Class, ExtensionLoader>类型):Dubbo 中一个扩展接口对应一个 ExtensionLoader 实例,该集合缓存了全部 ExtensionLoader 实例,其中的 Key 为扩展接口,Value 为加载其扩展实现的 ExtensionLoader 实例。

  • EXTENSION_INSTANCES(ConcurrentMap<Class<?>, Object>类型):该集合缓存了扩展实现类与其实例对象的映射关系。在前文示例中,Key 为 Class,Value 为 DubboProtocol 对象。

下面我们再来关注一下 ExtensionLoader 的实例字段。

  • type(Class<?>类型):当前 ExtensionLoader 实例负责加载扩展接口。

  • cachedDefaultName(String类型):记录了 type 这个扩展接口上 @SPI 注解的 value 值,也就是默认扩展名。

  • cachedNames(ConcurrentMap<Class<?>, String>类型):缓存了该 ExtensionLoader 加载的扩展实现类与扩展名之间的映射关系。

  • cachedClasses(Holder<Map<String, Class<?>>>类型):缓存了该 ExtensionLoader 加载的扩展名与扩展实现类之间的映射关系。cachedNames 集合的反向关系缓存。

  • cachedInstances(ConcurrentMap<String, Holder>类型):缓存了该 ExtensionLoader 加载的扩展名与扩展实现对象之间的映射关系。

    ExtensionLoader.getExtensionLoader() 方法会根据扩展接口从 EXTENSION_LOADERS 缓存中查找相应的 ExtensionLoader 实例,核心实现如下:

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { 
    
        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; 
    
    }
    复制代码

    得到接口对应的 ExtensionLoader 对象之后会调用其 getExtension() 方法,根据传入的扩展名称从 cachedInstances 缓存中查找扩展实现的实例,最终将其实例化后返回:

    public T getExtension(String name) { 
    
        // getOrCreateHolder()方法中封装了查找cachedInstances缓存的逻辑 
    
        Holder<Object> holder = getOrCreateHolder(name); 
    
        Object instance = holder.get(); 
    
        if (instance == null) { // double-check防止并发问题 
    
            synchronized (holder) { 
    
                instance = holder.get(); 
    
                if (instance == null) { 
    
                    // 根据扩展名从SPI配置文件中查找对应的扩展实现类 
    
                    instance = createExtension(name); 
    
                    holder.set(instance); 
    
                } 
    
            } 
    
        } 
    
        return (T) instance; 
    
    }
    复制代码

    在 createExtension() 方法中完成了 SPI 配置文件的查找以及相应扩展实现类的实例化,同时还实现了自动装配以及自动 Wrapper 包装等功能。其核心流程是这样的:

    1. 获取 cachedClasses 缓存,根据扩展名从 cachedClasses 缓存中获取扩展实现类。如果 cachedClasses 未初始化,则会扫描前面介绍的三个 SPI 目录获取查找相应的 SPI 配置文件,然后加载其中的扩展实现类,最后将扩展名和扩展实现类的映射关系记录到 cachedClasses 缓存中。这部分逻辑在 loadExtensionClasses() 和 loadDirectory() 方法中。

    2. 根据扩展实现类从 EXTENSION_INSTANCES 缓存中查找相应的实例。如果查找失败,会通过反射创建扩展实现对象。

    3. 自动装配扩展实现对象中的属性(即调用其 setter)。这里涉及 ExtensionFactory 以及自动装配的相关内容,本课时后面会进行详细介绍。

    4. 自动包装扩展实现对象。这里涉及 Wrapper 类以及自动包装特性的相关内容,本课时后面会进行详细介绍。

    5. 如果扩展实现类实现了 Lifecycle 接口,在 initExtension() 方法中会调用 initialize() 方法进行初始化。

    private T createExtension(String name) { 
    
        Class<?> clazz = getExtensionClasses().get(name); // --- 1 
    
        if (clazz == null) { 
    
            throw findException(name); 
    
        } 
    
        try { 
    
            T instance = (T) EXTENSION_INSTANCES.get(clazz); // --- 2 
    
            if (instance == null) { 
    
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); 
    
                instance = (T) EXTENSION_INSTANCES.get(clazz); 
    
            } 
    
            injectExtension(instance); // --- 3 
    
            Set<Class<?>> wrapperClasses = cachedWrapperClasses; // --- 4 
    
            if (CollectionUtils.isNotEmpty(wrapperClasses)) { 
    
                for (Class<?> wrapperClass : wrapperClasses) { 
    
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); 
    
                } 
    
            } 
    
            initExtension(instance); // ---5
    
            return instance; 
    
        } catch (Throwable t) { 
    
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " + 
    
                    type + ") couldn't be instantiated: " + t.getMessage(), t); 
    
        } 
    
    }
    复制代码

    2. @Adaptive 注解与适配器

    @Adaptive 注解用来实现 Dubbo 的适配器功能,那什么是适配器呢?这里我们通过一个示例进行说明。Dubbo 中的 ExtensionFactory 接口有三个实现类,如下图所示,ExtensionFactory 接口上有 @SPI 注解,AdaptiveExtensionFactory 实现类上有 @Adaptive 注解。

    image.png AdaptiveExtensionFactory 不实现任何具体的功能,而是用来适配 ExtensionFactory 的 SpiExtensionFactory 和 SpringExtensionFactory 这两种实现。AdaptiveExtensionFactory 会根据运行时的一些状态来选择具体调用 ExtensionFactory 的哪个实现。

    @Adaptive 注解还可以加到接口方法之上,Dubbo 会动态生成适配器类。例如,Transporter接口有两个被 @Adaptive 注解修饰的方法:

    @SPI("netty") 
    
    public interface Transporter { 
    
        @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) 
    
        RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException; 
    
        @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) 
    
        Client connect(URL url, ChannelHandler handler) throws RemotingException; 
    
    }
    复制代码

    Dubbo 会生成一个 Transporter$Adaptive 适配器类,该类继承了 Transporter 接口:

    public class Transporter$Adaptive implements Transporter { 
    
        public org.apache.dubbo.remoting.Client connect(URL arg0, ChannelHandler arg1) throws RemotingException { 
    
            // 必须传递URL参数 
    
            if (arg0 == null) throw new IllegalArgumentException("url == null"); 
    
            URL url = arg0; 
    
            // 确定扩展名,优先从URL中的client参数获取,其次是transporter参数 
    
            // 这两个参数名称由@Adaptive注解指定,最后是@SPI注解中的默认值 
    
            String extName = url.getParameter("client",
    
                url.getParameter("transporter", "netty")); 
    
            if (extName == null) 
    
                throw new IllegalStateException("..."); 
    
            // 通过ExtensionLoader加载Transporter接口的指定扩展实现 
    
            Transporter extension = (Transporter) ExtensionLoader 
    
                  .getExtensionLoader(Transporter.class) 
    
                        .getExtension(extName); 
    
            return extension.connect(arg0, arg1); 
    
        } 
    
        ... // 省略bind()方法 
    
    }
    复制代码

    生成 Transporter$Adaptive 这个类的逻辑位于 ExtensionLoader.createAdaptiveExtensionClass() 方法,若感兴趣你可以看一下相关代码,其中涉及的 javassist 等方面的知识,在后面的课时中我们会进行介绍。

    明确了 @Adaptive 注解的作用之后,我们回到 ExtensionLoader.createExtension() 方法,其中在扫描 SPI 配置文件的时候,会调用 loadClass() 方法加载 SPI 配置文件中指定的类,如下图所示:

    image.png loadClass() 方法中会识别加载扩展实现类上的 @Adaptive 注解,将该扩展实现的类型缓存到 cachedAdaptiveClass 这个实例字段上(volatile修饰):

    private void loadClass(){ 
        if (clazz.isAnnotationPresent(Adaptive.class)) { 
            // 缓存到cachedAdaptiveClass字段 
            cacheAdaptiveClass(clazz, overridden);
        } else ... // 省略其他分支 
    }
    复制代码

    我们可以通过 ExtensionLoader.getAdaptiveExtension() 方法获取适配器实例,并将该实例缓存到 cachedAdaptiveInstance 字段(Holder类型)中,核心流程如下:

    • 首先,检查 cachedAdaptiveInstance 字段中是否已缓存了适配器实例,如果已缓存,则直接返回该实例即可。

    • 然后,调用 getExtensionClasses() 方法,其中就会触发前文介绍的 loadClass() 方法,完成 cachedAdaptiveClass 字段的填充。

    • 如果存在 @Adaptive 注解修饰的扩展实现类,该类就是适配器类,通过 newInstance() 将其实例化即可。如果不存在 @Adaptive 注解修饰的扩展实现类,就需要通过 createAdaptiveExtensionClass() 方法扫描扩展接口中方法上的 @Adaptive 注解,动态生成适配器类,然后实例化。

    • 接下来,调用 injectExtension() 方法进行自动装配,就能得到一个完整的适配器实例。

    • 最后,将适配器实例缓存到 cachedAdaptiveInstance 字段,然后返回适配器实例。

    getAdaptiveExtension() 方法的流程涉及多个方法,这里不再粘贴代码,感兴趣的同学可以参考上述流程分析相应源码。

    此外,我们还可以通过 API 方式(addExtension() 方法)设置 cachedAdaptiveClass 这个字段,指定适配器类型(这个方法你知道即可)。

    总之,适配器什么实际工作都不用做,就是根据参数和状态选择其他实现来完成工作。

    3. 自动包装特性

    Dubbo 中的一个扩展接口可能有多个扩展实现类,这些扩展实现类可能会包含一些相同的逻辑,如果在每个实现类中都写一遍,那么这些重复代码就会变得很难维护。Dubbo 提供的自动包装特性,就可以解决这个问题。 Dubbo 将多个扩展实现类的公共逻辑,抽象到 Wrapper 类中,Wrapper 类与普通的扩展实现类一样,也实现了扩展接口,在获取真正的扩展实现对象时,在其外面包装一层 Wrapper 对象,你可以理解成一层装饰器。

    了解了 Wrapper 类的基本功能,我们回到 ExtensionLoader.loadClass() 方法中,可以看到:

    private void loadClass(){ 
    
        ... // 省略前面对@Adaptive注解的处理 
    
        } else if (isWrapperClass(clazz)) { // ---1 
    
            cacheWrapperClass(clazz); // ---2 
    
        } else ... // 省略其他分支
    
    }
    复制代码
    1. 在 isWrapperClass() 方法中,会判断该扩展实现类是否包含拷贝构造函数(即构造函数只有一个参数且为扩展接口类型),如果包含,则为 Wrapper 类,这就是判断 Wrapper 类的标准。

    2. 将 Wrapper 类记录到 cachedWrapperClasses(Set<Class<?>>类型)这个实例字段中进行缓存。

    前面在介绍 createExtension() 方法时的 4 处,有下面这段代码,其中会遍历全部 Wrapper 类并一层层包装到真正的扩展实例对象外层:

    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    
    if (CollectionUtils.isNotEmpty(wrapperClasses)) { 
    
        for (Class<?> wrapperClass : wrapperClasses) { 
    
            instance = injectExtension((T) wrapperClass 
    
                .getConstructor(type).newInstance(instance)); 
    
        } 
    
    }
    复制代码

    4. 自动装配特性

    在 createExtension() 方法中我们看到,Dubbo SPI 在拿到扩展实现类的对象(以及 Wrapper 类的对象)之后,还会调用 injectExtension() 方法扫描其全部 setter 方法,并根据 setter 方法的名称以及参数的类型,加载相应的扩展实现,然后调用相应的 setter 方法填充属性,这就实现了 Dubbo SPI 的自动装配特性。简单来说,自动装配属性就是在加载一个扩展点的时候,将其依赖的扩展点一并加载,并进行装配。

    下面简单看一下 injectExtension() 方法的具体实现:

    private T injectExtension(T instance) { 
    
        if (objectFactory == null) { // 检测objectFactory字段 
    
            return instance; 
    
        } 
    
    
    
        for (Method method : instance.getClass().getMethods()) { 
    
            ... // 如果不是setter方法,忽略该方法(略) 
    
            if (method.getAnnotation(DisableInject.class) != null) { 
    
                continue; // 如果方法上明确标注了@DisableInject注解,忽略该方法 
    
            } 
    
            // 根据setter方法的参数,确定扩展接口 
    
            Class<?> pt = method.getParameterTypes()[0]; 
    
            ... // 如果参数为简单类型,忽略该setter方法(略) 
    
            // 根据setter方法的名称确定属性名称 
    
            String property = getSetterProperty(method); 
    
            // 加载并实例化扩展实现类 
    
            Object object = objectFactory.getExtension(pt, property); 
    
            if (object != null) { 
    
                method.invoke(instance, object); // 调用setter方法进行装配 
    
            } 
    
        } 
    
        return instance; 
    
    }
    复制代码

    injectExtension() 方法实现的自动装配依赖了 ExtensionFactory(即 objectFactory 字段),前面我们提到过 ExtensionFactory 有 SpringExtensionFactory 和 SpiExtensionFactory 两个真正的实现(还有一个实现是 AdaptiveExtensionFactory 是适配器)。下面我们分别介绍下这两个真正的实现。

    第一个,SpiExtensionFactory。 根据扩展接口获取相应的适配器,没有到属性名称:

    @Override 
    
    public <T> T getExtension(Class<T> type, String name) { 
    
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { 
    
            // 查找type对应的ExtensionLoader实例 
    
            ExtensionLoader<T> loader = ExtensionLoader 
    
              .getExtensionLoader(type); 
    
            if (!loader.getSupportedExtensions().isEmpty()) { 
    
                return loader.getAdaptiveExtension(); // 获取适配器实现 
    
            } 
    
        } 
    
        return null; 
    
    }
    复制代码

    第二个,SpringExtensionFactory。 将属性名称作为 Spring Bean 的名称,从 Spring 容器中获取 Bean:

    public <T> T getExtension(Class<T> type, String name) { 
    
        ... // 检查:type必须为接口且必须包含@SPI注解(略) 
    
        for (ApplicationContext context : CONTEXTS) { 
    
            // 从Spring容器中查找Bean 
    
            T bean = BeanFactoryUtils.getOptionalBean(context,name,type); 
    
            if (bean != null) { 
    
                return bean; 
    
            } 
    
        } 
    
        return null; 
    
    }
    复制代码

    5. @Activate注解与自动激活特性

    这里以 Dubbo 中的 Filter 为例说明自动激活特性的含义,org.apache.dubbo.rpc.Filter 接口有非常多的扩展实现类,在一个场景中可能需要某几个 Filter 扩展实现类协同工作,而另一个场景中可能需要另外几个实现类一起工作。这样,就需要一套配置来指定当前场景中哪些 Filter 实现是可用的,这就是 @Activate 注解要做的事情。

    @Activate 注解标注在扩展实现类上,有 group、value 以及 order 三个属性。

    • group 属性:修饰的实现类是在 Provider 端被激活还是在 Consumer 端被激活。

    • value 属性:修饰的实现类只在 URL 参数中出现指定的 key 时才会被激活。

    • order 属性:用来确定扩展实现类的排序。

    我们先来看 loadClass() 方法对 @Activate 的扫描,其中会将包含 @Activate 注解的实现类缓存到 cachedActivates 这个实例字段(Map<String, Object>类型,Key为扩展名,Value为 @Activate 注解):

    private void loadClass(){ 
    
        if (clazz.isAnnotationPresent(Adaptive.class)) { 
    
            // 处理@Adaptive注解 
    
            cacheAdaptiveClass(clazz, overridden); 
    
        } else if (isWrapperClass(clazz)) { // 处理Wrapper类 
    
            cacheWrapperClass(clazz); 
    
        } else { // 处理真正的扩展实现类 
    
            clazz.getConstructor(); // 扩展实现类必须有无参构造函数 
    
            ...// 兜底:SPI配置文件中未指定扩展名称,则用类的简单名称作为扩展名(略) 
    
            String[] names = NAME_SEPARATOR.split(name); 
    
            if (ArrayUtils.isNotEmpty(names)) { 
    
                // 将包含@Activate注解的实现类缓存到cachedActivates集合中 
    
                cacheActivateClass(clazz, names[0]); 
    
                for (String n : names) { 
    
                    // 在cachedNames集合中缓存实现类->扩展名的映射 
    
                    cacheName(clazz, n);
    
                    // 在cachedClasses集合中缓存扩展名->实现类的映射 
    
                    saveInExtensionClass(extensionClasses, clazz, n, 
    
                         overridden); 
    
                } 
    
            } 
    
        } 
    
    }
    复制代码

    使用 cachedActivates 这个集合的地方是 getActivateExtension() 方法。首先来关注 getActivateExtension() 方法的参数:url 中包含了配置信息,values 是配置中指定的扩展名,group 为 Provider 或 Consumer。下面是 getActivateExtension() 方法的核心逻辑:

    1. 首先,获取默认激活的扩展集合。默认激活的扩展实现类有几个条件:①在 cachedActivates 集合中存在;②@Activate 注解指定的 group 属性与当前 group 匹配;③扩展名没有出现在 values 中(即未在配置中明确指定,也未在配置中明确指定删除);④URL 中出现了 @Activate 注解中指定的 Key。

    2. 然后,按照 @Activate 注解中的 order 属性对默认激活的扩展集合进行排序。

    3. 最后,按序添加自定义扩展实现类的对象。

    public List<T> getActivateExtension(URL url, String[] values, 
    
             String group) { 
    
        List<T> activateExtensions = new ArrayList<>(); 
    
        // values配置就是扩展名 
    
        List<String> names = values == null ?
    
                new ArrayList<>(0) : asList(values); 
    
        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {// ---1 
    
            getExtensionClasses(); // 触发cachedActivates等缓存字段的加载 
    
            for (Map.Entry<String, Object> entry :
    
                      cachedActivates.entrySet()) { 
    
                String name = entry.getKey(); // 扩展名 
    
                Object activate = entry.getValue(); // @Activate注解 
    
                String[] activateGroup, activateValue; 
    
                if (activate instanceof Activate) { // @Activate注解中的配置 
    
                    activateGroup = ((Activate) activate).group(); 
    
                    activateValue = ((Activate) activate).value(); 
    
                } else { 
    
                    continue; 
    
                } 
    
                if (isMatchGroup(group, activateGroup) // 匹配group 
    
                        // 没有出现在values配置中的,即为默认激活的扩展实现 
    
                        && !names.contains(name)
    
                        // 通过"-"明确指定不激活该扩展实现 
    
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
    
                        // 检测URL中是否出现了指定的Key 
    
                        && isActive(activateValue, url)) { 
    
                    // 加载扩展实现的实例对象,这些都是激活的 
    
                    activateExtensions.add(getExtension(name)); 
    
                } 
    
            } 
    
            // 排序 --- 2 
    
            activateExtensions.sort(ActivateComparator.COMPARATOR); 
    
        } 
    
    
    
        List<T> loadedExtensions = new ArrayList<>(); 
    
        for (int i = 0; i < names.size(); i++) { // ---3 
    
            String name = names.get(i); 
    
            // 通过"-"开头的配置明确指定不激活的扩展实现,直接就忽略了 
    
            if (!name.startsWith(REMOVE_VALUE_PREFIX) 
    
                    && !names.contains(REMOVE_VALUE_PREFIX + name)) { 
    
                if (DEFAULT_KEY.equals(name)) { 
    
                    if (!loadedExtensions.isEmpty()) { 
    
                        // 按照顺序,将自定义的扩展添加到默认扩展集合前面 
    
                        activateExtensions.addAll(0, loadedExtensions); 
    
                        loadedExtensions.clear(); 
    
                    } 
    
                } else { 
    
                    loadedExtensions.add(getExtension(name)); 
    
                } 
    
            } 
    
        } 
    
        if (!loadedExtensions.isEmpty()) { 
    
            // 按照顺序,将自定义的扩展添加到默认扩展集合后面 
    
            activateExtensions.addAll(loadedExtensions); 
    
        } 
    
        return activateExtensions; 
    
    }
    复制代码

文章分类
后端
文章标签