温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

如何理解Dubbo的SPI自适应

发布时间:2021-10-19 16:49:11 来源:亿速云 阅读:147 作者:iii 栏目:编程语言

这篇文章主要介绍“如何理解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;

@SPI
public 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 <T> ExtensionLoader<T> getExtensionLoader(Class<T> 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<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对象,主要是对接口类做一些验证,确认是扩展点(有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自适应”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI