• 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 错误 ,很奇怪

    万般皆下品,唯有读书高!
  • 相关阅读:
    WP8.1通过StreamSocket连接C++服务器
    WP10通过StreamSocket连接C++服务器
    二维背包(两个限制条件)
    dp(多重背包)
    dp(完全背包)
    dfs(迷宫)
    bfs迷宫
    蚁人cp数
    二分(老死不相往来)
    前缀和(狼和野牛)
  • 原文地址:https://www.cnblogs.com/s686zhou/p/15761067.html
Copyright © 2020-2023  润新知