首页 > 其他分享 >Dubbo——扩展(SPI)加载原理

Dubbo——扩展(SPI)加载原理

时间:2023-04-04 18:04:20浏览次数:31  
标签:Dubbo name 扩展 SPI String new null 加载


摘要

Dubbo 为了更好地达到 OCP 原则(即“对扩展开放,对修改封闭”的原则),采用了“微内核+插件”的架构。那什么是微内核架构呢?微内核架构也被称为插件化架构(Plug-in Architecture),这是一种面向功能进行拆分的可扩展性架构。内核功能是比较稳定的,只负责管理插件的生命周期,不会因为系统功能的扩展而不断进行修改。功能上的扩展全部封装到插件之中,插件模块是独立存在的模块,包含特定的功能,能拓展内核系统的功能。微内核架构中,内核通常采用 Factory、IoC、OSGi 等方式管理插件生命周期,Dubbo 最终决定采用 SPI 机制来加载插件,Dubbo SPI 参考 JDK 原生的 SPI 机制,进行了性能优化以及功能增强。

一、SPI 机制原理

当服务的提供者提供了一种接口的实现之后,需要在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,此文件记录了该 jar 包提供的服务接口的具体实现类。当某个应用引入了该 jar 包且需要使用该服务时,JDK SPI 机制就可以通过查找这个 jar 包的 META-INF/services/ 中的配置文件来获得具体的实现类名,进行实现类的加载和实例化,最终使用该实现类完成业务功能。

Dubbo——扩展(SPI)加载原理_缓存

package com.zhuangxiaoyan.dubbo;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.SPI;

/**
 * @Classname Car
 * @Description TODO
 * @Date 2021/12/8 23:00
 * @Created by xjl
 */
@SPI
public interface Car {

    //    @Adaptive
    public void test(URL url);
}
package com.zhuangxiaoyan.dubbo;

import org.apache.dubbo.common.URL;

/**
 * @Classname CarImpl
 * @Description TODO
 * @Date 2021/12/8 23:01
 * @Created by xjl
 */
public class CarImpl implements Car {

    @Override
    public void test(URL url) {
        System.out.println("SPI Test ……");
    }
}
package com.zhuangxiaoyan.dubbo;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;

import java.net.MalformedURLException;


/**
 * @Classname SpiTest
 * @Description TODO
 * @Date 2021/12/8 23:00
 * @Created by xjl
 */
public class SpiTest {
    public static void main(String[] args) throws MalformedURLException {
        ExtensionLoader<Car> extensionLoader= ExtensionLoader.getExtensionLoader(Car.class);

        URL url=new URL("http","localhost", 8080);

        Car car=extensionLoader.getExtension("car");
        car.test(null);
    }
}
car=com.zhuangxiaoyan.dubbo.CarImpl

二、JDK SPI 源码分析

我们可以看到 JDK SPI 的入口方法是 ServiceLoader.load() 方法,在 ServiceLoader.load() 方法中,首先会尝试获取当前使用的 ClassLoader(获取当前线程绑定的 ClassLoader,查找失败后使用 SystemClassLoader),然后调用 reload() 方法,调用关系如下图所示:

Dubbo——扩展(SPI)加载原理_配置文件_02

在 reload() 方法中,首先会清理 providers 缓存(LinkedHashMap 类型的集合),该缓存用来记录 ServiceLoader 创建的实现对象,其中 Key 为实现类的完整类名,Value 为实现类的对象。之后创建 LazyIterator 迭代器,用于读取 SPI 配置文件并实例化实现类对象。

ServiceLoader.reload() 方法的具体实现,如下所示:

// 缓存,用来缓存 ServiceLoader创建的实现对象 

private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); 

public void reload() { 

    providers.clear(); // 清空缓存 

    lookupIterator = new LazyIterator(service, loader); // 迭代器 

}

在前面的示例中,main() 方法中使用的迭代器底层就是调用了 ServiceLoader.LazyIterator 实现的。Iterator 接口有两个关键方法:hasNext() 方法和 next() 方法。这里的 LazyIterator 中的next() 方法最终调用的是其 nextService() 方法,hasNext() 方法最终调用的是 hasNextService() 方法,调用关系如下图所示:

