Dubbo源码(1)-SPI机制源码解析

dubbo SPI源码详解 1 jdk spi spi全名Service Provider Interface,是一种服务发现机制,即为接口寻找服务实现的机制。 jdk spi 约定提供方只需制定接口规范,提供给外部实现,通过读取jar包META-INF/services/目录以接口名命令的文件来加载该接口的实现,文件里就是该接口的具体实现。 下面是一个简单的例子: 提供一个接口JdkSpiService public interface JdkSpiService { public void hello(); } 提供 阅读详情

本文主要参考自Dubbo官方文档、Dubbo项目源码以及网络文章和相关书籍,并附上自身的一些理解,如有遗漏或错误,还望海涵并指出。谢谢!

------本文基于Dubbo-2.6.1版本

一、什么是SPI

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。

Java中的框架大量使用了SPI机制来实现接口的动态拓展,譬如数据库连接驱动Driver(META-INF/services),以及SpringBoot的Starter提供了自己的SPI机制(META-INF/spring.factories)。

dubbo对Java的SPI进行了增强,提供了对拓展点的IoC以及AOP的支持,并且相对于Java SPI来说,无需加载全部实现类,避免资源的浪费。dubbo SPI用以拓展协议、路由等26个拓展点,这也是整个dubbo体系的核心功能之一。

具体的SPI拓展用法参考dubbo文档:

http://dubbo.apache.org/zh-cn/docs/dev/SPI.html

二、基本使用

2.1、核心注解和URL类

首先介绍下三个SPI核心注解:@SPI、@Adaptive、@Activate,整个dubbo项目中只要有使用SPI的地方就需要用到这三个注解来实现底层的SPI机制,它们的作用是:

  • @SPI:
    指定当前接口为可拓展接口,@SPI(“impl1”)表示该接口点默认实现类为"impl1"。

  • @Adaptive:
    表明该可拓展接口的方法是可以被动态替换的(具有多个不同的实现类可以执行这个方法),@Adaptive({“key1”, “key2”})可以动态地通知指定key1或key2的实现类,来动态地执行制定实现类的该方法。
    Adaptive 可注解在类或方法上。当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。

  • @Activate:
    扩展点自动激活加载的注解,就是用条件来控制该扩展点实现是否被自动激活加载,在扩展实现类上面使用

注意:

1.所有扩展点都通过传递URL类携带配置信息,所以适配器中的方法必须携带URL参数,才能根据URL中的配置来选择对应的扩展实现。

2.@Adaptive注解中有一些key值,比如适配方法的注解中有两个key,分别为“key1”和“key2”,URL会首先去取key1对应的value来作为@SPI注解中写到的key值(也就是要执行的具体实现类方法),如果为空,则去取key2对应的value,如果还是为空,则会根据SPI默认的key去调用扩展的实现类,如果@SPI没有设定默认值,则会抛出IllegalStateException异常。

2.2、单元测试

来看看dubbo-common模块下的关于SPI的单元测试,就可以知道SPI是如何使用的了:

  1. 定义拓展接口
  • SimpleExt
package com.alibaba.dubbo.common.extensionloader.ext1;

/**
 * Simple extension, has no wrapper
 */
 // 该接口的默认实现为impl1
@SPI("impl1")
public interface SimpleExt {
    // @Adaptive example, do not specify a explicit key.
    @Adaptive
    String echo(URL url, String s);

    @Adaptive({"key1", "key2"})
    String yell(URL url, String s);

    // no @Adaptive
    String bang(URL url, int i);
}
  1. 定义实现类
  • SimpleExtImpl1
package com.alibaba.dubbo.common.extensionloader.ext1.impl;

public class SimpleExtImpl1 implements SimpleExt {
    public String echo(URL url, String s) {
        return "Ext1Impl1-echo";
    }

    public String yell(URL url, String s) {
        return "Ext1Impl1-yell";
    }

