Dubbo源码解析必读篇 — Dubbo SPI扩展机制

Dubbo源码解析必读篇 — Dubbo SPI扩展机制

Java SPI

在阅读本篇文章前,先介绍下 Java的 SPI机制, 典型的案例如: jdbc

在我们获取数据库连接时,需要写下面两行代码

 //1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//2. 获得数据库连接
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);

但是如果我们不写 第一行代码,同样可以获取数据库连接,原因就是使用了Java SPI 机制。
DriverManager 类的 static 方法中, 会通过 ServiceLoader.load(Driver.class) 来加载 跟资源路径 /META-INF/services/ 文件夹下的文件, 通过解析这些文件,来动态加载文件中配置好的 Driver驱动实现类:Class.forName(aDriver, true, loader)

Dubbo SPI 介绍

Dubbo 并没有使用 Java SPI, 而是重新实现了一套更强的SPI机制。

在我看来,Dubbo SPI 借鉴了 Java SPI 动态加载实现类机制 和 Spring 的 IOC 和 AOP 的思想。

Dubbo SPI 机制特点:

  • 通过配置文件动态加载接口实现类
  • 有管理 实现类 实例化后对象的 容器
  • IOC
  • AOP

这里 有个 URL总线的概念, 这个URL 是 Dubbo 自己的一个类,该类非常关键,它是贯穿了整个Dubbo 服务提供者与服务消费者 启动流程及 RPC调用 的一个关键点。后面会在源码分析中,慢慢渗透体会该URL,这里只要记住 URL是个总线。

Dubbo SPI 原理

Dubbo SPI案例:

与 Java SPI 类似, Dubbo SPI 也需要在 rescourse/META-INF/services/ 文件夹中先有相关接口的配置文件。

配置文件名 必须是 该接口类的全限定类名, 如:com.zzp.dubbo.spi.api.Car

该文件中 使用 key : value 键值对的方式, 声明 接口实现类的 名字 和 其全限定类名

red = com.zzp.dubbo.spi.impl.RedCar
black = com.zzp.dubbo.spi.impl.BlackCar

下面有两行代码,目的是为了获取接口实现类对象。

// 1. 获取接口类 Car.class 的 拓展加载器实例
ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);

// 2. 通过该接口类的拓展加载器实例, 获取名叫 red 的 Car.class接口的实现类。
Car redCar = extensionLoader.getExtension("red");

通过上面的基本案例,使用伪代码简单说明下,Dubbo SPI 获取 目标接口实现类对象的 原理过程:

  1. ExtensionLoader.getExtensionLoader(Car.class)

     //从缓存中 获取 Car.class 接口的 ExtensionLoader实例.
    
      ExtensionLoader<Car> extensionLoader = null; 
      // 缓存中 存在 则直接返回
      if(extensionLoader = ExtensionLoaderCache.get(Car.class)){
          return  extensionLoader;	
      }else{
          // 缓存中 不存在, 则创建一个放入缓存中, 并返回
          extensionLoader = new ExtensionLoader(Car.class)
          ExtensionLoaderCache.put(extensionLoader);
          return extensionLoader;   
      }
    
  2. extensionLoader.getExtension("red")

    // 实例 缓存容器
    Map<String,Object> cachedInstances = new HashMap();
    
    // 获取实例
    getExtension(String name){
        // 从缓存中 获取 名叫 red 的实例
    
        Car instance = null;
        // 缓存中有,则直接返回
        if( instance = cachedInstances.get(name)){
            return instance;
        }else{
            // 缓存中若没有,则创建一个存入缓存中,并返回。
            instance = createInstance(name);
            CachedInstances.put(instance);
            return instance;
        }
    }
    
    
    // 解析配置文件结果的缓存
    Map<String, Class<?>> extensionClasses = new HashMap();
    
    // 创建实例
    createInstance(String name){
        // 加载解析 rescoures/META-INF 中的文件,获取 该接口 所有实现类的全限定类名
        extensionClasses = loadClasses(Car.class);
        Class clazz= extensionClasses.get(name);
        // 通过反射创建实例对象
        return clazz.newInstance();
    }   
    

上面通过伪代码 简单介绍了 Dubbo SPI 的一个基本原理, 可以从中看到,使用了大量的 缓存(Map) 来存储 各种结果。 总的来说 就是 加载 配置文件,通过反射创建实例对象。 但是 其中还有 IOC 与 AOP 等实现,在后面的源码分析中会进行讲解。

