千家信息网

如何理解Dubbo的SPI自适应

发表于:2025-01-21 作者:千家信息网编辑
千家信息网最后更新 2025年01月21日,这篇文章主要介绍"如何理解Dubbo的SPI自适应",在日常操作中,相信很多人在如何理解Dubbo的SPI自适应问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"如何理解D
千家信息网最后更新 2025年01月21日如何理解Dubbo的SPI自适应

这篇文章主要介绍"如何理解Dubbo的SPI自适应",在日常操作中,相信很多人在如何理解Dubbo的SPI自适应问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"如何理解Dubbo的SPI自适应"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

先定义一个SPI接口(被@SPI标注):

import org.apache.dubbo.common.URL;import org.apache.dubbo.common.extension.Adaptive;import org.apache.dubbo.common.extension.SPI;@SPIpublic interface SpiIf {    @Adaptive    void test1(URL url);    @Adaptive    void test2(ObjHasUrl ohu);    void test3(URL url);    void test4(String name);}

这里的ObjHasUrl是一个内部有URL属性的对象,为什么要有URL属性的原因下面会说到。

下一步,定义两个实现类Spi1和Spi2:

public class Spi1 implements SpiIf {    @Override    public void test1(URL url) {        System.out.println("This is Spi1:test1");    }    @Override    public void test2(ObjHasUrl ohu) {        System.out.println("This is Spi1:test2");    }    @Override    public void test3(URL url) {        System.out.println("This is Spi1:test3");    }    @Override    public void test4(String name) {        System.out.println("This is Spi1:test4");    }}public class Spi2 implements SpiIf {    @Override    public void test1(URL url) {        System.out.println("This is Spi2:test1");    }    @Override    public void test2(ObjHasUrl ohu) {        System.out.println("This is Spi2:test2");    }    @Override    public void test3(URL url) {        System.out.println("This is Spi2:test3");    }    @Override    public void test4(String name) {        System.out.println("This is Spi2:test4");    }}

最后一步,定义一个Runner测试启动器:

