dubbo spi扩展

spi

Posted by CHuiL on September 15, 2020

SPI

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

dubbo SPI

java也有SPI,不过其存在缺陷,每次加载会将所有实现类加载出来,导致加载慢资源浪费;
dubbo相比java spi,不同的地方有以下几点

  • 配置文件通过键值对配置。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下
     optimusPrime = org.apache.spi.OptimusPrime
    bumblebee = org.apache.spi.Bumblebee
    
  • 按需加载实现类
  • 实现Class类,实例对象的缓存
  • 增加了 IOC 和 AOP 特性

源码解读-按需加载与缓存

dubbo获取一个实例对象的大致流程如下

1、ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。

2、先从缓存中获取实例对象getOrCreateHolder(name);,如果实例对象不存在,则尝试利用Class来实例化一个

3、接着需要获取Class
` Class<?> clazz = getExtensionClasses().get(name);; 先从缓存中获取ClassName到Class之间的map Map<String, Class<?» classes = cachedClasses.get();`,
如果为null,则读取配置文件,获取实现类名,利用Class.ForName来得到实例Class对象,并设置到Map和缓存中。

4、有了Class之后,就可以实例化了EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());

5、接着对该对象中的依赖进行注入。injectExtension(instance);

6、利用反射,获取该对象中所有set方法,通过set方法可以获得依赖的类型和变量名;
Class<?> pt = method.getParameterTypes()[0];
String property = getSetterProperty(method);
然后根据这两个值去获得依赖对象
Object object = objectFactory.getExtension(pt, property);
最后调用set方法实现依赖注入
method.invoke(instance, object);

自适应拓展机制

有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。

自适应拓展类的核心实现 —- 在拓展接口的方法被调用时,通过 SPI 加载具体的拓展实现类,并调用拓展对象的同名方法。; 也就是说,一开始我们并不注入扩展对象,而是构造一个实现了该接口的拓展代理,由该代理执行同名方法,并由该代理通过SPI加载具体的拓展实现类,再调用实际拓展对象的方法。

使用自适应拓展

  • ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

    自适应源码解读

    1、调用ExtensionLoader.getExtensionLoader(CLASS).getAdaptiveExtension();
    获取自适应拓展实例。先尝试从缓存中获取,Object instance = cachedAdaptiveInstance.get();
    如果缓存中没有,则创建一个自适应拓展对象。 instance = createAdaptiveExtension();

2、injectExtension((T) getAdaptiveExtensionClass().newInstance()); 这段代码中,先是自动生成代码,加载出代理扩展对象class,然后实例化对象,最后自动注入该对象依赖。主要重点在自动生成Class代码并加载Class对象

3、 在获取Class对象之前,也会先尝试从缓存中获取代理拓展Class getExtensionClasses(); ,获取不到才执行代码生成
return cachedAdaptiveClass = createAdaptiveExtensionClass();

4、String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();代码生成。

    public String generate() {
        // no need to generate adaptive class since there's no adaptive method found.
        if (!hasAdaptiveMethod()) {
            throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
        }

        StringBuilder code = new StringBuilder();
        code.append(generatePackageInfo()); //生成page代码 "package %s;\n";
        code.append(generateImports()); //生成import代码 "import %s;\n"; 
        code.append(generateClassDeclaration()); //生成 "public class %s$Adaptive implements %s {\n";

        //生成各个接口的自适应代码,如果接口不带有@Adaptive,调用将返回异常。
        Method[] methods = type.getMethods(); 
        for (Method method : methods) {
            code.append(generateMethod(method));
        }
        code.append("}");

        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        return code.toString();
    }
package %s;\n  //type.getPackage().getName()
import %s;\n //ExtensionLoader.class.getName()
public class %s$Adaptive implements %s {\n  //type.getSimpleName(), type.getCanonicalName()
    
    //if (adaptiveAnnotation == null) {
    public %s %s(%s) %s {  //methodReturnType, methodName, methodArgs, methodThrows
        throw new UnsupportedOperationException("The method %s of interface %s is not adaptive method!");
    }
        
    public %s %s(%s) %s {
        //如果接口参数中有url,则可以直接从参数中获取url
        if (arg%d == null) throw new IllegalArgumentException("url == null");
        %s url = arg%d;
        
        //利用反射,从参数中获取能够get,返回值为URL.class的参数方法,如果没有则抛出异常
        if (arg%d == null) throw new IllegalArgumentException("%s argument == null");
        if (arg%d.%s() == null) throw new IllegalArgumentException("%s argument %s() == null");
        %s url = arg%d.%s();
        
        //获取拓展对象名,会先查看Adaptive的值,没有默认为拓展simple name
        if (arg%d == null) throw new IllegalArgumentException(\"invocation == null\"); 
        String methodName = arg%d.getMethodName();\n
        
        String extName = %s; //url.getMethodParameter(methodName, "%s", "%s") || url.getParameter("%s", %s) || url.getProtocol() == null ? (%s) : url.getProtocol() || url.getProtocol()
        
        //extName判空
        if(extName == null) throw new IllegalStateException(\"Failed to get extension (%s) name from url (\" + url.toString() + \") use keys(%s)\");\n;

        //加载扩展对象
        %s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);//type.getName(),ExtensionLoader.class.getSimpleName(), type.getName()

        //返回,没有return则为""
        return extension.%s(%s);//method.getName(), args
        
    }
}

    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对象
    }

java spi dubbo spi 与 dubbo spi自适应拓展机制的区别

  • java spi会实例化所有的对象。
  • dubbo spi 则是会在用到的时候在实例化,第一次的时候会将所有的Class加载到缓存,然后第一次获取对象的时候在实例化
  • 自适应与前者的区别时,第一次获取对象获取到的是代理扩展对象,只有在第一次调用接口方法的时候,才会触发代理由代理实例扩展对象并调用实际的扩展方法,代理和扩展都会缓存起来。