Dubbo——扩展(SPI)加载原理_dubbo_03

首先来看 LazyIterator.hasNextService() 方法,该方法主要负责查找 META-INF/services 目录下的 SPI 配置文件,并进行遍历,大致实现如下所示:

private static final String PREFIX = "META-INF/services/"; 

Enumeration<URL> configs = null; 

Iterator<String> pending = null; 

String nextName = null; 

private boolean hasNextService() { 

    if (nextName != null) { 

        return true; 

    } 

    if (configs == null) { 

        // PREFIX前缀与服务接口的名称拼接起来,就是META-INF目录下定义的SPI配 

        // 置文件(即示例中的META-INF/services/com.xxx.Log) 

        String fullName = PREFIX + service.getName(); 

        // 加载配置文件 

        if (loader == null) 

            configs = ClassLoader.getSystemResources(fullName); 

        else 

            configs = loader.getResources(fullName); 

    } 

    // 按行SPI遍历配置文件的内容 

    while ((pending == null) || !pending.hasNext()) {  

        if (!configs.hasMoreElements()) { 

            return false; 

        } 

        // 解析配置文件 

        pending = parse(service, configs.nextElement());  

    } 

    nextName = pending.next(); // 更新 nextName字段 

    return true; 

}

在 hasNextService() 方法中完成 SPI 配置文件的解析之后,再来看 LazyIterator.nextService() 方法,该方法负责实例化 hasNextService() 方法读取到的实现类,其中会将实例化的对象放到 providers 集合中缓存起来,核心实现如下所示:

private S nextService() { 

    String cn = nextName; 

    nextName = null; 

    // 加载 nextName字段指定的类 

    Class<?> c = Class.forName(cn, false, loader); 

    if (!service.isAssignableFrom(c)) { // 检测类型 

        fail(service, "Provider " + cn  + " not a subtype"); 

    } 

    S p = service.cast(c.newInstance()); // 创建实现类的对象 

    providers.put(cn, p); // 将实现类名称以及相应实例对象添加到缓存 

    return p; 

}

以上就是在 main() 方法中使用的迭代器的底层实现。最后,我们再来看一下 main() 方法中使用ServiceLoader.iterator() 方法拿到的迭代器是如何实现的,这个迭代器是依赖 LazyIterator 实现的一个匿名内部类,核心实现如下:

public Iterator<S> iterator() { 

    return new Iterator<S>() { 

        // knownProviders用来迭代providers缓存 

        Iterator<Map.Entry<String,S>> knownProviders 

            = providers.entrySet().iterator(); 

        public boolean hasNext() { 

            // 先走查询缓存,缓存查询失败,再通过LazyIterator加载 

            if (knownProviders.hasNext())  

                return true; 

            return lookupIterator.hasNext(); 

        } 

        public S next() { 

            // 先走查询缓存,缓存查询失败,再通过 LazyIterator加载 

            if (knownProviders.hasNext()) 

                return knownProviders.next().getValue(); 

            return lookupIterator.next(); 

        } 

        // 省略remove()方法 

    }; 

}

三、Dubbo SPI机制原理

  • 扩展点:通过 SPI 机制查找并加载实现的接口(又称“扩展接口”)。前文示例中介绍的 Log 接口、com.mysql.cj.jdbc.Driver 接口,都是扩展点。
  • 扩展点实现:实现了扩展接口的实现类。

JDK SPI 在查找扩展实现类的过程中,需要遍历 SPI 配置文件中定义的所有实现类,该过程中会将这些实现类全部实例化。如果 SPI 配置文件中定义了多个实现类,而我们只需要使用其中一个实现类时,就会生成不必要的对象。例如,org.apache.dubbo.rpc.Protocol 接口有 InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol、ThriftProtocol 等多个实现,如果使用 JDK SPI,就会加载全部实现类,导致资源的浪费。

Dubbo SPI 不仅解决了上述资源浪费的问题,还对 SPI 配置文件扩展和修改。

首先,Dubbo 按照 SPI 配置文件的用途,将其分成了三类目录。

  • META-INF/services/ 目录:该目录下的 SPI 配置文件用来兼容 JDK SPI 。
  • META-INF/dubbo/ 目录:该目录用于存放用户自定义 SPI 配置文件。
  • META-INF/dubbo/internal/ 目录:该目录用于存放 Dubbo 内部使用的 SPI 配置文件。