Dubbo SPI 源码分析

Dubbo SPI 机制 实际上全程 就围绕着 ExtensionLoader 类 。 首先我们来看一下 ExtensionLoader类的 成员变量。

1.ExtensionLoader成员变量

 // =================直到下一个分割线之间 都是 static修饰的全局变量和常量===================== 
   // 日志
	private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
	
	// 下面三个 是 Dubbo SPI 要加载配置文件 的目录路径
    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

    // 每个接口类的 拓展载器 的缓存  
    // 例如:{ 
    //         Car.class : ExtensionLoader<Car>, 
    //         Driver.class : ExtensionLoader<Driver>
    //      }
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();

    // 可扩展实例的缓存   class -> object      相当于一个总的缓存表,缓存所有接口类的 实现类对象
    // 例如: {
    //         RedCar.class : redCar
    //         BlackCar.class : blackCar
    //       }
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();

    // ============================== 分割线 =========================================
	
    // 该ExtesionLoader实例 所管理的 接口类
    private final Class<?> type;

    // 后面会介绍
    private final ExtensionFactory objectFactory;

	// 
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();

    //  缓存 每个实现类的 名字与全限定类名的关系    beanName -> Class
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
		
	// 
    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();

    // 缓存 实现类名字 与 实现类对象的关系   beanName -> Object
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();

    // 自适应拓展实例缓存
    private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
    private volatile Class<?> cachedAdaptiveClass = null;

上面的变量 能理解最好, 不理解的话 待到后面的 方法源码中 仔细体会。 总之,Dubbo SPI 使用了大量的缓存,来提升性能,避免每次获取都要重新加载或者创建。

2.获取拓展加载器

在Dubbo SPI 原理的 示例中,有两行代码, 首先我们以 第一行代码ExtensionLoader.getExtensionLoader(Car.class); 为入口,来分析全过程。

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
		// ... 省略的部分 是做一些检查判断, 不重要
        
  		//  从全局 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;
    }

上面主要就是,从缓存中获取 目标 接口类 的 拓展加载器。 关键在于 创建 ExtensionLoader 的过程。

下面我们来看 创建 ExtensionLoader 的代码

    private ExtensionLoader(Class<?> type) {
        
        // 接口类 赋值
        this.type = type;
        
        // ExtensionFatory 表示拓展机制的工厂 (在Dubbo里面有SPI扩展机制,也有Spring扩展机制)
  
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

上面代码简单明了,就是属性赋值。 重点看一下在 objectFacotry 属性赋值时 ,实际上分为两步:

  1. ExtensionLoader.getExtensionLoader(ExtensionFactory.class) :又通过上面的方法加载了一次,当然初次是从缓存中获取不到的,所以又会进入该 构造方法内, 而此时 type == ExtensionFactory.class, 则 ExtensionFactory 的 拓展加载器实例 中 objectFactory属性 为 null 。
  2. getAdaptiveExtension() : 通过 ExtensionFactory 的 拓展加载器 获取 其 自适应拓展实例 ,且最终将结果赋值给 那个初次创建拓展加载器对象的 objectFactory属性。

有点绕很正常,仔细梳理下,其实并不难, 最终我们会把 目标 锁定在 ExtensionLoadergetAdaptiveExtension 方法上。

3.获取自适应拓展实例

    public T getAdaptiveExtension() {
        // 从 自适应拓展实例缓存中 获取 
        Object instance = cachedAdaptiveInstance.get();
        
        // DCL 检查
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            // 创建 自适应拓展 实例
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }
		
        return (T) instance;
    }

上面代码 很简单,主要就是 DCL缓存 的那一套代码。 最终将目标 锁定在 createAdaptiveExtension() 创建 自适应拓展 实例 上。

    // 创建 自适应拓展 实例
    private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

上面代码可以分为以下几个步骤看:

  1. getAdaptiveExtensionClass() : 获取自适应拓展类的 Class 。
  2. newInstance() : 通过 反射 创建 实例
  3. injectExtension() : IOC 依赖注入

newInstance 这个很好理解,就是简单的通过反射创建实例, 下面我们主要看 1,3两个步骤。

