Dubbo中的SPI机制实现,自定义对接SPI

Posted Leo Han

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dubbo中的SPI机制实现,自定义对接SPI相关的知识,希望对你有一定的参考价值。

SPI在java中称为Service Provider Interface,Dubbo中对java的spi机制自己实现相对应的逻辑。
我们以Dubbo中的Protociol为例,一般我们去获取对应的Protocol的时候都是通过如下方式:

ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
// 或者
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName);

我们看下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!");
        
        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;
    

可以看到,Dubbo中是每个通过SPI加载类型都有一个对应的ExtensionLoader,我们看看具体怎么加载类的:

 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) 
        if (StringUtils.isEmpty(name)) 
            throw new IllegalArgumentException("Extension name == null");
        
        if ("true".equals(name)) 
            return getDefaultExtension();
        
        final Holder<Object> holder = getOrCreateHolder(name);
        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 
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) 
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            
            injectExtension(instance);


            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);
                        if (wrapper == null
                                || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) 
                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        
                    
                
            

            initExtension(instance);
            return instance;
         catch (Throwable t) 
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        
    
private Map<String, Class<?>> loadExtensionClasses() 
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();

        for (LoadingStrategy strategy : strategies) 
            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        

        return extensionClasses;
    

这里我们首先会通过getExtensionClasses去加载对应的SPI扩展类,通过loadExtensionClasses去进行实际的加载,这里首先会用java的 ServiceLoader去加载对应的LoadingStrategy,而ServiceLoader会加载当前classpath下META-INF.services下对应去路径类对应的文件中列出的类:

而这里LoadingStrategy则会加载如下三个实现类:

org.apache.dubbo.common.extension.DubboInternalLoadingStrategy
org.apache.dubbo.common.extension.DubboLoadingStrategy
org.apache.dubbo.common.extension.ServicesLoadingStrategy

而这里这几个LoadingStrategy主要是定义了要扫描哪些文件夹:
DubboInternalLoadingStrategy会加载如下文件夹:

public class DubboInternalLoadingStrategy implements LoadingStrategy 
    public String directory() 
        return "META-INF/dubbo/internal/";
    
    public int getPriority() 
        return MAX_PRIORITY;
    

DubboLoadingStrategy:

public class DubboLoadingStrategy implements LoadingStrategy 
    public String directory() 
        return "META-INF/dubbo/";
    
    public boolean overridden() 
        return true;
    
    public int getPriority() 
        return NORMAL_PRIORITY;
    


ServicesLoadingStrategy:

public class ServicesLoadingStrategy implements LoadingStrategy 
    public String directory() 
        return "META-INF/services/";
    
    public boolean overridden() 
        return true;
    
    public int getPriority() 
        return MIN_PRIORITY;
    

也就是说,如果我们想要Dubbo的SPI区加载我们指定的目录下的实现的话,那么我们需要实现LoadingStrategy然后在classpath下新建预估ieorg.apache.dubbo.common.extension.LoadingStrategy名称的文件,文件的内容为我们自己实现的LoadingStrategy全名称,多个换行即可

从上面可以看到,Dubbo会从如下几个文件夹去加载对应的SPI扩展类:
META-INF/dubbo/internal/META-INF/dubbo/META-INF/services/
最终通过loadResource去加载对应需要加载的类全名称文件里面的内容:

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                              java.net.URL resourceURL, boolean overridden, String... excludedPackages) 
        try 
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) 
                String line;
                String clazz = null;
                while ((line = reader.readLine()) != null) 
                    final int ci = line.indexOf('#');
                    if (ci >= 0) 
                        line = line.substring(0, ci);
                    
                    line = line.trim();
                    if (line.length() > 0) 
                        try 
                            String name = null;
                            int i = line.indexOf('=');
                            if (i > 0) 
                                name = line.substring(0, i).trim();
                                clazz = line.substring(i + 1).trim();
                             else 
                                clazz = line;
                            
                            if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) 
                                loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
                            
                         catch (Throwable t) 
                        
                    
                
            
         catch (Throwable t) 
        
    