然后,Dubbo 将 SPI 配置文件改成了 KV 格式,例如:

dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

其中 key 被称为扩展名(也就是 ExtensionName),当我们在为一个接口查找具体实现类时,可以指定扩展名来选择相应的扩展实现。例如,这里指定扩展名为 dubbo,Dubbo SPI 就知道我们要使用:org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类,只实例化这一个扩展实现即可,无须实例化 SPI 配置文件中的其他扩展实现类。

使用 KV 格式的 SPI 配置文件的另一个好处是:让我们更容易定位到问题。假设我们使用的一个扩展实现类所在的 jar 包没有引入到项目中,那么 Dubbo SPI 在抛出异常的时候,会携带该扩展名信息,而不是简单地提示扩展实现类无法加载。这些更加准确的异常信息降低了排查问题的难度,提高了排查问题的效率。

四、Dubbo SPI 源码分析

Dubbo 中某个接口被 @SPI注解修饰时,就表示该接口是扩展接口,前文示例中的 org.apache.dubbo.rpc.Protocol 接口就是一个扩展接口:

Dubbo——扩展(SPI)加载原理_配置文件_04

@SPI 注解的 value 值指定了默认的扩展名称,例如,在通过 Dubbo SPI 加载 Protocol 接口实现时,如果没有明确指定扩展名,则默认会将 @SPI 注解的 value 值作为扩展名,即加载 dubbo 这个扩展名对应的 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类,相关的 SPI 配置文件在 dubbo-rpc-dubbo 模块中,如下图所示:

Dubbo——扩展(SPI)加载原理_缓存_05

4.1 ExtensionLoader源码

该类是扩展加载器,这是dubbo实现SPI扩展机制等核心,几乎所有实现的逻辑都被封装在ExtensionLoader中。

1关于存放配置文件的路径变量:

private static final String SERVICES_DIRECTORY = "META-INF/services/";
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

"META-INF/services/"、"META-INF/dubbo/"、"META-INF/dubbo/internal/"三个值,都是dubbo寻找扩展实现类的配置文件存放路径,也就是我在上述(一)注解@SPI中讲到的以接口全限定名命名的配置文件存放的路径。区别在于"META-INF/services/"是dubbo为了兼容jdk的SPI扩展机制思想而设存在的,"META-INF/dubbo/internal/"是dubbo内部提供的扩展的配置文件路径,而"META-INF/dubbo/"是为了给用户自定义的扩展实现配置文件存放。

2扩展加载器集合,key为扩展接口,例如Protocol等:

private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

3扩展实现类集合,key为扩展实现类,value为扩展对象,例如key为

private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();

4以下属性都是cache开头的,都是出于性能和资源的优化,才做的缓存,读取扩展配置后,会先进行缓存,等到真正需要用到某个实现时,再对该实现类的对象进行初始化,然后对该对象也进行缓存。

//以下提到的扩展名就是在配置文件中的key值,类似于“dubbo”等

    //缓存的扩展名与拓展类映射,和cachedClasses的key和value对换。
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
    //缓存的扩展实现类集合
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
    //扩展名与加有@Activate的自动激活类的映射
    private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
    //缓存的扩展对象集合,key为扩展名,value为扩展对象
    //例如Protocol扩展,key为dubbo,value为DubboProcotol
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object
    //缓存的自适应( Adaptive )扩展对象,例如例如AdaptiveExtensionFactory类的对象
    private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
    //缓存的自适应扩展对象的类,例如AdaptiveExtensionFactory类
    private volatile Class<?> cachedAdaptiveClass = null;
    //缓存的默认扩展名,就是@SPI中设置的值
    private String cachedDefaultName;
    //创建cachedAdaptiveInstance异常
    private volatile Throwable createAdaptiveInstanceError;
    //拓展Wrapper实现类集合
    private Set<Class<?>> cachedWrapperClasses;
    //拓展名与加载对应拓展类发生的异常的映射
    private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