首先来看 步骤1 getAdaptiveExtensionClass()

    // 获取自适应拓展类的Class 类
    private Class<?> getAdaptiveExtensionClass() {

        //  加载解析配置文件
        //  1.将 可拓展类(正常的类)                 缓存到  cachedClasses中
        //  2.将 自适应拓展类(@Adaptive标注的类)      缓存到 cachedAdaptiveClass中
        //  3.将 包装类(有参数为接口类的构造方法的类)  缓存到  cachedWrapperClasses 中
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        // 如果没有用户配置文件中没有自定义的 自适应拓展类, 
        // 那么Dubbo就会帮我们生成一个 自适应拓展类
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

上面代码很短, 但做了详细的注释,介绍了其中两个方法主要的目的。

  1. 加载解析 /resourse/META-INF/xxx/ 下的配置文件,解析分别获取 可拓展类, 自适应拓展类 和 包装类。
  2. 若没有用户自定义的拓展类,则Dubbo默认为我们生成一个。

4.加载解析配置文件

接着我们跟进 getExtensionClasses() 看看如何加载解析配置文件的。

   
// ==============  缓存 DCL 那一套模板代码 ======================== 
   
	private Map<String, Class<?>> getExtensionClasses() {
        // cachedClasses 缓存中 存储了 配置文件中对应的   name 和 Class类
        Map<String, Class<?>> classes = cachedClasses.get();
        // DCL
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 从配置文件中加载可拓展类
                    classes = loadExtensionClasses();
                    // 最终会将 解析出来的 可拓展类字典 合并到 cachedClasses 缓存中
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

上面又是先从缓存中获取,若获取不到则去加载解析配置文件,然后将结果存入缓存中。接着我们看 loadExtensionClasses()

    private Map<String, Class<?>> loadExtensionClasses() {
        
        // 缓存默认的 拓展类的名字   到 cacehdDefaultName中
        // 具体就是 获取该 接口类 上的SPI注解中的 value值。 
        // 当value中只有一个值时,直接缓存。
        // 当value中有多个值时,取第一个
        cacheDefaultExtensionName(); 

        /**
         * 分别从 以下几个 文件夹中获取 该接口类的 信息
         *
         * "META-INF/dubbo/internal/"
         *
         * META-INF/dubbo/
         *
         * META-INF/services/
         */

        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;
    }

上面代码中 cacheDefaultExtensionName() 方法比较简单,不做详细分析,实际上就是通过反射获取 接口类上@SPI注解中的 value值,然后存入cacheDefaultName 缓存中。

重点来看 loadDirectory() 方法

    /**
     * @param extensionClasses  map 需要将解析结果存入该字典中
     * @param dir               需要加载解析的文件目录
     * @param type              需要加载解析的类型,也就是文件名
     */
    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        //  组成具体的文件路径地址 (文件夹路径 + 接口全地址名) 
// 例如: /META-INF/services/ + "org.apache.dubbo.common.extension.ExtensionFactory"
        String fileName = dir + type;
        try {
            // 
            Enumeration<java.net.URL> urls;
            // 获取类加载器, 当前用于加载文件, 后面会用于加载Class字节码
            ClassLoader classLoader = findClassLoader();
            
            // 加载 文件
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            
            // 遍历加载的文件 
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    // 获取加载后文件的URL地址,注意这里是 java.net.URL
                    java.net.URL resourceURL = urls.nextElement();
                    // 参数1: map
                    // 参数2: 类加载器
                    // 参数3: 文件地址等信息
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

上面代码没什么好细聊的,简单概括功能就是 :

  1. 生成文件名
  2. 加载文件
  3. 解析文件 loadResource(extensionClasses, classLoader, resourceURL)

跟进 loadResource(extensionClasses, classLoader, resourceURL)方法

    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;
                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=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
             //参数1: extensionClasses   map
             //参数2: resourceURL       文件URL
             //参数3: Class类         例: SpiExtensionFactory.class
             //参数4: name           相当于beanName   例: spi
                              loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } 
                }
            }

上面代码,主要就是 读取 对应接口类配置文件中 的每一行,按照 key ,value的形式解析出来,并加载 value(全限定类名)获取Class类。

