dubbo源码---SPI

  首先要了解SPI是什么,他的全称是“服务发现机制”,如何理解呢?其实就是用来解耦的,他把接口和实现类,进行分离,把他们的连接部分,放在了一个文件里面,具体的代码内容,

请参考:https://www.jianshu.com/p/3a3edbcd8f24

dubbo用到了很多SPI机制,常用场景在,数据库,日志系统中,我们都需要spi机制,实现可插拔,如何理解呢?如果我们在中心系统中写死了使用哪个实现类,那么更换场景的时候,就需要手动去修改代码,耦合性太高,而我们把实现哪个类放在插入的插件里面,那么中心系统自动去读取插进来的是哪个,然回就可以完美自动的结合了!

dubbo在jdk基础上,进行了该进,原来的jdk实现的SPI,一次性加载全部文件内容中的实现类,资源浪费,时间浪费,而且无法做到字段的注入,管理等等。而dubbo在此基础上,实现了缓冲,扩展类的分类,解决按需实例化。而且还可以注入,实现IOC,DI.dubbo在spring基础上进行操作。

看一下具体的代码:

首先是SPI测试代码:官方给出的

public class DubboSPITest {
 @Test public void sayHello() throws Exception { 
  ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class); 
  Robot optimusPrime = extensionLoader.getExtension("optimusPrime"); 
  optimusPrime.sayHello(); 
  Robot bumblebee = extensionLoader.getExtension("bumblebee"); 
  bumblebee.sayHello(); 
 } 
}

可以说,dubbo的spi的实现,大部分都是在ExtensionLoader中实现的,他里面有很多字段,有静态的,动态的等等,一个ExtensionLoader对应一个[接口类],里面存放该接口实现的Class对象,实例等等。

我们先来看ExtensionLoader的获取,在该类里面有一个静态量,记录了哪个[接口类]对应哪个ExtensionLoader,我们需要先从里面get,找到不我们new一个放进去,再返回:

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
  //。。。。异常处理。。。。。
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); //不存在则new一个返回。
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

好的,我们拿到了接口对应的ExtensionLoader,现在开始获取我们想要的实现类,把实现类的名称传进去,

public T getExtension(String name) {
    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(); //获取T值
    if (instance == null) {  
        synchronized (holder) {  
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name); //创建扩展类对象 【入】
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

很明显,这里主要分为三种情况,

  1.获取该接口的默认实现类(前提是传入的参数为 true)

  2.从缓冲中获取指定的 实现类

  3.缓冲中不存在,我们需要手动创建实现类(这个最为复杂)

默认实现类,什么时候大概会用到呢?前面我们说到dubbo实现的spi可以进行自动注入,如果被注入的对象,是一个扩展点,那么我们使用哪个实现类去注入呢?这个时候就是[默认实现类]登场,还有的时候,该接口只有唯一实现类,那么我们也可以当作默认实现类来使用!,他的获取,我们之后再说,我们先说2,3。

  2.缓冲获取[实现类]

private Holder<Object> getOrCreateHolder(String name) {
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<>());
        holder = cachedInstances.get(name);
    }
    return holder;
}

这里登场一个Holder类,他其实很简单,里面就是一个成员T,get/set方法,也就是目标对象持有者的意思。

我们重缓冲实例中get 目标类,通过它的名字,我们看一下这个缓冲实例集合:

private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();

就是一个map,然后holder封装了我们的实现类,这里进行获取,取不到我们new一个空的holder,放进去,然回进行手动实例化,再补充该holder.

3.手动创建实例类

   这里就稍显复杂了,大体思路:判断缓冲中存在Class对象实例吗(我们的dubbo-spi采用两层缓冲,Class对象+instance)

   如何Class对象都不存在,那么我们就需要到指定的资源文件下遍历文件了,然回通过类名字+ClassLoader找到Class文件,放到内存中,然回分析Class,里面的注解。(这里分为三种情况,稍后再说),然回用Class对象进行实例化,再放入holder中。

首先看:

private T createExtension(String name) {
    //1。获取Class对象
    Class<?> clazz = getExtensionClasses().get(name); //获得 实例类的Class对象
    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);
//2. 遍历是否需要注入字段。
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (CollectionUtils.isNotEmpty(wrapperClasses)) {
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        initExtension(instance);
        return instance;
    } 
}

看第一个方法,获取Class对象,这个其实是存放在一个map中,把name作为key,Class对象作为value(其实关于普通扩展点来说,它有很多个集合保存信息:

    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>(); //name 扩展类Class对象
    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();//存放 name--@Active注解对象
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();

我们进入3.1获取CachedClasses中的T

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;
}

我们先获取当前是否存在 name-Class集合,第一get的时候,肯定是不存在的,所以需要执行

3.1.1 loadExtensionClasses()进行加载classes集合:

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;
}

然后看见一个没有返回值的方法,

3.1.1.1 cacheDefaultExtensionName()

