Dubbo SPI机制解析

272 阅读2分钟

SPI核心机制主要依赖于ExtensionLoader类,在3.0版本中获取扩展点的方式跟2版本是有区别的,已经废弃了原本的方式,而是采用以下方式

ExtensionLoader<Protocol> extensionLoader = 
ApplicationModel.defaultModel().getExtensionLoader(Protocol.class);
Protocol http = extensionLoader.getExtension("http");
http.sendRequest("http hello");

ExtensionLoader的核心功能点:

  • 扩展点的加载
  • 扩展点的依赖注入
  • 扩展点的自适应
  • 以及扩展点的激活

1.extensionLoader.getExtension()方法流程

public T getExtension(String name) {
    //调用重载方法,跟进去
    T extension = getExtension(name, true);
    if (extension == null) {
        throw new IllegalArgumentException("Not find extension: " + name);
    }
    return extension;
}
public T getExtension(String name, boolean wrap) {
    
    final Holder<Object> holder = getOrCreateHolder(cacheKey);
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                //双重锁创建对象
                instance = createExtension(name, wrap);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}
private T createExtension(String name, boolean wrap) {
    //获取类对象
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null || unacceptableExceptions.contains(name)) {
        throw findException(name);
    }
    try {
        //先从实例map中取一下看是否存在实例
        T instance = (T) extensionInstances.get(clazz);
        if (instance == null) {
            //创建实例并放入map中
            extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
            //再从实例map中获取
            instance = (T) extensionInstances.get(clazz);
            //前置增强
            instance = postProcessBeforeInitialization(instance, name);
            //判断是否需要setter注入
            injectExtension(instance);
            //后置增强
            instance = postProcessAfterInitialization(instance, name);
        }
        //是否存在包装
        if (wrap) {
            List<Class<?>> wrapperClassesList = new ArrayList<>();
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }

            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                for (Class<?> wrapperClass : wrapperClassesList) {
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    boolean match = (wrapper == null) ||
                        ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
                            !ArrayUtils.contains(wrapper.mismatches(), name));
                    if (match) {
                        //装饰增强
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        //执行后置通知
                        instance = postProcessAfterInitialization(instance, name);
                    }
                }
            }
        }

        // Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
            type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

2.Dubbo 依赖注入

依赖注入,实现类依赖的属性自动注入,类似spring的依赖注入。如果字段不想不注入,可以在set方法上使用DisableInject注解。被依赖注入的属性类的方法必须有@Adaptive注解,即这个类是一个自适应SPI。

3.扩展点原理

案例代码如下

@SPI
public interface Animal {
    @Adaptive
    String eat(URL url);
}
@Adaptive
public class Cat implements Animal{
    @Override
    public String eat(URL url) {
        return "Cat eat";
    }
}
public class Dog implements Animal{
    @Override
    public String eat(URL url) {
        return "Dog eat";
    }
}
public class Pig implements Animal{
    @Override
    public String eat(URL url) {
        return "Pig eat";
    }
}
public class Demo {


    public static void main(String[] args) {
        ExtensionLoader<Animal> extensionLoader = ExtensionLoader.getExtensionLoader(Animal.class);
        Animal animal = extensionLoader.getAdaptiveExtension();

        String cat = animal.eat(URL.valueOf("http://localhost?aa=pig"));
        System.out.println(cat);
    }
}

输出结果为Cat eat

一个SPI下扩展点只能有一个,原理代码如下

private Map<String, Class<?>> loadExtensionClasses() {
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
    loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
    return extensionClasses;
}
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException();
    }
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        //缓存扩展点
        cacheAdaptiveClass(clazz);
    } else if (isWrapperClass(clazz)) {
        
    } else {
       
    }
}



private void cacheAdaptiveClass(Class<?> clazz) {
    //只能有一个扩展点
    if (cachedAdaptiveClass == null) {
        cachedAdaptiveClass = clazz;
    } else if (!cachedAdaptiveClass.equals(clazz)) {
        throw new IllegalStateException("More than 1 adaptive class found: "
                + cachedAdaptiveClass.getName()
                + ", " + clazz.getName());
    }
}

注释掉Cat的@Adaptive,默认的实现code如下:

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);
}
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Animal\$Adaptive implements com.doukill.dubbo.adaptive.Animal {
    public java.lang.String eat(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("animal", "pig");
        if(extName == null) throw new IllegalStateException("Failed to get extension (com.doukill.dubbo.adaptive.Animal) name from url (" + url.toString() + ") use keys(\[animal])");
        
        com.doukill.dubbo.adaptive.Animal extension = (com.doukill.dubbo.adaptive.Animal)ExtensionLoader.getExtensionLoader(com.doukill.dubbo.adaptive.Animal.class).getExtension(extName);
        return extension.eat(arg0);
    }
}



```
类URL中代码:
public String getParameter(String key) {
    String value = parameters.get(key);
    return StringUtils.isEmpty(value) ? parameters.get(DEFAULT_KEY_PREFIX + key) : value;
}

public String getParameter(String key, String defaultValue) {
    String value = getParameter(key);
    return StringUtils.isEmpty(value) ? defaultValue : value;
}




ExtensionLoader<Animal> extensionLoader = ExtensionLoader.getExtensionLoader(Animal.class);
Animal animal = extensionLoader.getAdaptiveExtension();

String cat = animal.eat(URL.valueOf("http://localhost?animal=cat"));
System.out.println(cat);

String pig = animal.eat(URL.valueOf("http://localhost?animal=pig"));
System.out.println(pig);

String dog = animal.eat(URL.valueOf("http://localhost?animal=dog"));
System.out.println(dog);

//报错 没有默认值,除非在SPI注解上加一个默认值
String def = animal.eat(URL.valueOf("http://localhost"));
System.out.println(def);

```
作用其实比较简单,就是想动态地指定 URL 中的参数,来动态切换实现类去执行业务逻辑,把一堆根据参数获取实现类的重复代码,全部封装到了代理类中,以达到充分灵活扩展的效果。

@Adaptive 注解其实是生成了一个自适应的代理类,每个 SPI 接口都有且仅有一个自适应扩展点。

然后跟踪了加载 SPI 资源文件的 loadDirectory 方法的源码,发现 @Adaptive 注解不仅可以

写在 SPI 接口的方法上,还可以写在 SPI 接口实现类上,并且在使用自适应扩展的时候,若实

现类有 @Adaptive 注解,则优先使用该实现类作为自适应扩展点。


要实现SPI的setter注入,必须要有@Adaptive注解,源码如下:

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();
    return code.toString();
}



public class Bb$Adaptive implements com.doukill.dubbo.demo.Bb {
    public java.lang.String eat(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("bb");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (com.doukill.dubbo.demo.Bb) " +
                    "name from url (" + url.toString() + ") use keys([bb])");
        com.doukill.dubbo.demo.Bb extension = (com.doukill.dubbo.demo.Bb) 
                ExtensionLoader.getExtensionLoader(com.doukill.dubbo.demo.Bb.class).getExtension(extName);
        return extension.eat(arg0);
    }
}