目标接着锁定到 loadClass() 方法中

    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) 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.");
        }
        // 判断该实现类的 @Adaptive 注解  有该注解表示 该实现类是自适应拓展类
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);   // 缓存到 cachedAdaptiveClass(自适应拓展类缓存)
        }

        // 判断该实现类 是不是 包装类 ,通过该类的构造方法来判断(有一个可以传入type类型的构造方法就是包装类)
        else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);    // 缓存到 cachedWrapperClasses(包装类缓存)
        } else {
            
            //TODO 为什么没有赋值,这里单纯的执行getConsructor() 什么意义?
            //  我猜测 这里主要是检查 有没有构造器方法。否则会抛出NoSuchMethodException异常
            clazz.getConstructor();
            
            if (StringUtils.isEmpty(name)) {
                // 当没有name时 
                // 先会取@Extension注解的value值作为name 
                // 若没有@Extension注解 则从截取其类名作为name
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }

			//若有多个beanName(car1,car2,car3 = com.xx.xx.xx) 切分beanName
            String[] names = NAME_SEPARATOR.split(name);  
            
            if (ArrayUtils.isNotEmpty(names)) {
                
                // 若该类上有 @Activate注解  则 放到 激活类缓存中
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    
                    // 缓存到 cacheNames  (若有多个名字对应 一个实现类时,只缓存第一个名字)
                    //例如:{Red.class: "red"}  
                    cacheName(clazz, n);
                    
                    // 存入extensionClasses 字典中  例如:{"red":Red.class}
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }

上述代码看上去长, 其实逻辑非常清晰,注释中都有对应的解释。 其主要工作简单说明下:

  1. 判断该类是否是 自适应拓展类,若是 加入缓存中
  2. 判断该类是否是 包装类 ,若是 加入缓存中
  3. 如果以上都不是,则属于 可拓展类(普通实现类) ,则缓存到 cachedNames ,并添加到 extensionClasses

至此,加载解析配置文件的相关代码都 分析完毕。

再次总结下 加载解析配置文件 主要的工作有哪些:

  1. 缓存默认的拓展类名字(@SPI 的value值)到 cacehdDefaultName 中
  2. 将 可拓展类(正常的类) 缓存到 cachedClasses 和 cacheNames 中
  3. 将 自适应拓展类(@Adaptive标注的类) 缓存到 cachedAdaptiveClass中
  4. 将 包装类(有参数为接口类的构造方法的类) 缓存到 cachedWrapperClasses 中

目前我们的主线是 要从 ExtensionFactoryExtensionLoader 拓展加载器中 获取 cachedAdaptiveClass 自适应拓展类。

因此我们先看一下 ExtensionFactory 该接口类

ExtensionFactory

/**
 * 扩展工厂
 * ExtensionFactory
 */
@SPI   
public interface ExtensionFactory {

    /**
     * Get extension.
     *
     * @param type object type.
     * @param name object name.
     * @return object instance.
     */
    <T> T getExtension(Class<T> type, String name);
}

另外还有 该资源路径下的 META-INF/ 配置文件, 该文件所属的文件路径为

resources/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

根据上面的信息 ,按照加载解析配置文件的流程 ,能得出以下几个结果:

  1. ExtensionFactory 的@SPI 注解没有 value值, 则 cacheDefaultName 缓存为 null

  2. 配置文件中的AdaptiveExtensionFactory 上有 @Adaptive注解,则cachedAdaptiveClass 中缓存的是 AdaptiveExtensionFactory.class

  3. 配置文件中没有 符合包装类的 特征,则 cachedWrapperClasses 缓存为 null

  4. 配置文件中 SpiExtensionFactory 属于可拓展类,则会缓存到 cachedClassescacheNames

    cachedClasses = {"spi": SpiExtensionFactory.class}

    cacheNames = {SpiExtensionFactory.class : "spi"}

接下来,我们回到 getAdaptiveExtensionClass() 方法中, 加载解析配置文件后,会查看此次解析配置文件中 有没有缓存 cachedAdaptiveClass , 若没有 说明用户没有自定义自适应类,那么此时 需要 Dubbo 为我们生成一个 自适应拓展类。 而目前我们从 ExtensionFactory 接口类中 解析并得到了 cachedAdaptiveClassAdaptiveExtensionFactory.class, 那么就可以直接返回。

那么就会走到 AdaptiveExtensionFactory.class 的 newInstance 方法 创建实例

然后 执行 injectExtension() 方法 ,进行依赖注入, 该方法 先不分析 ,因为 getAdaptiveExtensionClass() 方法中 还剩下一行代码,先分析它。

现在我们先抛开 ExtensionFactory 该接口, 假设有其它的 接口类,而它并没有 标有 @Adaptive 注解的实现类,那么就需要来到 Dubbo 帮我们生成 自适应拓展实现类的方法。

5.创建自适应拓展类

生成 自适应拓展类 的前提 是在 接口类 的所有方法中, 必须存在 至少一个方法有 @Adaptive 注解。

也就是 createAdaptiveExtensionClass() 该方法。

    private Class<?> createAdaptiveExtensionClass() {
        
        // 使用 自适应类代码生成器  根据 当前接口类型以及cachedDefaultName  生成代码。
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        
        // 获取类加载器
        ClassLoader classLoader = findClassLoader();
        
        // 再次通过 Dubbo SPI 加载获取 Compiler接口类 的 自适应拓展类 默认为 AdaptiveCompiler类
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        // 通过获取的 Compiler接口类型 的实现类 根据 生成的代码 来得到字节码
        return compiler.compile(code, classLoader);
    }

上述代码虽然只有四行代码, 但是其中做了很多事情, 大致可以理解为, 按照 目标接口类中的方法, 生成代码字符串, 该代码字符串实际上就是一个 接口实现类的代码。 然后经过 Compiler 的自适应拓展类 AdaptiveCompiler 编译此代码 得到 对应的字节码。

生成代码

首先来看一下,是如何 生成 代码的。

创建自适应类代码生成器实例

    public AdaptiveClassCodeGenerator(Class<?> type, String defaultExtName) {
        this.type = type;
        this.defaultExtName = defaultExtName;
    }

该代码非常简单,就是属性赋值而已。

    // 生成代码
	public String generate() {
    
        // 判断 接口类方法 中 有没有 标注 @Adaptive注解的 方法
        // 若没有 则报错
        if (!hasAdaptiveMethod()) {
            throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
        }
		
        StringBuilder code = new StringBuilder();
        // 生成包名
        // 例如: package com.zzp.dubbo.spi.api;
        code.append(generatePackageInfo());
        
        // 生成import
        // 例如: import org.apache.dubbo.common.extension.ExtensionLoader;
        code.append(generateImports());
        
        // 生成 类声明
        // 例如: public class Driver$Adaptive implements com.zzp.dubbo.spi.api.Driver {
        code.append(generateClassDeclaration());
        
        // 遍历所有方法,为每个接口方法 生成对应的实现方法
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            code.append(generateMethod(method));
        }
        code.append("}");
        
        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        return code.toString();
    }