private void cacheDefaultExtensionName() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class); //SPI是一个注解。type应该是我们传入的Class,但是目前没有找到set在哪里,
    if (defaultAnnotation == null) { //检测是否实现了@SPI注解,
        return;
    }

    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];
        }
    }
}

我们这里就判断传入的接口,的@SPI中是否存在value属性值,它代表的就是我们的默认实现类,也就是最上方的第一点。有的话,我们就需要把当前类ExtensionLoader的字段赋值,保存起来,方便以后使用。

3.1.1.2loadDirectory()           -----------------------------------  前方高能 --------------------------------------------------

这个方法就是加载 扩展点的 开始了!

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                           boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
    String fileName = dir + type; //生成全路径
    try {
        Enumeration<java.net.URL> urls = null;
        ClassLoader classLoader = findClassLoader();

        // try to load from ExtensionLoader's ClassLoader first   一般是false
        if (extensionLoaderClassLoaderFirst) {
            ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
            if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                urls = extensionLoaderClassLoader.getResources(fileName);
            }
        }
        //走这里,肯定是null
        if (urls == null || !urls.hasMoreElements()) {
            if (classLoader != null) {
//3.1.1.2.1
                urls = classLoader.getResources(fileName); //通过类加载器区加载。获得URL
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
        }

        if (urls != null) {  //不为空,要进入
            while (urls.hasMoreElements()) {
                java.net.URL resourceURL = urls.nextElement();
                loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages); //【入】
            }
        }
    } 
}

该方法体内,首先就是构造资源的绝对路径,然后装载,我们执行

3.1.1.2.1classLoader.getResources(fileName)方法

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;
            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('=');  //key-value 分割
                        if (i > 0) {
                            name = line.substring(0, i).trim();
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
// 3.1.1.2.1.1
                            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden); //载入
                        }
                    }
                }
            }
        }
    } 
}

开始读取内容了,一行一行的读取,一行一行的解析,把name和实现类的权限名取出来,读取Class信息

3.1.1.2.1.1 loadClass()

//载入Class信息,更加实现类的注解类型,放到对应的缓冲中
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                       boolean overridden) throws NoSuchMethodException {
    //三种 情况,
    if (clazz.isAnnotationPresent(Adaptive.class)) { //自适应扩展点
        cacheAdaptiveClass(clazz, overridden); //--1
    } else if (isWrapperClass(clazz)) { //包装扩展点
        cacheWrapperClass(clazz);  //--2
    } else { //普通的
        clazz.getConstructor();
        if (StringUtils.isEmpty(name)) {
            name = findAnnotationName(clazz); //通过Class对象,形成一个name(可能配置了,没有就默认创建)
            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]); //--3
            for (String n : names) {     //两个缓冲
                cacheName(clazz, n);
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

这里就会对Class进行分析,注解分析,区分 普通扩展类,包装扩展类,自适应扩展类 三种扩展类,然后缓冲到不同的集合中。

包装扩展类:构造方法中又把 扩展点接口 作为参数

包装类的判断:

    private boolean isWrapperClass(Class<?> clazz) {
        try {
            clazz.getConstructor(type);
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

自适应扩展类:配有@Adaptive注解,接口有很多实现类,实现哪个不明确,配置注解URl来确定使用

普通扩展类:查找有没有传入name(配置文件中以key-value形式存放的时候,key就是name,如果没有传入name,根据Class的名字生成一个Name)

然后把这个name和Class对象,放入了三个缓冲中,上面我们有提到的;到此我们就实现了Class对象的集合。

回到3.createExtension(String name)中:

拿到了Class对象,我们就可以实例化了,给出代码片段

  T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }

Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。整个过程对应的代码如下:

(注意ObjectFacotry在ExtensionLoader构造函数中就创建了!)

objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

代码的含义就是获取接口ExtensionFactory接口扩展类中的 自适应扩展类 ,观察它的实现类,发现和spring有关,那么就整合起来了!

3.2 injectExtension

//IOC 利用set()方法,进行注入,这里要遍历 类中的所有方法,观察是否具有set特性
    private T injectExtension(T instance) {

        if (objectFactory == null) {
            return instance;
        }
        try {
            for (Method method : instance.getClass().getMethods()) {
                // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
                if (!isSetter(method)) {
                    continue;
                }
                if (method.getAnnotation(DisableInject.class) != null) {
                    continue;
                }
                Class<?> pt = method.getParameterTypes()[0]; 
                if (ReflectUtils.isPrimitives(pt)) {
                    continue;
                }
                //复合条件,注入
                try {
                    String property = getSetterProperty(method); //获得参数名字
                    Object object = objectFactory.getExtension(pt, property); //Class对象+名字,获得实例
                    if (object != null) {//实例存在就执行
                        method.invoke(instance, object);
                    }
                }
            }
        } 
        return instance;
    }

到此,我们就通过dubbo的SPI,获得指定的实例类了!

可以参考官网:http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值