public class Runner {    public static void main(String[] args) {        URL url = new URL("dubbo", "123", 999);        url = url.addParameter("spi.if", "S2"); //设置url值,来获取 SpiTest的自适应扩展 S2。        SpiIf spiIf = ExtensionLoader.getExtensionLoader(SpiIf.class).getAdaptiveExtension();        spiIf.test1(url);        ObjHasUrl ohu = new ObjHasUrl(url);        spiIf.test2(ohu);                url = url.addParameter("spi.if", "S1");        SpiIf spiIf2 = ExtensionLoader.getExtensionLoader(SpiIf.class).getAdaptiveExtension();        spiIf.test2(ohu);    }}

以上类可以直接拷贝到自己的本地工程DEBUG用。

当我们启功Runner的时候,第一步先看getExtensionLoader方法,这里开始进入Dubbo的代码:

public static  ExtensionLoader getExtensionLoader(Class type) {if (type == null) {            throw new IllegalArgumentException("Extension type == null");        }//验证是否是接口        if (!type.isInterface()) {            throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");        }//验证是否有SPI注解        if (!withExtensionAnnotation(type)) {            throw new IllegalArgumentException("Extension type (" + type +                    ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");        }        ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);        if (loader == null) {            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));            loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);        }        return loader;    }

这一步主要是获取ExtensionLoader对象,主要是对接口类做一些验证,确认是扩展点(有SPI注解)。

第二步,进入ExtensionLoader的getAdaptiveExtension方法:

public T getAdaptiveExtension() {//先找缓存        Object instance = cachedAdaptiveInstance.get();        if (instance == null) {            if (createAdaptiveInstanceError != null) {                throw new IllegalStateException("Failed to create adaptive instance: " +                        createAdaptiveInstanceError.toString(),                        createAdaptiveInstanceError);            }//缓存没有,则开始创建            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);                    }                }            }        }        return (T) instance;    }

这一步主要是获取接口的实现对象实列,继续分析实际创建扩展点的方法:

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

这一步拆分成两步,第一步:getAdaptiveExtensionClass,第二步:injectExtension

第一步:

private Class getAdaptiveExtensionClass() {        getExtensionClasses();        if (cachedAdaptiveClass != null) {            return cachedAdaptiveClass;        }        return cachedAdaptiveClass = createAdaptiveExtensionClass();    }    private Class createAdaptiveExtensionClass() {        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();        ClassLoader classLoader = findClassLoader();        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();        return compiler.compile(code, classLoader);    }

重点来了,创建class的时候会生成一个code(实际是SpiIf$Adaptive->SpiIf一个实现类):

import org.apache.dubbo.common.extension.ExtensionLoader;public class SpiIf$Adaptive implements SpiIf {    public void test2(com.zf.server.authserver.spi.dubbospitest2.ObjHasUrl arg0)  {        if (arg0 == null) throw new IllegalArgumentException("com.zf.server.authserver.spi.dubbospitest2.ObjHasUrl argument == null");        if (arg0.getUrl() == null) throw new IllegalArgumentException("com.zf.server.authserver.spi.dubbospitest2.ObjHasUrl argument getUrl() == null");        org.apache.dubbo.common.URL url = arg0.getUrl();        String extName = url.getParameter("spi.if");        if(extName == null) throw new IllegalStateException("Failed to get extension (com.zf.server.authserver.spi.dubbospitest2.SpiIf) name from url (" + url.toString() + ") use keys([spi.if])");        com.zf.server.authserver.spi.dubbospitest2.SpiIf extension = (com.zf.server.authserver.spi.dubbospitest2.SpiIf)ExtensionLoader.getExtensionLoader(com.zf.server.authserver.spi.dubbospitest2.SpiIf.class).getExtension(extName);        extension.test2(arg0);    }    public void test1(org.apache.dubbo.common.URL arg0)  {        if (arg0 == null) throw new IllegalArgumentException("url == null");        org.apache.dubbo.common.URL url = arg0;        String extName = url.getParameter("spi.if");        if(extName == null) throw new IllegalStateException("Failed to get extension (com.zf.server.authserver.spi.dubbospitest2.SpiIf) name from url (" + url.toString() + ") use keys([spi.if])");        com.zf.server.authserver.spi.dubbospitest2.SpiIf extension = (com.zf.server.authserver.spi.dubbospitest2.SpiIf)ExtensionLoader.getExtensionLoader(com.zf.server.authserver.spi.dubbospitest2.SpiIf.class).getExtension(extName);        extension.test1(arg0);    }    public void test3(org.apache.dubbo.common.URL arg0)  {        throw new UnsupportedOperationException("The method public abstract void com.zf.server.authserver.spi.dubbospitest2.SpiIf.test3(org.apache.dubbo.common.URL) of interface com.zf.server.authserver.spi.dubbospitest2.SpiIf is not adaptive method!");    }    public void test4(java.lang.String arg0)  {        throw new UnsupportedOperationException("The method public abstract void com.zf.server.authserver.spi.dubbospitest2.SpiIf.test4(java.lang.String) of interface com.zf.server.authserver.spi.dubbospitest2.SpiIf is not adaptive method!");    }}

仔细一看,其实就是为我们的接口生成了一个实现类。然后为Adaptive注解标注的方法生成了实际的内容(就是根据URL参数来获取实际的扩展类),这也解释的Adaptive注解的实际作用。

还有一点需要注意:test1提供的是URL的参数,test2提供的是包含URL属性的对象。它们的共同点就是都包含了一个URL,如果不提供会提示没有URL异常。具体原因可以自行分析以下方法:

new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();

最终在加载生成的实现类。

第二步:injectExtension

private T injectExtension(T instance) {        if (objectFactory == null) {            return instance;        }        try {            for (Method method : instance.getClass().getMethods()) {                if (!isSetter(method)) {                    continue;                }                           if (method.getAnnotation(DisableInject.class) != null) {                    continue;                }                Class pt = method.getParameterTypes()[0];                if (ReflectUtils.isPrimitives(pt)) {                    continue;                }                try {                    String property = getSetterProperty(method);                    Object object = objectFactory.getExtension(pt, property);                    if (object != null) {                        method.invoke(instance, object);                    }                } catch (Exception e) {                    logger.error("Failed to inject via method " + method.getName()                            + " of interface " + type.getName() + ": " + e.getMessage(), e);                }            }        } catch (Exception e) {            logger.error(e.getMessage(), e);        }        return instance;    }

而这一步干啥呢?简单说就是循环set方法,如果参数类型也是一个自适应扩展点的话,继续上面的步骤拿到扩展点对象并反射注入,实现了Dubbo版的依赖注入。

至此,返回最终生成的对象-> SpiIf$Adaptive的实例并缓存在cachedAdaptiveInstance中,在Runner中就会根据url对应的参数值来获取对应的扩展类。

总结:

1、自适应扩展接口需要 SPI注解,方法需要Adaptive注解,Adaptive方法需要URL参数或者是有URL属性的对象参数;

2、最终会返回接口实现类对象 SpiIf$Adaptive,里面封装了根据url参数来获取扩展对象的方法;

到此,关于"如何理解Dubbo的SPI自适应"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

0