Dubbo源码学习(一)

dubbo的SPI机制-ExtensionLoader

Posted by 独顽且鄙 on August 20, 2017

JAVA的SPI机制

SPI例子

SPI的全名为Service Provider Interface。先写下例子更好理解:

public interface Coder {
    void code();
}
public class JavaCoder implements Coder {
    public void code() {
        System.out.println("code in JAVA");
    }
}
public class GoCoder implements Coder {
    public void code() {
        System.out.println("code in GO");
    }
}

META-INF/services下创建文件名:com.dubbo.mylearncode.spi.code(即Coder接口的全路径名)

com.dubbo.mylearncode.spi.JavaCoder
com.dubbo.mylearncode.spi.GoCoder
public class Main {
    public static void main(String[] args){
        ServiceLoader<Coder> serviceLoader = ServiceLoader.load(Coder.class);
        for(Coder coder : serviceLoader){
            coder.code();
        }
    }
}

执行Main类,输出的结果为:

code in JAVA
code in GO

通过ServiceLoader.load(Coder.class)方法,java会到META-INF/services目录下寻找Coder接口的全路径名的文件,即com.dubbo.mylearncode.spi.code,找到文件后会自动加载配置的实现类,从而能够实现可插拔的接口实现定制。

SPI的缺点

  • JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过 getName()获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。
  • 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。

为了解决这个问题,dubbo实现了自己的一套spi机制,即ExtensionLoader

ExtensionLoader

ExtensionLoader的用法不做即录,直接记录阅读源码的过程。

缓存注释(类属性)

缓存类型 缓存名 作用
ConcurrentMap<Class<?>, String> cachedNames 非@Adaptive的实现类的名字缓存 class -> 扩展点名字(即配置文件中的key)
Holder<Map<String, Class<?>>> cachedClasses 非@Adaptive的实现类的名字缓存,与上面相反
Map<String, Activate> cachedActivates @Activate的实现类缓存 名字 -> Activate对象
Class<?> cachedAdaptiveClass @Adaptive的实现类类缓存(从代码中看出,只允许一个Adaptive实现类)
Holder<Object> cachedAdaptiveInstance @Adaptive注解的实例缓存
ConcurrentMap<String, Holder<Object>> cachedInstances 非@Adaptive实现类的实例缓存
String cachedDefaultName 默认扩展的名字,即@SPI的value值

