dubbo 扩展点 分析

本文深入探讨了Dubbo的扩展点机制,对比了它与JDK SPI的区别。通过示例展示了如何利用Java SPI动态替换和添加组件。Dubbo针对JDK SPI的效率低和无法有效区分扩展类的问题进行了优化,如全局缓存和AOP支持。通过源码分析,揭示了Dubbo如何自动生成和编译动态代理适配器类,以及依赖注入的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

dubbo

一直使用dubbo 却没有仔细研读 dubbo的代码 所以打算近期一段时间抽空研究一下 dubbo的源码。

学习dubbo之前需要掌握的基础知识

JDK的spi
java 多线程、线程池
Netty相关的基础知识
Zookeeper基础知识,zkClient的api
设计模式,如工厂模式、装饰模式、模板模式、单例模式

dubbo 扩展点机制

先从dubbo扩展点开始 因为dubbo中四处都在用 看不懂这个 dubbo没法看

java spi

面向接口编程中,我们会根据不同的业务抽象出不同的接口,然后根据不同的业务实现建立不同规则的类,因此一个接口会实现多个实现类,在具体调用过程中,指定对应的实现类,当业务发生变化时会导致新增一个新的实现类,亦或是导致已经存在的类过时,就需要对调用的代码进行变更,具有一定的侵入性。

举个例子:

/**
 * @author LDZ
 * @date 2019/5/25 2:48 PM
 */
public interface api {
    // 输出
    void shout();
}

// 早起业务有两个实现类 一个 cat 一个 dog 两个实现类:
// dog
/**
 * @author LDZ
 * @date 2019/5/25 2:49 PM
 */
public class Dog implements api {
    @Override
    public void shout() {
        System.out.println("wang wang wang");
    }
}
// cat
/**
 * @author LDZ
 * @date 2019/5/25 2:49 PM
 */
public class Cat implements api {
    @Override
    public void shout() {
        System.out.println("miao miao miao");
    }
}
// 调用方
public class ApiClient {

   private final static Map<String,api> API_LIST = new HashMap<> ();

   static {
       API_LIST.put ( Dog.class.getSimpleName(), new Dog ());
       API_LIST.put ( Cat.class.getSimpleName(), new Cat ());
   }

   public void doInvoke(String apiType) {
       api a = ApiClient.API_LIST.get (apiType);
       if (a != null) {
           a.shout();
       }
   }
}

如果未来业务发生了变化,多了一个 Pig 就要新增 Pig实现类,同时要修改ApiClient的static中的API_LIST 代码块,如果需要多个实现进行处理,就需要更加复杂的维护性。因此 java spi解决了调用者对实现的依赖关系,能够在配置文件中对实现进行动态的替换、添加(组件)。

java SPI 当服务的提供者,提供服务接口的一种实现后,在jar包中的 META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件就是该接口服务的具体实现类。当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。
沿用上边的例子:

path = /resources/META-INF/services/com.yi23.common.api
com.yi23.common.util.Cat
com.yi23.common.util.Dog
public class SpiApiClient {

   private final static Map<String,api> API_LIST = new HashMap<> ();

   static {
       // 使用ServiceLoader
       ServiceLoader<api> serviceLoader = ServiceLoader.load (api.class);
       Iterator<api> iterator = serviceLoader.iterator ();
       while (iterator.hasNext ()) {
           api itr = iterator.next ();
           API_LIST.put ( itr.getClass().getSimpleName(), itr);
       }
   }

   /**
    * 描述:调用相关协议
    * @since JDK 1.8
    */
   public void doInvoke(String apiType) {
     api a = ApiClient.API_LIST.get (apiType);
        if (a != null) {
            a.shout ();
        }
   }
}

可以看到利用SPI 未来增加实现,或者替换已存在实现,只要修改 配置文件 /resources/META-INF/services/com.yi23.common.api 就可以了

dubbo扩展点和JDK spi 的区别

JDK 中的 SPI 有如下缺陷 :

  • 需要调用服务时必须加载所有扩展类并生成对象,效率低
  • 扩展类没有标志可以进行有效的区分。调用方需要调用某一个扩展类时无法有效指定。