这里提到了Wrapper类的概念。那我就解释一下:Wrapper类也实现了扩展接口,但是Wrapper类的用途是ExtensionLoader 返回扩展点时,包装在真正的扩展点实现外,这实现了扩展点自动包装的特性。通俗点说,就是一个接口有很多的实现类,这些实现类会有一些公共的逻辑,如果在每个实现类写一遍这个公共逻辑,那么代码就会重复,所以增加了这个Wrapper类来包装,把公共逻辑写到Wrapper类中,有点类似AOP切面编程思想。

4.2 getExtensionLoader源码

根据扩展点接口来获得扩展加载器。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        //扩展点接口为空,抛出异常
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        //判断type是否是一个接口类
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        //判断是否为可扩展的接口
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                        ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        //从扩展加载器集合中取出扩展接口对应的扩展加载器
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);

        //如果为空,则创建该扩展接口的扩展加载器,并且添加到EXTENSION_LOADERS
        if (loader == null) {
           EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
                loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

4.3 getActivateExtension源码

public List<T> getActivateExtension(URL url, String key) {
        return getActivateExtension(url, key, null);
    }
    //弃用
    public List<T> getActivateExtension(URL url, String[] values) {
        return getActivateExtension(url, values, null);
    }

    public List<T> getActivateExtension(URL url, String key, String group) {
        String value = url.getParameter(key);
        // 获得符合自动激活条件的拓展对象数组
        return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
    }

    public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> exts = new ArrayList<T>();
        List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);

        //判断不存在配置 `"-name"` 。
        //例如,<dubbo:service filter="-default" /> ,代表移除所有默认过滤器。
        if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {

            //获得扩展实现类数组,把扩展实现类放到cachedClasses中
            getExtensionClasses();
            for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Activate activate = entry.getValue();
                //判断group值是否存在所有自动激活类中group组中,匹配分组
                if (isMatchGroup(group, activate.group())) {
                    //通过扩展名获得拓展对象
                    T ext = getExtension(name);
                    //不包含在自定义配置里。如果包含,会在下面的代码处理。
                    //判断是否配置移除。例如 <dubbo:service filter="-monitor" />,则 MonitorFilter 会被移除
                    //判断是否激活
                    if (!names.contains(name)
                            && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
                            && isActive(activate, url)) {
                        exts.add(ext);
                    }
                }
            }
            //排序
            Collections.sort(exts, ActivateComparator.COMPARATOR);
        }
        List<T> usrs = new ArrayList<T>();
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            //还是判断是否是被移除的配置
            if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
                    && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
                //在配置中把自定义的配置放在自动激活的扩展对象前面,可以让自定义的配置先加载
                //例如,<dubbo:service filter="demo,default,demo2" /> ,则 DemoFilter 就会放在默认的过滤器前面。
                if (Constants.DEFAULT_KEY.equals(name)) {
                    if (!usrs.isEmpty()) {
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
                    T ext = getExtension(name);
                    usrs.add(ext);
                }
            }
        }
        if (!usrs.isEmpty()) {
            exts.addAll(usrs);
        }
        return exts;
    }

可以看到getActivateExtension重载了四个方法,其实最终的实现都是在最后一个重载方法,因为自动激活类的条件可以分为无条件、只有value以及有group和value三种。

4.4 getExtension源码

@SuppressWarnings("unchecked")
    public T getExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        //查找默认的扩展实现,也就是@SPI中的默认值作为key
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        //缓存中获取对应的扩展对象
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(name);
        }
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    //通过扩展名创建接口实现类的对象
                    instance = createExtension(name);
                    //把创建的扩展对象放入缓存
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

4.5 getDefaultExtension源码

查找默认的扩展实现

public T getDefaultExtension() {
        //获得扩展接口的实现类数组
        getExtensionClasses();
        if (null == cachedDefaultName || cachedDefaultName.length() == 0
                || "true".equals(cachedDefaultName)) {
            return null;
        }
        //又重新去调用了getExtension
        return getExtension(cachedDefaultName);
    }

4.6 addExtension源码