最终生成的代码模板案例如下:

package com.zzp.dubbo.spi.api;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class Driver$Adaptive implements com.zzp.dubbo.spi.api.Driver {
    
    // 接口方法的实现
	public void driveCar(org.apache.dubbo.common.URL arg0)  {
        // 参数检查
		if (arg0 == null) throw new IllegalArgumentException("url == null");
        
        // 根据 URL参数中的 对应值,获取要执行的 实现类对象的 名字
		org.apache.dubbo.common.URL url = arg0;
		String extName = url.getParameter("driver");
        
		if(extName == null) 
            throw new IllegalStateException("Failed to get extension 			(com.zzp.dubbo.spi.api.Driver) name from url (" + url.toString() + ") use keys([driver])");
        
        // 通过Dubbo SPI 中的容器,获取对应 extname的 实例对象
		com.zzp.dubbo.spi.api.Driver extension =	(com.zzp.dubbo.spi.api.Driver)ExtensionLoader.getExtensionLoader(com.zzp.dubbo.spi.api.Driver.class).getExtension(extName);
        
        // 调用该对象的 当前目标方法
		extension.driveCar(arg0);
    }
}

以上是生成 XXX&Adaptive 代码的分析。

那么下面我们要看一 下 Dubbo SPI 如何加载并获取 Compiler 接口的 AdaptiveExtensionClass 的。

Dubbo SPI 加载和解析 Compiler 接口类的过程 上面都讲过了, 因此我们只需要看 Compiler接口 和其对应的配置文件 。

/**
 * Compiler. (SPI, Singleton, ThreadSafe)
 */