Dubbo针对上述缺陷 对 serviceLoader进行了改写:

  • dubbo 用到了大量的全局缓存 所有Extension都缓存在chachedInstances中,该对象类型是ConcurrentMap<Stirng, Holder>
  • 可以动态获取扩展对象
  • Dubbo 扩展点提供了 AOP
  • Dubbo 扩展点提供了 IOC (后续分析)

源码

dubbo spi 的目的是什么? 帮我们获取一个我们所需要的指定的对象。如何获取所需的对象呢?通过调用ExtensionLoader.getExtension(String name),从这个代码段入手分析这段代码的整个流程

在Dubbo源码中大面积使用这种写法,都是获得某个接口的适配类,在真正执行的时候才决定最终的作用类

    private static final Protocol protocol =  ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();`

首先看一下 Protocol.class 到底是什么

/**
 * Protocol. (API/SPI, Singleton, ThreadSafe)
 */
SPI("dubbo")
public interface Protocol {

    /**
     * 获取默认端口
     *
     * @return default port
     */
    int getDefaultPort();

    /**
     * 暴露远程服务
     * 1. Protocol 应该在收到请求后记录请求源地址
     * RpcContext.getContext().setRemoteAddress();
     * 2. export() 必须是幂等的, 也就是说, 当暴露相同的URL时候,调用一次和两次是没有区别的
     * 3. 调用方实例由框架传入,协议不需要关心
     *
     */
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    /**
     * 引用远程服务
     */
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    /**
     * 释放协议: <br>
     */
    void destroy();

}

这是一个协议接口 类上有一个 @SPI 注解(默认值 “dubbo”), 在方法上有 @Adaptive 注解
这个 SPI 注解来标识为spi接口,其中value值为默认扩展类标识:

package com.ryan;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    /**
     * 默认扩展类标识
     */
    String value() default "";

}

继续分析 getExtensionLoader()方法。

     // 因为每一个扩展类加载器只能加载特定的SPI扩展的实现,所以要获得某个扩展的实现的话首先要找到他对应的扩展类加载器(ExtensionLoader) 一个扩展接口的所有实现都是被同一个扩展类加载器来加载的
    @SuppressWarnings("unchecked")
    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!");
        }
        // 要有 @SPI 注解
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type (" + type +
                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
        }
        // 先从缓存上取值,如果没有,就去new一个新的,然后放进缓存
        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 的构造方法

    private ExtensionLoader(Class<?> type) {
     // 给 type 赋值, 给objectFactory 赋值
        this.type = type;
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

对于 ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(), 此时传入的type是Protcol.class, 继续执行 ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
此时分为两个步骤分析:

  1. ExtensionLoader.getExtensionLoader(ExtensionFactory.class) 此时 type 是 ExtensionFactory.class 得到一个 ExtensionLoader 实例 此实例中 objectFactorynull
  2. getAdaptiveExtension()cachedAdaptiveInstance 赋值

首先看 getAdaptiveExtension() 方法

    @SuppressWarnings("unchecked")
    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
            // 单例双重检查 如果没有则进入`createAdaptiveExtension()`方法
            //cachedAdaptiveInstance作为一个Holder(只有简单的get和set方法),也是一个锁对象
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            instance = createAdaptiveExtension();
                            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;
    }

createAdaptiveExtension()方法分两部分 一个是injectExtension 一个是 getAdaptiveExtensionClass().newInstance()

// 创建一个接口的适配类
    @SuppressWarnings("unchecked")
    private T createAdaptiveExtension() {
        try {
            //获取 AdaptiveExtensionClass 并完成注入
            //基本分两步:
            // 1.获取适配器类 getAdaptiveExtensionClass().newInstance()
            // 2.在适配器里面注入其他的扩展点 injectExtension
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

getAdaptiveExtensionClass(),获取一个适配器扩展点的类

// 获得一个 适配器扩展点 
    private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    
    // 双重检查锁判断 防止同一个扩展点被多次加载
    // 加载当前扩展所有实现,看是否有实现类上被标注为@Adaptive
    private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // loadExtensionClasses 会加载所有的配置文件,将配置文件中对应的的类加载到当前的缓存中
                    // load完之后该classes已经保留了所有的扩展类映射关系
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
 // loadExtensionClasses() 方法
  // 此方法已经getExtensionClasses方法同步过
    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
        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;
    }
    

这里有个type.getAnnotation(SPI.class),这个type就是刚刚再初始化ExtensionLoader的时候传入的,我们先看type=ExtensionFactory.class的情况,ExtensionFactory接口类上有@SPI注解,但是value为空,然后三次调用loadFile方法,分别对应Dubbo扩展点的三个配置文件路径,在源码中我们可以找到ExtensionFactory对应的文件,如下:

path=dubbo-common/target/classes/META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory 

adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory

path = META-INF/dubbo/internal/org.apache.dubbo.common.extension.ExtensionFactory

spring=org.apache.dubbo.config.spring.extension.SpringExtensionFactor

通过 loadDirectory() 方法,最终extensionClasses返回SpringExtensionFactorySpiExtensionFactory 缓存到cachedClasses中。AdaptiveExtensionFactory因为类上有@Adaptive注解,所以直接缓存到cachedAdaptiveClass中。

至此 我们拿到了 extensionClasses, 并将其缓存到了cachedClasses中, 重新进入createAdaptiveExtensionClass()方法,这个方法的目的是自动生成和编译一个动态代理适配器类,名字叫Protocol$Adaptive, 这里又用到了一个Compile扩展点,可以看到,这里用到了ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension()
在这里插入图片描述
执行`complier.compile(code, classLoader) 如下:
在这里插入图片描述