入口

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 interface!");
    }
    if(!withExtensionAnnotation(type)) {   //如果无SPI注解
        throw new IllegalArgumentException("Extension type(" + type + 
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }
    
    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的主入口是一个静态方法getExtensionLoader,接受一个class类型的参数,代表着要获取哪个接口的扩展点。ExtensionLoader是通过@SPI注解来标记一个接口是否可以使用ExtensionLoader获取扩展,所以在指定接口没有@SPI注解时会直接抛出异常。

EXTENSION_LOADERS是一个static的全局静态缓存,存着class与ExtensionLoader的对应map,所以先会在缓存里找对应的ExtensionLoader,如果找不到,就new一个ExtensionLoader并加入缓存中。下面看看new ExtensionLoader(type)是怎么实现的。

构造方法

private ExtensionLoader(Class type) {
    this.type = type;
    if(type == ExtensionFactory.class){
        objectFactory = null;
    }else{
        objectFactory = ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();
    }
}

进入私有构造方法后,主要对两个属性进行了赋值。type,即ExtensionLoader的泛型类型;objectFactory为ExtensionFactory类型,之后在动态注入时会用到。

主要方法

private static final ExtensionLoader<Container> loader = ExtensionLoader.getExtensionLoader(Container.class);

上面一行代码是在com.alibaba.dubbo.container.Main获取ExtensionLoader的代码,在这行代码执行之后,loader中的关于扩展点的缓存还尚未加载,那缓存是什么时候加载的呢?ExtensionLoader针对spi机制进行了扩展,只有在真正用到的时候才会进行加载。

T getExtension(String name) 根据name获取类实例对象

getExtension(name)    //在cachedInstances缓存中找,找不到进入下面
    -> createExtension(name)
        -> getExtensionClasses()  //在cachedClasses缓存中找,找不到进入下面
            -> loadExtensionClasses()
                ->loadFile(extensionClasses,filePath)  //读取文件,填充缓存
    -> injectExtension(T instance)
    

loadFile()

loadFile是ExtensionLoader加载spi配置文件的方法,是一个比较重要的方法,不只是在getExtension(String name)方法中用到,在所有类似的方法(例如:getExtensionClass(String name),getDefaultExtensionName()等)缓存还未初始化时都会调用。

加载配置文件时,dubbo会从三个位置去找文件,即:

  • META-INF/services/
  • META-INF/dubbo/
  • META-INF/services/internal/ 但是dubbo默认都将配置文件放到了META-INF/services/internal/路径下面

下面是loadFile方法中解析class的其中较关键的部分:

if (clazz.isAnnotationPresent(Adaptive.class)) {    //如果该实现类有@Adaptive注解
    if(cachedAdaptiveClass == null) {
        cachedAdaptiveClass = clazz;
    } else if (! cachedAdaptiveClass.equals(clazz)) {
        throw new IllegalStateException("More than 1 adaptive class found: "
            + cachedAdaptiveClass.getClass().getName()
            + ", " + clazz.getClass().getName());
    }
} else {    //如果该实现类无@Adaptive注解
    try {
        clazz.getConstructor(type); //查看该实现类是否有type的构造方法,没有则直接进入异常
        //如果有type类型的构造方法,则为wrapper类。加入wrapper缓存
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
} catch (NoSuchMethodException e) {     //没有type类型的构造方法
    clazz.getConstructor();
    if (name == null || name.length() == 0) {
        name = findAnnotationName(clazz);
        if (name == null || name.length() == 0) {
            if (clazz.getSimpleName().length() > type.getSimpleName().length()
                && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
            } else {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
            }
        }
    }
    String[] names = NAME_SEPARATOR.split(name);
    if (names != null && names.length > 0) {
        Activate activate = clazz.getAnnotation(Activate.class);
        if (activate != null) {
            cachedActivates.put(names[0], activate);
        }
        for (String n : names) {
            if (! cachedNames.containsKey(clazz)) {
                cachedNames.put(clazz, n);
            }
            Class<?> c = extensionClasses.get(n);
            if (c == null) {
                extensionClasses.put(n, clazz);
            } else if (c != clazz) {
                throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
            }
        }
    }
}

可以看出,实现类被分成两种类型进行了解析。有@Adaptive注解、无@Adaptive注解。而无@Adaptive注解又分为两种,Wrapper与普通实现类,wrapper即对普通的实现类又进行了一层包装,必要特征即:构造方法有一个当前类的参数。

  1. 如果实现类有@Adaptive注解,则直接将cachedAdaptiveClass赋值。(如果之前有值且与当前值不相等,直接抛异常)
  2. 如果为wrapper类,则加入cachedWrapperClasses缓存
  3. 其余的即为普通实现类,则加入cachedNames缓存。另,如果有@Active注解的,则加入cachedActivates缓存。(@Active注解还不太明白,后续看代码看到的时候继续补充)
  4. 最后loadFile方法结束后,会将name-class的缓存加入cachedClasses(getExtensionClasses()方法中)

另:如果是有多个Wrapper实现类时,是会进行多层包装的。例如Protocol接口,可能最外层是 ProtocolFilterWrapper 包装了 ProtocolListenerWrapper ,而 ProtocolListenWrapper 里面才包装着具体的实现类(例如 DubboProtocol )。

createExtension(String name) EXtensionLoader的实例化

之前说到SPI的其中一个缺点就是一次会实例化所有的配置类。而在createExtension(String name)方法中可以看出,ExtensionLoader在使用一个类的时候才会去实例化这个类。

T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
    EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
    instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);  //注入实例属性
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
//如果有wrapper,为wrapper,否则直接返回。后面的wrapper会覆盖前面的wrapper
if (wrapperClasses != null && wrapperClasses.size() > 0) {
    for (Class<?> wrapperClass : wrapperClasses) {
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    }
}
return instance;

方法首先会到EXTENSION_INSTANCES缓存中取得对象实例,如果没有,则直接利用反射创建一个新对象,并进行属性的注入。如果有wrapper,则创建wrapper对象。通过Wrapper类可以把所有扩展点公共逻辑移至Wrapper中。新加的Wrapper在所有的扩展点上添加了逻辑,有些类似AOP(Wraper代理了扩展点) (目前对wrapper理解还不是特别深刻,后续看代码继续补充)

injectExtension(T instance) ExtensionLoader的自动注入

for (Method method : instance.getClass().getMethods()) {
    if (method.getName().startsWith("set")
         && method.getParameterTypes().length == 1
          && Modifier.isPublic(method.getModifiers())) {  //寻找setter方法
        Class<?> pt = method.getParameterTypes()[0];
        try {
            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
            Object object = objectFactory.getExtension(pt, property);
            if (object != null) {
                method.invoke(instance, object);
            }
        } catch (Exception e) {
            logger.error("fail to inject via method " + method.getName()
                   + " of interface " + type.getName() + ": " + e.getMessage(), e);
        }
    }
}

ExtensionLoader会寻找符合条件的setter方法,并利用objectFactory获取对象进行注入。这里获取的对象是通过getAdaptiveExtension()方法获取的自适应对象。@Adaptive我会单独记录一篇文章。

其他常用方法(非Adaptive、Active)

  • T getDefaultExtension() 获取默认的扩展。
  • String getDefaultExtensionName() 获取默认扩展名,即SPI注解中配置的值
  • boolean hasExtension(String name) 是否有指定的扩展
  • Set getSupportedExtensions() 获取所有支持的扩展