专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Dubbo源码之spi

版本 2.7.4.1

spi是什么

来自百度:SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

java spi

简单的代码示例

public interface People {
    String getName();
}

public class Student implements People {

    @Override
    public String getName() {
        System.out.println("student");
        return "Student";
    }
}

在META-INF/services/ 路径下面配置实现类

  • 70_1.png测试类:

    public static void main(String[] args) {
        ServiceLoader<People> serviceLoader = ServiceLoader.load(People.class);
        Iterator<People> iterator = serviceLoader.iterator();
        // resources/META-INF/services/ 源码已经写死了 META-INF/services/ 这个路径
        //所以只能在这个路径下面编写接口实现类
        while (iterator.hasNext()){
            //获取接口实现类,做相应的操作
            People next = iterator.next();
            next.getName();
        }
    }

以上就是java spi的简单使用,可以实现对某个接口的动态扩展。这也是为什么要学习dubbo的spi,我们也可以对dubbo进行动态扩展。

dubbo的spi

dubbo的spi并不是使用原生的java spi机制,dubbo的spi机制更加丰富

入口

ServiceConfig里面的一句代码开始

//通过 ExtensionLoader 获取 Protocol 自适应扩展点
    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

一步一步分析,先看 ExtensionLoader.getExtensionLoader(Protocol.class)

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    //从map中拿 ExtensionLoader ,第一次没拿到
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
        //没拿到就new 一个 ExtensionLoader 这个动作又做了好多事情,下面说
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

new ExtensionLoader<T>(type) 做了什么

    private ExtensionLoader(Class<?> type) {
        this.type = type;
        //三目运算 这时候的 type=Protocol.class 
        //所以走的 ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
        //发现又回到 ExtensionLoader.getExtensionLoader(ExtensionFactory.class) 这个代码里面
        //又掉绕这里, ExtensionLoader.getExtensionLoader(ExtensionFactory.class)
        //这句代码走到这里的时候type=ExtensionFactory 所以直接返回null
        //当type=Protocol.class 的时候,objectFactory 是 ExtensionFactory 一个扩展点
        //这个后面说
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }

所以ExtensionLoader.getExtensionLoader(Protocol.class) 这个代码做的事情就是new一个 ExtensionLoader 对象,然后把type=Protocol.class放到 ExtensionLoader的type属性,同时通过spi机制获取到ExtensionFactory的扩展点放到objectFactory 这个属性。这两个属性都是为后面的 getAdaptiveExtension 方法服务的

``getAdaptiveExtension“ 做了什么,顾名思义,这个方法就是获取某个接口的自适应扩展点 dubbo的自适应扩展点是什么?下面源码有答案

public T getAdaptiveExtension() {
//cachedAdaptiveInstance 是缓存自适应扩展点
        Object instance = cachedAdaptiveInstance.get();
        //一开始缓存肯定为空
        if (instance == null) {
        //dubbo大量用到双重检查
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                    //缓存拿不到,看是真正创建自适应扩展点
                    //重点代码,下面解析
                        instance = createAdaptiveExtension();
                    //放到缓存里面
                        cachedAdaptiveInstance.set(instance);
                    } 
                }
            }
        }

        return (T) instance;
    }

createAdaptiveExtension 获取自适应扩展点

    private T createAdaptiveExtension() {
        try {
        //一行代码做了很多事情
        //1.先看 getAdaptiveExtensionClass 方法
        //2.newInstance 这个不用看,就是通过反射new一个对象
        //3.最后看 injectExtension 这个方法
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        }
    }

getAdaptiveExtensionClass 获取自适应扩展点的class


private Class<?> getAdaptiveExtensionClass() { //重点方法,去加载type这个接口下面所有的扩展点,放到缓存中 getExtensionClasses(); //如果上面的逻辑已经加载了自适应扩展点,就直接返回自适应扩展点的class if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } //拼接代码生成自适应扩展点,然后默认使用 javassist 生成class return cachedAdaptiveClass = createAdaptiveExtensionClass(); }

getExtensionClasses

    private Map<String, Class<?>> getExtensionClasses() {
    //cachedClasses 缓存type下面的实现类
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                //开始去load实现类
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

loadExtensionClasses 加载实现类

    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();
    //存放实现类的容器
        Map<String, Class<?>> extensionClasses = new HashMap<>();
    // 有两个方法,另外一个应该是为了兼容alibaba相关的类,往下看loadDirectory方法   
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        //.....省略去加载 "META-INF/services/" 和  "META-INF/dubbo/" 下面的实现类
        //因为都是调用 loadDirectory 方法加载的
        return extensionClasses;
    }

loadDirectory 加载文件

 private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls;
            //获取类加载器
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
            //加载资源文件
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    //load文件下来之后开始解析
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } 
    }