@SPI("javassist")  // 默认使用 名叫 javassist 的实现类
public interface Compiler {

    /**
     * Compile java source code.
     *
     * @param code        Java source code
     * @param classLoader classloader
     * @return Compiled class
     */
    Class<?> compile(String code, ClassLoader classLoader);

}

/META-INF/dubbo/internal/org.apache.dubbo.common.compiler.Compiler

adaptive=org.apache.dubbo.common.compiler.support.AdaptiveCompiler
jdk=org.apache.dubbo.common.compiler.support.JdkCompiler
javassist=org.apache.dubbo.common.compiler.support.JavassistCompiler

从中不难发现, AdaptiveCompiler为自适应拓展类,而JdkCompilerJavassistCompiler 都是可拓展类

因为 CompilerExtensionLoader 调用了 getAdaptiveExtension() ,那么会解析配置文件,然后会在getAdaptiveExtensionClass() 方法中 得到 AdaptiveCompiler.class ,最终会 通过反射生成实例,并调用injectExtension() 方法 进行依赖注入。

6.Dubbo SPI IOC依赖注入

我们先看一下 AdaptiveCompiler 该类

@Adaptive
public class AdaptiveCompiler implements Compiler {

    // compiler实现类
    private static volatile String DEFAULT_COMPILER;

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

    @Override
    public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        String name = DEFAULT_COMPILER; // copy reference
        if (name != null && name.length() > 0) {
            compiler = loader.getExtension(name);
        } else {
            compiler = loader.getDefaultExtension();
        }
        return compiler.compile(code, classLoader);
    }

}

以 AdaptiveCompiler 为例 看看是如何 进行依赖注入的

    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                // 获取并遍历 实例 的所有方法
                for (Method method : instance.getClass().getMethods()) {

                    // 判断有没有set方法, 有则对setXX方法进行赋值
                    if (isSetter(method)) {
                        // 若方法上有 @DisableInject 注解 表示 禁止依赖注入
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        // 获取该方法的参数类型
                        Class<?> pt = method.getParameterTypes()[0];
                        
                        // 判断该参数类型 是否是私有的或者是基础类型,若是则 不进行依赖注入
                        // 这里 由于 AdaptiveCompiler 的方法参数 为 java.lang.String,则跳过
                        if (ReflectUtils.isPrimitives(pt)) {
                            continue;
                        }
                        try {
                            //  截取 方法名 除了 set 之后的字符串,作为参数名
                            String property = getSetterProperty(method);

                            // 从objectFactory中获取 属性值
                            // 参数一: pt      参数类型
                            // 参数二: property  参数名
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        }
                    }
                }
        return instance;
    }

上述代码中,重点关注的 是 objectFactory.getExtension(pt, property);

从前面我们已知 objectFactoryAdaptiveExtensionFactory 实例对象。

    // 参数一: pt      参数类型
    // 参数二: property  参数名
    public <T> T getExtension(Class<T> type, String name) {
        
        // 遍历所有的 扩展工厂实例对象, 调用其 getExtension()方法
        // 已知 默认仅有一个扩展工厂为 SpiExtensionFactory
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

上面的代码 就是 遍历 所有的 扩展工厂 调用他们的 getExtension 方法,默认仅有一个扩展工厂 为 SpiExtensionFactory。

来看一下它的实现

    // 参数一: pt   参数类型
    // 参数二: property  参数名
	public <T> T getExtension(Class<T> type, String name) {
        // 判断 该类型是否是 接口类型 和 该接口类 上是否有 @SPI 注解
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            
            //  获取该接口类的 ExtensionLoader
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
           
            if (!loader.getSupportedExtensions().isEmpty()) {
                // 获取其自适应扩展类对象,并返回
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

从上述 Dubbo SPI 依赖注入代码分析,可知 若想要进行依赖注入,首先是 目标注入的属性类型 必须是 接口类型,且该接口类型必须有 @SPI 注解 , 其次要实现该属性的 setXXX 方法.

这里有一个循环依赖的问题,我目前不知道Dubbo SPI 是怎么解决的? 执行时不会报错,并有正确结果。但是Debug的过程中,会无限递归最终导致 StackOverFlowError 错误 ,很奇怪

万般皆下品,唯有读书高!
原文地址:https://www.cnblogs.com/s686zhou/p/15761067.html