    public String bang(URL url, int i) {
        return "bang1";
    }
}
  • SimpleExtImpl2
package com.alibaba.dubbo.common.extensionloader.ext1.impl;

public class SimpleExtImpl2 implements SimpleExt {
    public String echo(URL url, String s) {
        return "Ext1Impl2-echo";
    }

    public String yell(URL url, String s) {
        return "Ext1Impl2-yell";
    }

    public String bang(URL url, int i) {
        return "bang2";
    }

}
  • SimpleExtImpl3
package com.alibaba.dubbo.common.extensionloader.ext1.impl;

public class SimpleExtImpl3 implements SimpleExt {
    public String echo(URL url, String s) {
        return "Ext1Impl3-echo";
    }

    public String yell(URL url, String s) {
        return "Ext1Impl3-yell";
    }

    public String bang(URL url, int i) {
        return "bang3";
    }
}
  1. 定义SPI配置文件

根据SPI的规则,为了实现对SimpleExt的动态拓展,需要在resource/META-INF/dubbo目录下以SimpleExt接口全限定名作为文本文件,并且在该文件中以xxx=全限定名Xxx实现类标示该接口的所有可能实现类:

  • META-INF/dubbo/internal/com.alibaba.dubbo.common.extensionloader.ext1.SimpleExt
# Comment 1
impl1=com.alibaba.dubbo.common.extensionloader.ext1.impl.SimpleExtImpl1#Hello World
impl2=com.alibaba.dubbo.common.extensionloader.ext1.impl.SimpleExtImpl2  # Comment 2
impl3=com.alibaba.dubbo.common.extensionloader.ext1.impl.SimpleExtImpl3 # with head space

注意,这里的dubbo/internal表明这是dubbo自身内置的实现

  1. 单元测试
  • ExtensionLoader_Adaptive_Test
@Test
    public void test_getAdaptiveExtension_defaultAdaptiveKey() throws Exception {
        {
            // URL中不指定key,使用默认的impl1作为实现类
            SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();

            Map<String, String> map = new HashMap<String, String>();
            URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);

            String echo = ext.echo(url, "haha");
            assertEquals("Ext1Impl1-echo", echo);
        }

        {

            // URL中通过SimleExt接口的拆分名simple.ext指定实现类为impl2
            SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();

            Map<String, String> map = new HashMap<String, String>();
            map.put("simple.ext", "impl2");
            URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);

            String echo = ext.echo(url, "haha");
            assertEquals("Ext1Impl2-echo", echo);
        }
    }

    @Test
    public void test_getAdaptiveExtension_customizeAdaptiveKey() throws Exception {
        // URL通过key2和key1来分别指定实现类为impl2和impl3
        SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();

        Map<String, String> map = new HashMap<String, String>();
        map.put("key2", "impl2");
        URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);

        String echo = ext.yell(url, "haha");
        assertEquals("Ext1Impl2-yell", echo);

        url = url.addParameter("key1", "impl3"); // note: URL is value's type
        echo = ext.yell(url, "haha");
        assertEquals("Ext1Impl3-yell", echo);
    }

三、SPI机制源码解析

  • Dubbo SPI机制源码的官方文档解读:http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

整个SPI机制的实现代码都位于dubbo-common模块中的com.alibaba.dubbo.common.extension包下,来看看extension包所含的内容:
在这里插入图片描述

所含的Java代码量(不含注释)为900行左右:
在这里插入图片描述

其中,ExtensionLoader是Dubbo SPI机制的核心类,相当于Java SPI机制中的ServiceLoader,ServiceLoader通过加载的是META-INF/services下的内容,而ExtensionLoader加载的是META-INF/dubbo下的内容并且做出了更复杂的定制。

3.1、ExtensionLoader

ExtensionLoader是整个Dubbo SPI的核心类,用来实现对META-INF/dubbo下的实现类做SPI判断、动态加载、自动装配、封装成Wrapper给用户所使用。