loadResource 解析实现类文件


private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { //...省略大量逻辑,大概就是从文件加载出实现类 //这个文件跟java的spi文件不一样的是用键值对来表示 //加载文件,下面详解 loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); /........ }

loadClass 加载类


private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { //判断是否加 @Adaptive 如果是就放到 cachedAdaptiveClass 这个属性里 //所以在实现类中 加@Adaptive 注解就是自适应扩展点,自适应扩展点只能有一个 if (clazz.isAnnotationPresent(Adaptive.class)) { cacheAdaptiveClass(clazz); //判断是否包装类,通过 clazz.getConstructor(type); 这行代码判断 //如果不抛出异常,表示是包装类,否则不是 //包装类会缓存到 cachedWrapperClasses,后面会用到 } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } else { //...省略名称获取 if (ArrayUtils.isNotEmpty(names)) { //将 有@Activate 注解的类缓存到 cachedActivates 中 cacheActivateClass(clazz, names[0]); for (String n : names) { //cachedNames 缓存类跟名称的键值 cacheName(clazz, n); //extensionClasses 缓存名称跟类的键值 saveInExtensionClass(extensionClasses, clazz, n); } } } }

loadClass 这个方法可以看出来,spi的扩展点有很多形式,比如加@Adaptive @Active 包装类,普通类 但是 cachedClasses 这个只放普通和@Active@Adaptive 放到 cachedAdaptiveClass 包装类放到 cachedWrapperClasses@Active还会放到 cachedActivates

现在回到 getAdaptiveExtensionClass 这个方法

    private Class<?> getAdaptiveExtensionClass() {
    //上面分析到这里,从配置文件中加载所有的实现类出来,放到本地缓存
        getExtensionClasses();
    //这个是缓存@Adaptive     
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
    //如果所有实现类没有一个注有 @Adaptive 就走下面的逻辑,往下走    
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

    private Class<?> createAdaptiveExtensionClass() {
    //这个方法很长很复杂,不往下走了,总的就是判断type是否注有@Adaptive,没有就会抛异常
    //有就会为这个方法生成具体的实现
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        ClassLoader classLoader = findClassLoader();
    //生成code之后,默认用javassist 生成class    
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

以上就差不多完成从配置文件加载到本地缓存的过程,接下来看 injectExtension 方法做了什么 injectExtension 实现了dubbo的ioc的注入


private T injectExtension(T instance) { //这个如果还记得的话,就是上文中实例ExtensionLoader的时候,new进去的 //所以这时候不为空 if (objectFactory == null) { return instance; } try { //遍历所有的方法 for (Method method : instance.getClass().getMethods()) { //如果不是set方法就不做操作 if (!isSetter(method)) { continue; } //如果有 @DisableInject 在方法上,说明不需要自动注入 if (method.getAnnotation(DisableInject.class) != null) { continue; } //获取需要注入得calss Class<?> pt = method.getParameterTypes()[0]; if (ReflectUtils.isPrimitives(pt)) { continue; } try { //获取需要注入的名称比如setXX 就是通过xx去容器获取 String property = getSetterProperty(method); //objectFactory 的作用来了,就是从容器中获取依赖,下面讲 Object object = objectFactory.getExtension(pt, property); if (object != null) { //调用反射,注入依赖 method.invoke(instance, object); } } } } return instance; }

完成注入之后就可以返回对象了,接下来看 objectFactory.getExtension 是怎么从容器中获取依赖的 在AdaptiveExtensionFactory中实现。

public <T> T getExtension(Class<T> type, String name) {
//AdaptiveExtensionFactory 实例化的时候会通过spi机制加载
//ExtensionFactory 的普通实现类,目前包括
//SpiExtensionFactory 和 SpringExtensionFactory
//然后遍历 ExtensionFactory 实现类,执行 getExtension 获取依赖
//接下来分别看看这两个实现类的逻辑
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

SpringExtensionFactory 从spring容器中获取依赖

 public <T> T getExtension(Class<T> type, String name) {
//......省略一下判断
//从spring上下文判断bean存不存在,如果存在getBean返回
        for (ApplicationContext context : CONTEXTS) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
//.....
    }

SpiExtensionFactory 通过dubbo的spi机制获取依赖,需要的依赖可能通过spi机制加载

public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
            //获取自适应扩展点
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

到这里dubbo的spi已经理的差不多了

为什么需要了解dubbo的spi

1、方便在使用dubbo的时候对dubbo进行扩展 2.dubbo很多地方都会用到spi,所以想要了解dubbo就先要知道dubbo的spi干了什么

未完待续………………

文章永久链接:https://tech.souyunku.com/26697

未经允许不得转载:搜云库技术团队 » Dubbo源码之spi

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们