『Dubbo SPI源码分析』依赖注入机制分析

news2024/12/26 11:18:27

Dubbo Wrapper 依赖注入机制分析

  • 基于 2.7.0 版本
  • 上一章:『Dubbo SPI源码分析』Wrapper 机制分析
  1. 创建测试 demo
  • 首先创建一个接口,举例为 Car
package com.luban.dubbo_spi.api;

@SPI
public interface Car {
    public void getColor();
    public void getColorForUrl(URL url);
}
  • 根据接口,先创建一个实现类
package com.luban.dubbo_spi.impl;

public class RedCar implements Car {

    public void getColor() {
        System.out.println("red");
    }
    
    @Override
    public void getColorForUrl(URL url) {
    }
}
  • 创建实现类的自适应扩展类
package com.luban.dubbo_spi.impl;

@Adaptive
public class AdaptiveCar implements Car {

    @Override
    public void getColor() {
    }
    
    @Override
    public void getColorForUrl(URL url) {
        System.out.println("123123");
    }
}
  • 然后在 resources 创建一个目录 META-INF.services 目录
  • 并在 META-INF.services 目录中创建文件 com.luban.dubbo_spi.api.Car 一定要与接口同名
red = com.luban.dubbo_spi.impl.RedCar
com.luban.dubbo_spi.impl.AdaptiveCar
  • 再创建一个接口,名为 Driver
package com.luban.dubbo_spi.api;

@SPI
public interface Driver {

    public void getColorForUrl(URL url);
}
  • 创建 Driver 的实现类
package com.luban.dubbo_spi.impl
public class Trucker implements Driver {

    private Car car;
    public void setCar(Car car) {
        this.car = car;
    }
    @Override
    public void getColorForUrl(URL url) {
        car.getColorForUrl(url);
    }
}
  • 并在 META-INF.services 目录中创建文件 com.luban.dubbo_spi.api.Driver 一定要与接口同名
tru = com.luban.dubbo_spi.impl.Trucker
  • 创建主程序测试 @SPI
public class CarDemo {

    public static void main(String[] args) {
        ExtensionLoader<Driver> extensionLoader =
                ExtensionLoader.getExtensionLoader(Driver.class);

        Driver driver = extensionLoader.getExtension("tru");

        Map<String, String> map = new HashMap<>();
        map.put("car", "black");
        URL url = new URL("","",1, map);
        driver.getColorForUrl(url);
    }
}
  1. 首先通过 ExtensionLoader.getExtensionLoader() 获取 Driver.class 的加载器,加载器的流程可以参考 『Dubbo SPI源码分析』SPI 机制分析。获取到加载器以后,从加载器中,拿出扩展类
public class CarDemo {

    public static void main(String[] args) {
        ExtensionLoader<Driver> extensionLoader =
                ExtensionLoader.getExtensionLoader(Driver.class);
		// 1. 从中拿出扩展类
        Driver driver = extensionLoader.getExtension("tru");
        ...
    }
}
  1. 由于还没创建实体,所以同时需要执行 createExtension() 方法
public class ExtensionLoader<T> {

    // 缓存实例,每个接口的实现类对应一个实例
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
    ...
    public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(name);
        }
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 1. 执行创建实体方法
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
}
  1. 创建时,首先通过 getExtensionClasses() 扫描目录的配置,获取所有配置的类,然后从中取出标识为 “tru” 的类,其中相关流程在 『Dubbo SPI源码分析』SPI 机制分析 分析过,就不再叙述。获取 com.luban.dubbo_spi.impl.Trucker 类后,执行依赖注入
public class ExtensionLoader<T> {

    // 实现类对应的实例
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
    ...
    private T createExtension(String name) {
        // 1. 扫描目录中的配置文件,获取扩展类集合
        Class<?> clazz = getExtensionClasses().get(name); 
        if (clazz == null) {
            throw findException(name);
        }
        try {
            // 生成实例并缓存
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // 2. 对类执行依赖注入
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }
}
  1. com.luban.dubbo_spi.impl.TruckerExtensionLoader 包含 AdaptiveExtensionFactory 的对象工厂,所以这次会尝试对创建后的实体判断是否需要执行依赖注入,主要是判断是否有公有的 set() 方法,然后取出属性名称,既 set[Name]() 中这个 Name 就是要注入的属性名称,case 这里是 “car”,然后入参就是代表对应的类,最后调用 AdaptiveExtensionFactory.getExtension() 获取实体类之后注入方法中
  • 重点
    • 依赖注入的类,一定要标注 @Adaptive@SPI
    • 被注入的类,要标注 @SPI,同时如果要注入依赖的话,需要实现 set[Name](),其中 Name 是名称,而入参是实现类
public class ExtensionLoader<T> {

