如何理解Dubbo的SPI自适应

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

措美网站制作公司哪家好,找创新互联公司!从网页设计、网站建设、微信开发、APP开发、自适应网站建设等网站项目制作,到程序开发,运营维护。创新互联公司成立于2013年到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选创新互联公司

先定义一个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  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自适应”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!


当前文章:如何理解Dubbo的SPI自适应
文章源于:http://pwwzsj.com/article/jsheip.html