3.1.1、属性
// logger
    private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
    // JDK SPI文件夹
    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    // Dubbo 用户SPI文件夹
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    // Dubbo 内置SPI文件夹
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
    // 正则表达式,以,分割成字符串数组
    private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*");
    // 拓展加载器集合,key为拓展接口,value为对应的加载器
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
    // 拓展加载器实例集合,key为拓展实现类,value为拓展接口
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();

    // ==============================

    // 被拓展的接口对象
    private final Class<?> type;
    // 当前拓展对象工厂,内含getExtension方法
    private final ExtensionFactory objectFactory;
    // 缓存的拓展接口与拓展名
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
    // 缓存的拓展实现类集合(Holder是拓展对象的包装实现类,在其中包装了实现类)
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
    // 扩展名与加有@Activate的自动激活类的映射
    private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
    // 缓存的扩展对象集合,key为扩展名,value为扩展对象
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
    // 缓存的@Adaptive对象
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
    // 缓存的自适应扩展对象的类
    private volatile Class<?> cachedAdaptiveClass = null;
    // 缓存的默认扩展名,@SPI中设置的值
    private String cachedDefaultName;
    // 创建@Adaptive对象失败异常
    private volatile Throwable createAdaptiveInstanceError;
    // 拓展Wrapper实现类集合
    private Set<Class<?>> cachedWrapperClasses;
    // 拓展名与加载对应拓展类发生的异常的映射
    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();
3.1.2、拓展类资源加载

首先, 为了获取到拓展类,ExtensionLoader做的的一件事就是对拓展类资源进行加载,这个步骤依次调用了getExtensionClasses方法、loadExtensionClasses和loadFile方法:

1)getExtensionClasses
private Map<String, Class<?>> getExtensionClasses() {
        // 从缓存中获取拓展实现类
        Map<String, Class<?>> classes = cachedClasses.get();
        // 双重检查加锁,若classes不存在,则使用loadExtensionClasses方法
        // 将实现类加入cachedClasses中,再进行获取
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
2)loadExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
        // 获取到该拓展接口的@SPI注解
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        // 将@SPI注解的默认实现类名称放入cachedDefaultName中
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if (value != null && (value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                if (names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }
        // 创建extensionClasses,以拓展类名称为key,以具体拓展类为value放入map中
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        // 从3个SPI文件夹中加载拓展类
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }
3)loadFile
/*
    *   根据SPI DIR加载拓展类到Map中
     */
    private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            // 获取ExtensionLoader的ClassLoader
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            // h获取到SPI DIR下的urls,对其解析
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL url = urls.nextElement();
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                        try {
                            String line = null;
                            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) {
                                            // 用反射+全限定名+ClassLoader获取到实现类的Class对象
                                            Class<?> clazz = Class.forName(line, true, classLoader);
                                            if (!type.isAssignableFrom(clazz)) {
                                                // 如果clazz并不是type的实现类,抛出异常
                                                throw new IllegalStateException("Error when load extension class(interface: " +
                                                        type + ", class line: " + clazz.getName() + "), class "
                                                        + clazz.getName() + "is not subtype of interface.");
                                            }
                                            // 如果class已经声明了@Adaptive注解,那么设置默认的cachedAdaptiveClass为class
                                            if (clazz.isAnnotationPresent(Adaptive.class)) {
                                                if (cachedAdaptiveClass == null) {
                                                    cachedAdaptiveClass = clazz;
                                                } else if (!cachedAdaptiveClass.equals(clazz)) {
                                                    // 如果多个实现类都声明了@Adaptive注解,抛出异常
                                                    throw new IllegalStateException("More than 1 adaptive class found: "
                                                            + cachedAdaptiveClass.getClass().getName()
                                                            + ", " + clazz.getClass().getName());
                                                }
                                            } else {
                                                // 缓存拓展 Wrapper 实现类到 cachedWrapperClasses
                                                try {
                                                    clazz.getConstructor(type);
                                                    Set<Class<?>> wrappers = cachedWrapperClasses;
                                                    if (wrappers == null) {
                                                        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                        wrappers = cachedWrapperClasses;
                                                    }
                                                    wrappers.add(clazz);
                                                } catch (NoSuchMethodException e) {
                                                    clazz.getConstructor();
                                                    if (name == null || name.length() == 0) {
                                                        name = findAnnotationName(clazz);
                                                        if (name == null || name.length() == 0) {
                                                            if (clazz.getSimpleName().length() > type.getSimpleName().length()
                                                                    && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                                                                name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                                                            } else {
                                                                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
                                                            }
                                                        }
                                                    }
                                                    String[] names = NAME_SEPARATOR.split(name);
                                                    // 将拓展实现类加载到extensionClasses这个Map中
                                                    if (names != null && names.length > 0) {
                                                        Activate activate = clazz.getAnnotation(Activate.class);
                                                        if (activate != null) {
                                                            cachedActivates.put(names[0], activate);
                                                        }
                                                        for (String n : names) {
                                                            if (!cachedNames.containsKey(clazz)) {
                                                                cachedNames.put(clazz, n);
                                                            }
                                                            Class<?> c = extensionClasses.get(n);
                                                            if (c == null) {
                                                                extensionClasses.put(n, clazz);
                                                            } else if (c != clazz) {
                                                                throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    } catch (Throwable t) {
                                         // 发生异常,记录到异常集合
                                        IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
                                        exceptions.put(line, e);
                                    }
                                }
                            } // end of while read lines
                        } finally {
                            reader.close();
                        }
                    } catch (Throwable t) {
                        logger.error("Exception when load extension class(interface: " +
                                type + ", class file: " + url + ") in " + url, t);
                    }
                } // end of while urls
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
3.1.3、获取指定Extension