public void addExtension(String name, Class<?> clazz) {
        getExtensionClasses(); // load classes

        //该类是否是接口的本身或子类
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Input type " +
                    clazz + "not implement Extension " + type);
        }
        //该类是否被激活
        if (clazz.isInterface()) {
            throw new IllegalStateException("Input type " +
                    clazz + "can not be interface!");
        }

        //判断是否为适配器
        if (!clazz.isAnnotationPresent(Adaptive.class)) {
            if (StringUtils.isBlank(name)) {
                throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
            }
            if (cachedClasses.get().containsKey(name)) {
                throw new IllegalStateException("Extension name " +
                        name + " already existed(Extension " + type + ")!");
            }

            //把扩展名和扩展接口的实现类放入缓存
            cachedNames.put(clazz, name);
            cachedClasses.get().put(name, clazz);
        } else {
            if (cachedAdaptiveClass != null) {
                throw new IllegalStateException("Adaptive Extension already existed(Extension " + type + ")!");
            }

            cachedAdaptiveClass = clazz;
        }
    }

4.7 getAdaptiveExtension源码

获得自适应扩展对象,也就是接口的适配器对象,思路就是先从缓存中取适配器类的对象,如果没有,则创建一个适配器对象,然后放入缓存,createAdaptiveExtension方法解释在后面给出

@SuppressWarnings("unchecked")
    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //创建适配器对象
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            } else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }

4.8 createExtension源码

通过扩展名创建扩展接口实现类的对象。

@SuppressWarnings("unchecked")
    private T createExtension(String name) {
        //获得扩展名对应的扩展实现类
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            //看缓存中是否有该类的对象
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //向对象中注入依赖的属性(自动装配)
            injectExtension(instance);
            //创建 Wrapper 扩展对象(自动包装)
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }

4.9 injectExtension源码

向创建的拓展注入其依赖的属性,思路就是是先通过反射获得类中的所有方法,然后找到set方法,找到需要依赖注入的属性,然后把对象注入进去。

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                //反射获得该类中所有的方法
                for (Method method : instance.getClass().getMethods()) {
                    //如果是set方法
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            //获得属性,比如StubProxyFactoryWrapper类中有Protocol protocol属性,
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            //获得属性值,比如Protocol对象,也可能是Bean对象
                            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);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

4.10 getExtensionClass源码

获得扩展名对应的扩展实现类

private Class<?> getExtensionClass(String name) {
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        if (name == null)
            throw new IllegalArgumentException("Extension name == null");
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null)
            throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!");
        return clazz;
    }

4.11 getExtensionClasses源码

获得扩展实现类数组,这里思路就是先从缓存中取,如果缓存为空,则从配置文件中读取扩展实现类。

private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    //从配置文件中,加载扩展实现类数组
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

4.12 loadExtensionClasses源码

private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            //@SPI内的默认值
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                //只允许有一个默认值
                if (names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names));
                }
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }

        //从配置文件中加载实现类数组
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadDirectory(extensionClasses, DUBBO_DIRECTORY);
        loadDirectory(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

前一部分逻辑是在把SPI注解中的默认值放到缓存中去,加载实现类数组的逻辑是在后面几行,关键的就是loadDirectory方法。并且这里可以看出去找配置文件访问的资源路径顺序。

4.13 loadDirectory源码

从一个配置文件中,加载拓展实现类数组,这边的思路是先获得完整的文件名,遍历每一个文件,在loadResource方法中去加载每个文件的内容。

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
        //拼接接口全限定名,得到完整的文件名
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            //获取ExtensionLoader类信息
            ClassLoader classLoader = findClassLoader();
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            if (urls != null) {
                //遍历文件
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }

4.14 loadResource源码

加载文件中的内容,该类的主要的逻辑就是读取里面的内容,跳过“#”注释的内容,根据配置文件中的key=value的形式去分割,然后去加载value对应的类。

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
            try {
                String line;
                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) {
                                //根据"="拆分key跟value
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                //加载扩展类
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            } finally {
                reader.close();
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

4.15 loadClass源码

根据配置文件中的value加载扩展类,重点关注该方法中兼容了jdk的SPI思想。因为jdk的SPI相关的配置文件中是xx.yyy.DemoFilter,并没有key,也就是没有扩展名的概念,所有为了兼容,通过xx.yyy.DemoFilter生成的扩展名为demo。

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        //该类是否实现扩展接口
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
        }
        //判断该类是否为扩展接口的适配器
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            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 if (isWrapperClass(clazz)) {
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {
            //通过反射获得构造器对象
            clazz.getConstructor();
            //未配置扩展名,自动生成,例如DemoFilter为 demo,主要用于兼容java SPI的配置。
            if (name == null || name.length() == 0) {
                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 (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());
                    }
                }
            }
        }
    }