可以看到,这里是按照行进行读取的,然后需要注意的是这里会判断每行是否有=,如果有=号的化,那么会进行分割,拿到对应的SPI需要加载的类

拿到类之后,接下来就去加载类,通过loadClass实现:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                           boolean overridden) throws NoSuchMethodException 
        if (!type.isAssignableFrom(clazz)) 
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        
        if (clazz.isAnnotationPresent(Adaptive.class)) 
            cacheAdaptiveClass(clazz, overridden);
         else if (isWrapperClass(clazz)) 
            cacheWrapperClass(clazz);
         else 
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) 
                name = findAnnotationName(clazz);
                if (name.length() == 0) 
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                
            

            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) 
                cacheActivateClass(clazz, names[0]);
                for (String n : names) 
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
                
            
        
    

上面逻辑主要有三个:

  1. 加载的类上面是否有Adaptive注解,如果有Adaptive那么会设置cacheAdaptiveClass
  2. 如果没有Adaptive注解,那么判断是否是Wrapper类,这里Dubbo判断是否是Wrapper的方式很简单,就是判断这个类的构造函数是否支持只传入一个参数,且这个参数是待加载的类型:
private boolean isWrapperClass(Class<?> clazz) 
        try 
            clazz.getConstructor(type);
            return true;
         catch (NoSuchMethodException e) 
            return false;
        
    
  1. 如果上面两项都不符合的话,如果name为空,那么查看类上Extension注解对应的值作为name,如果没有Extension注解,那么name的逻辑为判断类名称的是否以待获取的类名称结尾,如果是直接直接取类名称去除待获取类名称的部分并转小写然后将name和对应的class类型设置到缓存中去:
private String findAnnotationName(Class<?> clazz) 
        org.apache.dubbo.common.Extension extension = clazz.getAnnotation(org.apache.dubbo.common.Extension.class);
        if (extension != null) 
            return extension.value();
        
        String name = clazz.getSimpleName();
        if (name.endsWith(type.getSimpleName())) 
            name = name.substring(0, name.length() - type.getSimpleName().length());
        
        return name.toLowerCase();
    

也就是说,我们通过

这样我们就加载到了所有的类,并设置到了对应的类中,如果不是AdaptiveWrapperClass那么会将类和对应name放置到一个map中并返回,然后在createExtension中根据name就能够获取到对应的类型

对于createExtension,首先是根据获取到的类型进行实例化,并注入相关的属性(注入属性也是通过SPI机制,如果有set方法会进行注入,如果方法上有DisableInject注解或忽略

实例化类之后,会进行wrap也就是包装,会实例化Wrapper,然后将生成的实际类的实例注入进去,这里由于会存在多个Warapper类,会进行排序,通过获取Activate中的order属性,这里是按照从小到大的顺序排列。
这样多个Wrapper实际上是一个Wraprer嵌套这一个Wrapper

所以说,Dubbo的SPI机制如果有warapper实现返回的是一个Wrapper类。

另外对于getAdaptiveExtension,Dubbo中的处理也很简单暴力,直接加载对应要加载类名称后面加上$Adaptive

private Class<?> createAdaptiveExtensionClass() 
        ClassLoader classLoader = findClassLoader();
        if (ApplicationModel.getEnvironment().getConfiguration(以上是关于Dubbo中的SPI机制实现,自定义对接SPI的主要内容,如果未能解决你的问题,请参考以下文章

dubbo源码之——基于SPI的自定义扩展实现

三歪问我Dubbo的SPI机制是啥?

dubbo源码实践-SPI扩展-自适应扩展机制

dubbo源码实践-SPI扩展-自适应扩展机制

学习手写Dubbo中的SPI

dubbo实现原理之SPI简介