当完成了拓展点的实现资源加载之后,需要获取到特定的拓展类(Extension)以调用相应的方法,第一种获取Extension的方法是通过getExtension方法并指定拓展类名称(在SPI文件中配置的那个xxx=XxxProtocol中的xxx就是名称):

ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name)
2)createExtension

创建指定拓展类对象并缓存。

private T createExtension(String name) {
        // 首先获取到第一步资源加载中已经加载好的实现类class
        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, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            // Dubbo自实现的IoC,通过setter方法注入实例所需的对象或属性
            injectExtension(instance);
            // 构建Wrapper对象
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                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);
        }
    }
3)injectExtension

Dubbo 自实现的IoC(通过过setter方法注入)

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                // 获取到实例的Method对象,解析出setter方法
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            // 获取setter方法中的参数
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // 通过参数获取到所需注入的对象
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                // 执行setter方法注入
                                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;
    }
3.1.4、获取自适应Extension

要获取到Extension实现类,除第一种直接指定名称获取Extension方法外,还可以用更简单的方式获取到Extension,也就是通过getAdaptiveExtension方法获取到自适应的Extension,由Dubbo SPI机制来决定获取到哪个Extension。

ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()
1)getAdaptiveExtension

该方法获取到自适应的拓展实现类对象。

public T getAdaptiveExtension() {
        // 获取缓存的Adaptive实例对象
        Object instance = cachedAdaptiveInstance.get();
        // 若instance不存在
        if (instance == null) {
            // 双重检查加锁,通过createAdaptiveExtension方法创建instance
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            // 创建AdaptiveExtension
                            instance = createAdaptiveExtension();
                            // 将该instance存储到cachedAdaptiveInstance中
                            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);
            }
        }
        // 返回该instance
        return (T) instance;
    }
2)createAdaptiveExtension

该方法,创建自适应拓展实现类对象。

private T createAdaptiveExtension() {
        try {
            // 还是通过injectExtension做IoC注入
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }
3)getAdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
4)createAdaptiveExtensionClass

创建自适应拓展类的代码并进行编译返回,生成Xxx$Adaptive自适应类。