4.16 createAdaptiveExtensionClass源码

创建适配器类,类似于dubbo动态生成的Transporter$Adpative这样的类。

private Class<?> createAdaptiveExtensionClass() {
        //创建动态生成的适配器类代码
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        //编译代码,返回该类
        return compiler.compile(code, classLoader);
    }

这个方法中就做了编译代码的逻辑,生成代码在createAdaptiveExtensionClassCode方法中,createAdaptiveExtensionClassCode方法由于过长,我不在这边列出,下面会给出github的网址,读者可自行查看相关的源码解析。createAdaptiveExtensionClassCode生成的代码逻辑可以对照上述讲的注解@Adaptive中的Transporter$Adpative类来看。

4.17 AdaptiveExtensionFactory源码

该类是ExtensionFactory的适配器类

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    //扩展对象的集合,默认的可以分为dubbo 的SPI中接口实现类对象或者Spring bean对象
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        //遍历所有支持的扩展名
        for (String name : loader.getSupportedExtensions()) {
            //扩展对象加入到集合中
            list.add(loader.getExtension(name));
        }
        //返回一个不可修改的集合
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            //通过扩展接口和扩展名获得扩展对象
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}
  1. factories是扩展对象的集合,当用户没有自己实现ExtensionFactory接口,则这个属性就只会有两种对象,分别是 SpiExtensionFactory 和 SpringExtensionFactory 。
  2. 构造器中是把所有支持的扩展名的扩展对象加入到集合
  3. 实现了接口的getExtension方法,通过接口和扩展名来获取扩展对象。

4.18 SpiExtensionFactory源码

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        //判断是否为接口,接口上是否有@SPI注解
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            //获得扩展加载器
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                //返回适配器类的对象
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}

4.19 ActivateComparator源码

该类在ExtensionLoader类的getActivateExtension方法中被运用到,作为自动激活拓展对象的排序器。

public class ActivateComparator implements Comparator<Object> {

    public static final Comparator<Object> COMPARATOR = new ActivateComparator();

    @Override
    public int compare(Object o1, Object o2) {
        //基本排序
        if (o1 == null && o2 == null) {
            return 0;
        }
        if (o1 == null) {
            return -1;
        }
        if (o2 == null) {
            return 1;
        }
        if (o1.equals(o2)) {
            return 0;
        }
        Activate a1 = o1.getClass().getAnnotation(Activate.class);
        Activate a2 = o2.getClass().getAnnotation(Activate.class);
        //使用Activate注解的 `after` 和 `before` 属性,排序
        if ((a1.before().length > 0 || a1.after().length > 0
                || a2.before().length > 0 || a2.after().length > 0)
                && o1.getClass().getInterfaces().length > 0
                && o1.getClass().getInterfaces()[0].isAnnotationPresent(SPI.class)) {
            ExtensionLoader<?> extensionLoader = ExtensionLoader.getExtensionLoader(o1.getClass().getInterfaces()[0]);
            if (a1.before().length > 0 || a1.after().length > 0) {
                String n2 = extensionLoader.getExtensionName(o2.getClass());
                for (String before : a1.before()) {
                    if (before.equals(n2)) {
                        return -1;
                    }
                }
                for (String after : a1.after()) {
                    if (after.equals(n2)) {
                        return 1;
                    }
                }
            }
            if (a2.before().length > 0 || a2.after().length > 0) {
                String n1 = extensionLoader.getExtensionName(o1.getClass());
                for (String before : a2.before()) {
                    if (before.equals(n1)) {
                        return 1;
                    }
                }
                for (String after : a2.after()) {
                    if (after.equals(n1)) {
                        return -1;
                    }
                }
            }
        }
        // 使用Activate注解的 `order` 属性,排序。
        int n1 = a1 == null ? 0 : a1.order();
        int n2 = a2 == null ? 0 : a2.order();
        // never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
        return n1 > n2 ? 1 : -1;
    }

}

博文参考

深入理解Apache Dubbo与实战.pdf

面试官问烂的Dubbo中SPI机制的源码解析_哔哩哔哩_bilibili

标签:Dubbo,name,扩展,SPI,String,new,null,加载
From: https://blog.51cto.com/u_13643065/6169226

相关文章

  • Dubbo——服务治理(dubbo-Admin)平台
    摘要本博文将介绍Dubbo最新的服务治理平台的实现原理。详细介绍服务治理中的路由规则、动态配置、访问控制、权重管理、负载均衡的实现原理。服务治理平台总体结构Dubbo有新旧两个服务治理平台,旧的服务治理平台在Dubbo2.6.0以后就从源码中被移除了,现在已经没有继续维护。新的服务......
  • dubbo 限制方法线程数_不可忽视的Dubbo线程池避坑指南
    转载:https://blog.csdn.net/weixin_39574140/article/details/110193195?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-110193195-blog-121764780.235%5Ev27%5Epc_relevant_recovery_v2&depth_1-ut......
  • dubbo线程池又被打爆(打满)了java.util.concurrent.RejectedExecutionException: Thread
    转载:https://blog.csdn.net/kevin_mails/article/details/121764780?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-121764780-blog-124236206.235%5Ev27%5Epc_relevant_recovery_v2&depth_1-utm_sourc......
  • 记几次 [线上环境] Dubbo 线程池占满原因分析(第三次:GC STW)
    转载:https://blog.csdn.net/wsmalltiger/article/details/124236206前言  某天晚上正在开开心心写代码,忽然收到了线上告警:dubbo线程池活跃线程数告警、应用错误日志告警、dubbo线程池队列长度告警;瞬间意识到要出大事情了,得赶紧定位到原因并解决问题,不然时间长了肯定会影响商......
  • Revit附加模块Add-in Manager里一次性加载所有.dll文件
    publicResultExecute(ExternalCommandDatacommandData,refstringmessage,ElementSetelements){GlobalRevitData.commandData=commandData;AppDomain.CurrentDomain.AssemblyResolve+=CurrentDomain_AssemblyResolve;//在Execute方法体内添加这行......
  • 前端项目首页加载速度及项目性能优化
    提升首屏的加载速度或项目整体优化,是前端性能优化中最重要的环节,接下来跟大家分享一些常规且有效的首屏优化建议及做法。一、路由懒加载SPA项目,一个路由对应一个页面,如果不做处理,项目打包后,会把所有页面打包成一个文件,当用户打开首页时,会一次性加载所有的资源,造成首页加载很慢,降......
  • 前端项目首页加载速度及项目性能优化
    提升首屏的加载速度或项目整体优化,是前端性能优化中最重要的环节,接下来跟大家分享一些常规且有效的首屏优化建议及做法。一、路由懒加载SPA项目,一个路由对应一个页面,如果不做处理,项目打包后,会把所有页面打包成一个文件,当用户打开首页时,会一次性加载所有的资源,造成首页加载很慢,降......
  • app直播源码,css预加载旋转动画 与 流光字体
    app直播源码,css预加载旋转动画与流光字体一、预加载旋转动画Html<viewclass="concentric_round"></view>​cssbody{}.concentric_round{width:200rpx;height:200rpx;position:relative;position:absolute;top:50%;left:50%;transform:translate(-50%,-100%);}.......
  • 深入理解 Java 中 SPI 机制
    vivo互联网技术微信公众号 作者:姜柱SPI(ServiceProviderInterface),是JDK内置的一种服务提供发现机制,本文由浅入深地介绍了JavaSPI机制。一、简介SPI(ServiceProviderInterface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如j......
  • Tomcat 应用中并行流带来的类加载问题
    vivo互联网技术微信公众号 作者:肖铭轩、王道环随着Java8的不断流行,越来越多的开发人员使用并行流(parallel)这一特性提升代码执行效率。但是,作者发现在Tomcat容器中使用并行流会出现动态加载类失败的情况,通过对比Tomcat多个版本的源码,结合并行流和JVM类加载机制的原理,成......