	// 用于依赖注入
    private final ExtensionFactory objectFactory;
    ...
    private T injectExtension(T instance) {
        try {
            // com.luban.dubbo_spi.impl.Trucker 的 ExtensionLoader 已经包含 AdaptiveExtensionFactory
            if (objectFactory != null) {
                // 1. 判断是否包含 set() 方法
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        /**
                         * Check {@link DisableInject} to see if we need auto injection for this property
                         */
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        Class<?> pt = method.getParameterTypes()[0];
                        if (ReflectUtils.isPrimitives(pt)) {
                            continue;
                        }
                        try {
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // 如果在spring容器中已经存在了一个对象就会直接去容器中的对象
                            // 如果没有,则会使用SpiExtensionFactory来获取对象,这个时候,property没有使用到,会直接根据pt来生成Adaptive类并且构造实例,也就是dubbo的代理对象
                            //  2. 执行 AdaptiveExtensionFactory 获取对象
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
								// 执行 set 方法,注入                                
                                method.invoke(instance, object); 
                            }
                        } catch (Exception e) {
                            logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

}
  1. AdaptiveExtensionFactory 在执行的 getExtension() 方法时,会遍历所有在配置文件中,实现了 ExtensionFactory.class 接口的类,从而获取其中的实体,这里只有 1 个,就是 SpiExtensionFactory
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
    // 存储实现了 ExtensionFactory.class 接口的类
    private final List<ExtensionFactory> factories;
    ...
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
        	// 1. 执行对象工厂,获取扩展实体,其中 type=com.luban.dubbo_spi.impl.Trucker,name=car
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}
  1. 执行 SpiExtensionFactorygetExtension() 方法时,会先判断入参的 type(即 com.luban.dubbo_spi.api.Car)是否为接口,同时标注了 @SPI 注解,如果有,则先获取 com.luban.dubbo_spi.api.CarExtensionLoader(过程参考 『Dubbo SPI源码分析』SPI 机制分析 ),通过 getSupportedExtensions() 扫描所有配置文件中实现了 com.luban.dubbo_spi.api.Car 接口的类,然后获取 Adaptive 扩展类
public class SpiExtensionFactory implements ExtensionFactory {
   
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        // 1. 判断是否为接口、同时标注了 @SPI 注解
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            // 2. 获取加载器
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                // 3. 获取 Adaptive 扩展类 
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}
  1. 执行 ExtensionLoader 获取 Adaptive 扩展类时,先判断缓存 cachedAdaptiveInstance 是否有一个创建过的实体,如果有就返回,没有则创建
  • 重点
    • 一个接口只能有一个 Adaptive 实现
public class ExtensionLoader<T> {

 	private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
    private volatile Throwable createAdaptiveInstanceError;
    ...
    public T getAdaptiveExtension() {
        // 一个接口只能有一个Adaptive实现
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            // 1. 创建 Adaptive 扩展类
                            instance = createAdaptiveExtension(); 
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }
}
  1. 执行 createAdaptiveExtension() 方法时,先获取 Adaptive 扩展类
public class ExtensionLoader<T> {
    ..
    private T createAdaptiveExtension() {
        try {
            // 1. 获取 Adaptive 扩展类
            return injectExtension((T) getAdaptiveExtensionClass().newInstance()); // 第一次先创建 org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }
}
  1. 首先执行一次 getExtensionClasses() 扫描目录获取 com.luban.dubbo_spi.api.Car 的实现类(上面获取过一次,其实已经缓存了),返回缓存的 @Adaptive 扩展类
public class ExtensionLoader<T> {
    ...
    // Adaptive 类存在的意义就是在调用接口方法时,根据url参数去加载对应的实现类,这样不用提前加载
    // 对于一个接口你可以手动实现一个 Adaptive 类,比如 AdaptiveExtensionFactory,
    // 也可以有 Dubbo 默认给我们实现,在实现的时候会根据接口中的方法是否含有 Adaptive 注解,有注解的方法才会代理,没有注解的方法则不会代理,并且使用代理类调用的时候会抛异常
    private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        // 如果一个接口实现了一个 Adaptive 实现就直接用,如果没有就默认实现一个
        if (cachedAdaptiveClass != null) {
            // 1. 直接返回扩展类
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
}
  1. 获取到 com.luban.dubbo_spi.api.Car 的扩展类并创建实体,然后对该类也执行依赖注入,但是我们的 case 也没在对 com.luban.dubbo_spi.impl.AdaptiveCar 再进行注入,所以就直接返回
public class ExtensionLoader<T> {
    ...
    @SuppressWarnings("unchecked")
    private T createAdaptiveExtension() {
        try {
            // adaptive 类中有属性需要注入
            // 1. 没有再对 com.luban.dubbo_spi.impl.AdaptiveCar 执行依赖注入,直接返回
            return injectExtension((T) getAdaptiveExtensionClass().newInstance()); // 第一次先创建 org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }
}
  1. 当完成对 com.luban.dubbo_spi.impl.Trucker 注入以后,就可以使用 com.luban.dubbo_spi.impl.AdaptiveCar 这个属性
public class CarDemo {

    public static void main(String[] args) {
        ExtensionLoader<Driver> extensionLoader =
                ExtensionLoader.getExtensionLoader(Driver.class);


        Driver driver = extensionLoader.getExtension("tru");
		
		// 1. 访问对应的代理类
        Map<String, String> map = new HashMap<>();
        map.put("car", "black");
        URL url = new URL("","",1, map);
        driver.getColorForUrl(url);
    }
}
  1. 总结
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/783133.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构(王道)——数据结构之 由遍历序列构造二叉树

结论&#xff1a;给出遍历序列当中的一种&#xff0c;不能唯一确定一颗二叉树。

[回馈]ASP.NET Core MVC开发实战之商城系统(一)

经过一段时间的准备&#xff0c;新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始&#xff0c;今天着重讲解布局设计&#xff0c;环境搭建&#xff0c;系统配置&#xff0c;及首页商品类型&#xff0c;banner条&#xff0c;友情链接等功能的开发。 首页布局设计 首页是…

Android使用Shape画格子图和圆形

觉得画来玩玩&#xff0c;比较有趣&#xff0c;记录一下。 1格子。 <?xml version"1.0" encoding"utf-8"?> <layer-list xmlns:android"http://schemas.android.com/apk/res/android"><item ><shape><solid andro…

性能测试-Jmeter之Linux下压力测试

我们在做测试的时候&#xff0c;有时候要运行很久&#xff0c;公司用的测试服务器一般都是linux&#xff0c;就可以运行在linux下面&#xff0c;linux下面不能像windows一样有图形化界面&#xff0c;那怎么运行脚本呢&#xff0c;就先在windows上把脚本做好&#xff0c;然后在l…

解决ORACLE PLSQL查询速度慢问题

在表内已建有索引情况下&#xff0c;查询速度有时快&#xff0c;有时慢的问题。 数据库&#xff1a;Oracle&#xff0c; 工具&#xff1a;PlsqlDev 不走索引的原因通常有以下几种&#xff1a; 1.索引失效或丢失&#xff1a;当数据库中的索引被减少、删除或者失效时&#xff0…

浅析JAVA虚拟机结构与机制

本文旨在给所有希望了解 可以看出&#xff0c;JVM主要由类加载器子系统、运行时数据区&#xff08;内存空间&#xff09;、执行引擎以及与本地方法接口等组成。其中运行时数据区又由方法区、堆、Java栈、PC寄存器、本地方法栈组成。 从上图中还可以看出&#xff0c;在内存空间…

Flask 创建文件目录,删除文件目录

项目结构 app.py from flask import Flask, render_template, request, redirect, url_for import osapp Flask(__name__) BASE_DIR os.path.abspath(os.path.dirname(__file__)) FILE_DIR os.path.join(BASE_DIR, testfile)app.route(/, methods[GET, POST]) def index():…

心海舟楫、三一重工面试(部分)

心海舟楫 一道算法题&#xff1a; 我开始给出的是暴力解法&#xff0c;时间复杂度O(n^2)。 在面试官的提示下&#xff0c;实现了时间复杂度为O(n)的解法。 三一重工 没啥特别的

【VTK】VTK 让小球动起来,在 Windows 上使用 Visual Studio 配合 Qt 构建 VTK

知识不是单独的&#xff0c;一定是成体系的。更多我的个人总结和相关经验可查阅这个专栏&#xff1a;Visual Studio。 文章目录 版本环境A.uiA.hA.cppRef. 本文主要目的是在 Qt 界面中&#xff0c;显示出来使用 VTK 构建的小球&#xff0c;并让小球能够动起来。同时为了方便对比…

第2章 SparkSQL 核心编程

第2章 SparkSQL 核心编程 2.1 新的起点2.2 DataFrame2.2.1 创建 DataFrame2.2.2 SQL 语法2.2.3 DSL 语法2.2.4 RDD 转换为 DataFrame2.2.5 DataFrame 转换为 RDD 2.3 DataSet2.3.1 创建 DataSet2.3.2 RDD 转换为 DataSet2.3.3 DataSet 转换为 RDD 2.4 DataFrame 和 DataSet 转…

学习记录681@Gitlab升级实战

前言 我的Linux目前是centos8&#xff0c;目前使用的gitlab是从https://mirrors.tuna.tsinghua.edu.cn/ 下载下来的gitlab-ce-12.10.1-ce.0.el8.x86_64.rpm&#xff0c;然后安装的。 这里需要注意如果是centos8需要下载el8的gitlab&#xff0c;如果是centos7需要下载el7的git…

golang - 下载大文件,实时返回前端下载进度,实现下载进度条

示例&#xff1a; package mainimport ("fmt""io""net/http""os""path"//"github.com/kataras/iris""github.com/kataras/iris/v12""time" )func doSomething() {time.Sleep(time.Second * …

大数据学习04-Hbase分布式集群部署

系统环境&#xff1a;centos7 软件版本&#xff1a;jdk1.8、zookeeper3.4.8、hadoop2.8.5 一、下载 HBASE官网 cd /home/toolswget https://archive.apache.org/dist/hbase/2.2.4/hbase-2.2.4-bin.tar.gz二、解压 tar -zxvf hbase-2.2.4-bin.tar.gz -C /home/local/移动目…

【弹力设计篇】聊聊降级设计

我们知道在分布式系统中&#xff0c;故障是不可避免的&#xff0c;所以我们需要设计一个高可用的系统&#xff0c;对于接口层面除了幂等&重试机制&#xff0c;还需要保证接口高可用&#xff0c;因此 限流&排队&降级&熔断也需要考虑。本篇主要介绍下接口故障下降…

Qt 之 自定义json配置文件类,QJsonDocument应用

目录 一、前言 二、头文件代码 三、源文件代码 四、使用示例 五、使用效果 一、前言 Qt的配置类QSettings主要是键值结构的配置&#xff0c;若需要的配置项为树形结构&#xff0c;例如配置学校\学院\班级\学生这样&#xff0c;使用键值结构已经不满足我们的需求了&#xf…

【计算机视觉 | 图像分割】arxiv 计算机视觉关于图像分割的学术速递(7 月 21 日论文合集)

文章目录 一、分割|语义相关(14篇)1.1 CNOS: A Strong Baseline for CAD-based Novel Object Segmentation1.2 Spinal nerve segmentation method and dataset construction in endoscopic surgical scenarios1.3 WeakPolyp: You Only Look Bounding Box for Polyp Segmentatio…

【unity】模型裁剪shader(建筑生长动画)

【unity】模型裁剪shader&#xff08;建筑生长动画&#xff09; 思路 使用的核心方法是clip,当传入正值时渲染&#xff0c;传入负值时不渲染。定义一个裁剪向量&#xff0c;使用裁剪向量和模型点点乘&#xff0c;如果模型点和裁剪向量是同一个方向&#xff0c;点乘为正&#…

代码随想录算法训练营第58天|739 496

739 用stack来写 stack里面发index 不要放数值 重点在于 1.填写result数组不需要按顺序填写 根据index就可以 2.遍历的值比top小的话就放入stack 这样stack里面是一个递减数组 遍历的值只需和top比 如果比他大就pop 一直到把stack里面比新加入的值小的都pop完为止 这样stack里…

vue项目的vue.config.js在打包过程中,并不会处理api请求。

主要处理打包选项和静态资源文件 请求是axios处理的

nonebot2聊天机器人插件12:stable_diffusion_webui_api

nonebot2聊天机器人插件12&#xff1a;stable_diffusion_webui_api 1. 插件用途2. 代码实现3. 实际效果 该插件涉及知识点&#xff1a;定时器&#xff0c;调用bot的api发送消息 插件合集&#xff1a;nonebot2聊天机器人插件 该系列为用于QQ群聊天机器人的nonebot2相关插件&…