private Class<?> createAdaptiveExtensionClass() { 
        // 生成自适应类的代码字符串
        String code = createAdaptiveExtensionClassCode();
        // 获取ClassLoader
        ClassLoader classLoader = findClassLoader();
        // 对代码进行编译,生成相应的Class返回
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }
3.2、ExtensionFactory

整个SPI体系最重要的ExtensionLoader的主要源码看完了,现在来看看一些Factory接口和实现类。

该接口自身也是一个可拓展的接口,在ExtensionLoader中injectExtension方法中被用来获取到setter方法注入的对象:

    try {
            // 获取setter方法中的参数
            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
            // 通过参数获取到所需注入的对象
            Object object = objectFactory.getExtension(pt, property);
            if (object != null) {
                // 执行setter方法注入
                method.invoke(instance, object);
            }
        } catch (Exception e) {
            logger.error("fail to inject via method " + method.getName()
                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
        }

代码:

@SPI
public interface ExtensionFactory {

    /**
     * 获取拓展实例
     *
     * @param type object 拓展接口.
     * @param name object 拓展名.
     * @return object 拓展实例.
     */
    <T> T getExtension(Class<T> type, String name);

}

ExtensionFactory有两个实现类,其实新版本中存在三个实现类,分别是:
AdaptiveExtensionFactory、SpiExtensionFactory和SpringExtensionFactory,其中AdaptiveExtensionFactory是自适应的实现类,使用的是ExtensionLoader来注入对象;而SpiExtensionFactory也是使用ExtensionLoader来注入对象,但是它会直接获取到当前type的自适应对象进行注入;而SpringExtensionFactory则是使用Spring IoC容器中的Bean进行注入。

  • AdaptiveExtensionFactory
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}
  • SpiExtensionFactory
public class SpiExtensionFactory implements ExtensionFactory {

    /**
     * 获得拓展对象
     *
     * @param type object type. 拓展接口
     * @param name object name. 拓展名
     * @param <T> 泛型
     * @return 拓展对象
     */
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) { // 校验是 @SPI
            // 加载拓展接口对应的 ExtensionLoader 对象
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            // 加载拓展对象
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}
  • SpringExtensionFactory
public class SpringExtensionFactory implements ExtensionFactory {

    /**
     * Spring Context 集合
     */
    private static final Set<ApplicationContext> contexts = new ConcurrentHashSet<ApplicationContext>();

    public static void addApplicationContext(ApplicationContext context) {
        contexts.add(context);
    }