其中DEFAULT_COMPILER值为 JavassistCompiler,执行loader.getExtension(name),结果是得到JavassistCompiler实例,这里是一个装饰模式的设计,最终调用JavassistCompiler.compile()方法得到Protocol$Adpative

回到分析这段代码的入口:
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); 其得到的返回值就是 Protocol$Adpative 这个代理类为:

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

    public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
        public void destroy() {
            throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
        }

        public int getDefaultPort() {
            throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
        }

        public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
            if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
            if (arg0.getUrl() == null)
                throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
            org.apache.dubbo.common.URL url = arg0.getUrl();
            String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
            if (extName == null)
                throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
            org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
            return extension.export(arg0);
        }

        public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
            if (arg1 == null) throw new IllegalArgumentException("url == null");
            org.apache.dubbo.common.URL url = arg1;
            String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
            if (extName == null)
                throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
            org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
            return extension.refer(arg0, arg1);
        }
    }

这时 我们得到了 Protocol的适配代理类, 最后 我们看下 injectExtension(instance) 方法

    // 通过反射自动调用instance的set方法把自身的属性注入进去,解决的扩展类依赖问题,也就是说解决扩展类依赖扩展类的问题
private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                // 1.拿到所有的方法
                for (Method method : instance.getClass().getMethods()) {
                // 判断是不是 set 方法
                    if (isSetter(method)) {
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        Class<?> pt = method.getParameterTypes()[0];
                        if (ReflectUtils.isPrimitives(pt)) {
                            continue;
                        }
                        try {
                            // 得到属性名称,比如setName方法就得到name属性名称
                            String property = getSetterProperty(method);
                            // 从objectFactory中获取所需要注入的实例
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                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;
    }

至此 dubbo 完成了依赖注入,关于 dubbo 的扩展点机制的源代码 基本分析完成了

总结

1.为了获得一个扩展点的适配类,首先会看缓存中有没有已经加载过的适配类,如果有的话就直接返回,没有的话就进入第2步。
2.加载所有的配置文件,将所有的配置类都load进内存并且在ExtensionLoader内部做好缓存,如果配置的文件中有适配类就缓存起来,如果没有适配类就自行通过代码自行创建适配类并且缓存起来(代码之后给出样例)。
3.在加载配置文件的时候,会依次将包装类,自激活的类都进行缓存。
4.将获取完适配类时候,如果适配类的set方法对应的属性也是扩展点话,会依次注入对应的属性的适配类(循环进行)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值