    public static void removeApplicationContext(ApplicationContext context) {
        contexts.remove(context);
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {
        for (ApplicationContext context : contexts) {
            if (context.containsBean(name)) {
                // 获得属性
                Object bean = context.getBean(name);
                // 判断类型
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
        return null;
    }

}
Dubbo学习之SPI实战与debug源码 目录绪论环境搭建dubbo-demo-interfacedubbo-demo-xmldubbo-demo-xml-provider源码跟踪getExtensioncreateExtensiongetExtensionClassesloadDirectoryloadResourceinjectExtension总结 绪论 上篇文章《dubbo学习之源码创建属于自己的dubbo-demo》溪源带着大家简单搭建了自己的demo,基础环境已经搭建完成,从这篇文章开始,溪源便开始学习并总结Dubbo的相关机制,此篇文 阅读详情

相关推荐

全国大学生软件测试大赛Web应用测试(二)Selenium功能测试环境配置

全国大学生软件测试大赛Web应用测试(二)Selenium功能测试环境配置指南配置JAVA配置mooctest配置ChromeDriver配置SeleniumSelenium的使用 环境配置指南 配置JAVA 首先进入JDK8官方安装地址 然后找到Windows 64位 jdk8 点击下载(这里下载需要登录Orcale 账户) 下载后JDK的安装根据提示进行,还有安装JDK的时候也会安装JRE,一并安装就可以了。 安装JDK,安装过程中可以自定义安装目录等信息,例如我们选择安装目录为 D:\java\jav

Kennen0_0的博客 9606

Dubbo源码解析-——SPI机制

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。

ZXMSH的博客 3880

SAP入门技术分享四:模块化程序

SAP入门技术分享三:模块化程序

ITxiaoangzai的博客 1153

Dubbo 源码分析 – SPI 机制

SpringExtensionFactory 则是到 Spring 的 IOC 容器中获取所需拓展,该类的实现并不复杂,大家自行分析源码,这里就不多说了。以上代码的写法是典型的双重检查锁,前面所分析的 getExtension 方法中有相似的代码。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。除此之外,该方法没有其他什么逻辑了,就不多说了。整个过程没什么特别复杂的地方,大家按部就班的分析就行了,不懂的地方可以调试一下。

qq_33291299的博客 356

电子数据取证实战:从“平航杯”竞赛看计算机检材分析的关键技术与案例解析

本文通过'平航杯'电子数据取证竞赛案例,深入解析计算机检材分析的关键技术,包括USB设备追踪、浏览器历史分析、AI换脸取证、虚拟货币交易追踪等实战技巧。重点探讨了数字痕迹的发现与关联,以及电子取证工具链的应用,为从业人员提供实用的技术参考和案例分析。

www00的博客 537

EGO_Planner代码学习(三):px4ctrl代码流程

上节阅读了EGO_Planner的轨迹服务器部分的代码,其实质是将规划器生成的轨迹进行离散化,间隔10ms计算当前时刻在轨迹上的位置,并发送到话题上。这两天抽空看了下控制器部分的代码,在l这个软件包里,它的功能是进行px4的模式识别和上层控制,通过线性控制器把发布过来的位置命令解算成飞控的控制量,通过mavros发送给px4。下面就详细看一下它的启动过程。

qq_41069236的博客 6805

Dubbo源码剖析-SPI机制(超详细深度剖析篇)

Dubbo源码剖析-SPI篇

weixin_55721317的博客 1755

华中科技大学 计算机组成原理 educoder Logisim平台 计算机数据表示实验

本文主要是对 计算机数据表示实验的笔记和相关Logisim平台的图,供大家学习交流使用。 需要源文件或者源代码,可以留言评论! 目录实验目的第1关:汉字国标码转区位码实验第2关:汉字机内码获取实验第3关:偶校验编码设计第4关:偶校验解码电路设计第5关:16位海明编码电路设计第6关:16位海明解码电路设计第7关:海明编码流水传输实验第8关:16位CRC并行编解码电路设计第9关:CRC编码流水传输实验实验结果: 实验目的 本实训将帮助同学们巩固对计算机中数据表示方法的理解,要求能设计汉字国标码与区位码之间的转换

weixin_48383580的博客 2万+

【Dubbo学习系列】Dubbo消费端的代理生成详解(@DubboReference和@Reference)

依托于Spring容器的好处在于职责的划分更加清晰,dubbo本身只负责于rpc的通信,而bean的管理交还给spring,而且使ReferenceAnnotationBeanPostProcessor类更加轻,dubbo的关注点更加集中。

qq_43842093的博客 3807

手把手教你学simulink(36.1)--ACAC变换器场景示例:基于 Simulink 的矩阵变换器(Matrix Converter)

矩阵变换器(Matrix Converter, MC)是一种直接将交流电(AC)转换为不同频率和幅值的交流电的电力电子设备。与传统的基于直流环节的变频器不同,矩阵变换器不需要中间的直流环节,而是通过双向开关直接在输入和输出之间进行能量传递。这种结构使得矩阵变换器具有更高的效率、更小的体积和更好的动态响应。在本项目中,我们将使用 MATLAB/Simulink 来建模和仿真一个矩阵变换器,并分析其性能。Simulink 是一个强大的仿真工具,特别适合用于电力电子系统的建模和仿真。

MHD0815的博客 1066

【Dify】36氪新闻热榜智能自动化采集与AI处理

本文介绍如何利用Dify等自动化工具实现36氪新闻热榜的智能采集与处理。通过HTTP请求节点获取数据,JSON解析节点结构化处理,AI处理节点生成智能摘要,最终格式化输出结果。工作流程包含数据获取、解析、AI摘要、文本整理和内容输出五个环节,支持批量化、定制化的新闻处理需求。该方案适用于行业监测、内容创作、舆情管理等场景,显著提升信息获取效率,实现"所需即得"的自动化新闻服务。系统采用OpenAI GPT和Dify API作为核心模型,无需复杂人工干预即可完成全流程处理。

Mr数据杨 602

03.Dubbo源码系列V1-Dubbo第三节-可扩展机制SPI源码解析【万字文持续更新中】

文章目录第三节: Dubbo的可扩展机制SPI源码解析SPI的概念Java的SPI机制项目目录MainTestDataSaveServiceMySQLSaveServiceRedisSaveServiceSPI文件示例Java的SPI机制的作用Dubbo为什么要实现自己的SPI机制?DemogetExtensionLoader源码ExtensionLoader源码getExtension()createExtension()getExtensionClasses()loadDirectory()loadRe

youthlql的博客 1172

【proteus】模拟串口和物理串口通讯

proteus提供了两种串口,一种是模拟的串口,还有一张是用软件仿真到硬件的物理串口。他们的区别就是:模拟串口只能在proteus内部进行通讯,但是物理可以通过软件映射到硬件上和单机或者串口软件通讯。 物理串口在元器件搜索COMPIM即可找到。 模拟串口在这里: PROTEUS仿真图: 物理串口COMPIM的设置: 模拟串口的设置: VSPD是一款虚拟串口软件,VSPD下载链接: 链接:https://pan.baidu.com/s/1AZygJCeWZfQTnxdCb_3TPg 提取码:12pm

阿蛋的博客 8934

Dubbo - Dubbo的SPI机制

SPI是什么? SPI全称service provider interface,比如你有一个接口,现在这个接口有三个实现类,那么在系统运行的时候对这个接口到底选择哪个实现类呢?这就需要SPI了,需要根据指定的配置或者默认的配置,去找到对应的实现类加载进来,然后用这个实现类的实例对象。 举个例子: 你有一个接口A,A1/A2/A3分别是接口A的不同实现,你通过配置接口A = 实现类A2,那么系统在...

yang的博客 3万+

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

【Dubbo源码】SPI机制源码解析

文章目录一、什么是SPI机制二、SPI机制源码分析ExtensionLoader静态类和静态方法new ExtensionLoader(type) 实例化扩展加载器如何创建自适应扩展实例如何获取自适应扩展的`Class`?如何自动创建`自适应扩展类`实例化之后,如何依赖注入?依赖注入的属性从哪里来加载当前`Type`中所有的扩展类加载文件中的具体实现类@Activate 注解的作用总结 先去看Du...

石臻说AI(szzdzhp001) 【原名 石臻臻的杂货铺】 4846

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Dubbo源码解读-dubbo的SPI机制

Dubbo的SPI机制,读这篇文章,你一定有所收获。 Dubbo的SPI机制,这篇文章就够了。

胖墩儿没腹肌 1387

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了

兄弟们,上次的故障结果出来了。还好销售团队给力,没有让客户几千万的单子丢掉,成功挽回了本次损失。不过内部处罚还是相对严重,年终奖悬了。这也告诫我们要对生产保持敬畏之情!恰巧最近领导看我在写Dubbo源码系列,看到我们的项目中用了SPI扩展。于是给我一个将功补过的机会,让我好好的分析分析Dubbo的SPI的扩展机制,进行组内技术分享。作为一个常年分享源码系列文章的选手,当然不会拒绝!乾坤未定,你我皆是黑马,冲!

爱敲代码的小黄 4968

dubbo源码分析之八-dubbo的spi机制

本片博文以spi机制为切入点,首先了解spi机制,介绍了java自己的SPI实现方式和源码分析,并通过其缺点引出了Dubbo的SPI机制,通过对其使用示例、流程、源码分析等详细分析了其相关流程,其中对ExtensionLoader、@Adaptive,@Activate,@SPI注解进行使用介绍。希望这边文章能让读者很好的了解java SPI和Dubbo的SPI机制。

liushangzaibeijing的博客 5万+

【dubbo源码解析】 --- dubbo spi 机制之@Activate简介

本文对应源码地址:https://github.com/nieandsun/dubbo-study 文章目录1 @Activate标签的应用场景2 @Activate标签 及其使用简介2.1 简单看一下@Activate标签的源码2.2 @Activate标签的使用姿势2.2.1 定义标有@Activate注解的实现类2.2.2 在META-INFO/dubbo文件夹下建立配置文件2.2.3 测试 1 @Activate标签的应用场景 除了上篇文章《【dubbo源码解析】 — dubbo spi .

nrsc 2229

Dubbo SPI 源码解析

Dubbo SPI 源码解析 简介 SPI 全称为 Service Provider Interface,是一种服务发现机制。 SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。 SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,

R先生专栏 1837

Dubbo中的SPI机制源码解析

Dubbo中的SPI机制源码解析SPI机制简介自适应拓展机制介绍扩展点源码阅读1.获取扩展的方法 SPI机制简介 SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。 在源码阅读之前必须要理解的SPI机制中的注解。 @SPI 这个注解标识这是spi的一个扩展点,实现这个接口可以增加一

思考的力量 390

Dubbo 源码分析 - SPI 机制

1.简介 SPI 全称为 Service Provider Interface,是 Java 提供的一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制...

Java2B 1055

【dubbo系列】SPI机制源码解析

​ SPI 全称为 Service Provider Interface,是一种服务发现机制。此机制在dubbo中大量使用。以至于dubbo框架的及其灵活。 ​ dubbo SPI 源码地址:http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html 1.java SPI源码解析 1.1 SPI使用场景 ​ dubbo官方中有java SPI的示例,但是初始并不是特别的理解。或者根本不理解这种设计的好处。直到看到mysql驱动包

叁滴水 的博客 1736

Dubbo源码阅读——扩展机制 Dubbo SPI

文章目录一、SPI简介1.JDK SPI2.Dubbo SPI二、Dubbo 扩展点机制源码阅读1.ExtensionLoader的属性2.getExtensionLoader()方法获取加载器实例3.getExtension()方法获取扩展点的实现类实例getExtension()createExtension() 一、SPI简介 一套成熟的框架在扩展性方面必然需要完备,duboo扩展点加载机制...

yozzs的博客 1483

dubbo(2) Dubbo源码解析之SPI机制

来源:https://juejin.im/post/5ca2d84bf265da3083063aa3 1. 简介 Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。 2. 源码分析 Dubbo SPI 的相关逻辑被封装在了 Extension...

cowbin2012的专栏 1万+

java SPI 04-spi dubbo 实现源码解析

系列目录 spi 01-spi 是什么?入门使用 spi 02-spi 的实战解决 slf4j 包冲突问题 spi 03-spi jdk 实现源码解析 spi 04-spi dubbo 实现源码解析 spi 05-dubbo adaptive extension 自适应拓展 spi 06-自己从零手写实现 SPI 框架 spi 07-自动生成 SPI 配置文件实现方式 dubbo spi 而Dubbo中最核心的一点就是SPI和自适应扩展,Dubbo的高扩展性以及其它功能都是在这个基础上实现的,理解掌握其原理

1万+
上一篇: 深入理解远程过程调用
下一篇: Dubbo源码(2)-动态编译技术源码解析
BoringRong
BoringRong 领域专家: 后端开发技术领域 领域专家: 后端开发技术领域
博客等级 码龄9年 2万+粉丝 386原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BoringRong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值