dubbo源碼分析1之SPI原理以及IOC和AOP原理

1.Dubbo SPI是什么

首先看下dubbo官網(wǎng)的描述:
SPI 全稱為 Service Provider Interface,是一種服務(wù)發(fā)現(xiàn)機(jī)制。SPI 的本質(zhì)是將接口實現(xiàn)類的全限定名配置在文件中,并由服務(wù)加載器讀取配置文件,加載實現(xiàn)類。這樣可以在運(yùn)行時,動態(tài)為接口替換實現(xiàn)類。正因此特性,我們可以很容易的通過 SPI 機(jī)制為我們的程序提供拓展功能。SPI 機(jī)制在第三方框架中也有所應(yīng)用,比如 Dubbo 就是通過 SPI 機(jī)制加載所有的組件。不過,Dubbo 并未使用 Java 原生的 SPI 機(jī)制,而是對其進(jìn)行了增強(qiáng),使其能夠更好的滿足需求。在 Dubbo 中,SPI 是一個非常重要的模塊?;?SPI,我們可以很容易的對 Dubbo 進(jìn)行拓展。如果大家想要學(xué)習(xí) Dubbo 的源碼,SPI 機(jī)制務(wù)必弄懂。

2.Java SPI和Dubbo SPI示例

參考官網(wǎng)的例子,下面以Robot(機(jī)器人)接口為例,有兩個實現(xiàn)類OptimusPrime(擎天柱) 和Bumblebee(大黃蜂)。

@SPI   //JavaSPI不需要這個注解,使用dubboSPI的時候需要
public interface Robot {
    void sayHello();
}

public class OptimusPrimeimplements Robot {
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Optimus Prime.");
    }
}

public class Bumblebeeimplements Robot {
    @Override
    public void sayHello() {
        System.out.println("Hello, I am Bumblebee.");
    }
}

Java SPI配置文件放在 META-INF/services 下,如下:

com.pigcoffe.spi.Bumblebee
com.pigcoffe.spi.OptimusPrime

Dubbo SPI的配置文件放在META-INF/dubbo下,如下:

optimusPrime = com.pigcoffe.spi.OptimusPrime
bumblebee = com.pigcoffe.spi.Bumblebee

文件名都是接口全類名。
Java SPI測試類:

package com.pigcoffe.spi;
import com.pigcoffe.spi.Robot;
import org.junit.Test;
import java.util.ServiceLoader;
public class JavaSPITest {
    @Test
    public void sayHello() throws Exception {
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("Java SPI");
        serviceLoader.forEach(Robot::sayHello);
    }
}

輸出:

Java SPI
Hello, I am Bumblebee.
Hello, I am Optimus Prime.

Dubbo SPI測試類,這里需要引入dubbo依賴

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.6.4</version>
        </dependency>
package com.pigcoffe.spi;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import org.junit.Test;
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();
    }
}

輸出:

Hello, I am Optimus Prime.
Hello, I am Bumblebee.

3.Dubbo SPI有什么優(yōu)勢,為什么不使用jdk自帶的SPI

從示例代碼中就能看出來一些區(qū)別了

  • JDK 標(biāo)準(zhǔn)的 SPI 會一次性實例化擴(kuò)展點所有實現(xiàn),如果有擴(kuò)展實現(xiàn)初始化很耗時,但如果沒用上也加載,會很浪費資源。
  • 如果擴(kuò)展點加載失敗,連擴(kuò)展點的名稱都拿不到了。比如:JDK 標(biāo)準(zhǔn)的 ScriptEngine,通過 getName() 獲取腳本類型的名稱,但如果 RubyScriptEngine 因為所依賴的 jruby.jar 不存在,導(dǎo)致 RubyScriptEngine 類加載失敗,這個失敗原因被吃掉了,和 ruby 對應(yīng)不起來,當(dāng)用戶執(zhí)行 ruby 腳本時,會報不支持 ruby,而不是真正失敗的原因。
  • JDK SPI不支持緩存,不支持默認(rèn)值,不支持IOC和AOP功能

4.源碼分析

從第一行代碼開始

ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");

從extensionLoader.getExtension("optimusPrime");跟進(jìn)代碼

    public T getExtension(String name) {
        if (name != null && name.length() != 0) {
            if ("true".equals(name)) {
                return this.getDefaultExtension();
            } else {
                Holder<Object> holder = (Holder)this.cachedInstances.get(name);
                if (holder == null) {
                    this.cachedInstances.putIfAbsent(name, new Holder());
                    holder = (Holder)this.cachedInstances.get(name);
                }

                Object instance = holder.get();
                if (instance == null) {
                    synchronized(holder) {
                        instance = holder.get();
                        if (instance == null) {
                            //緩存中沒有,新創(chuàng)建
                            instance = this.createExtension(name);
                            holder.set(instance);
                        }
                    }
                }

                return instance;
            }
        } else {
            throw new IllegalArgumentException("Extension name == null");
        }
    }

邏輯比較簡單,就是先從緩存獲取對象,如果沒有就創(chuàng)建this.createExtension(name)并加入緩存
下面是createExtension(String name) 方法

    private T createExtension(String name) {
//從配置文件中加載所有的拓展類,可得到“配置項名稱”到“配置類”的映射關(guān)系表
        Class<?> clazz = (Class)this.getExtensionClasses().get(name);
        if (clazz == null) {
            throw this.findException(name);
        } else {
            try {
                T instance = EXTENSION_INSTANCES.get(clazz);
                if (instance == null) {
                    EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                    instance = EXTENSION_INSTANCES.get(clazz);
                }
                //dubbo IOC
                this.injectExtension(instance);
                Set<Class<?>> wrapperClasses = this.cachedWrapperClasses;
                Class wrapperClass;
                if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                    for(Iterator i$ = wrapperClasses.iterator(); i$.hasNext(); 
//dubbo AOP
instance = this.injectExtension(wrapperClass.getConstructor(this.type).newInstance(instance))) {
                        wrapperClass = (Class)i$.next();
                    }
                }

                return instance;
            } catch (Throwable var7) {
                throw new IllegalStateException("Extension instance(name: " + name + ", class: " + this.type + ")  could not be instantiated: " + var7.getMessage(), var7);
            }
        }
    }

這段代碼首先是getExtensionClasses()從配置文件中加載所有的拓展類,可得到“配置項名稱”到“配置類”的映射關(guān)系表,
ExtensionLoader#getExtensionClasses()-->loadExtensionClasses()

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/";
  private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            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;
    }

這里體現(xiàn)出dubbo SPI配置文件目錄了,這里除了META-INF/dubbo/,還有META-INF/dubbo/internal/,internal目錄下主要放的都是dubbo框架自己的擴(kuò)展,用戶新建的擴(kuò)展一般放在dubbo目錄下,上面示例的配置文件移動到META-INF/dubbo/internal/目錄下也可以正常運(yùn)行。

然后涉及到dubbo的IOC和AOP實現(xiàn),先進(jìn)入this.injectExtension(instance) IOC的實現(xiàn)

    private T injectExtension(T instance) {
        try {
            if (this.objectFactory != null) {
                Method[] arr$ = instance.getClass().getMethods();
                int len$ = arr$.length;

                for(int i$ = 0; i$ < len$; ++i$) {
                    Method method = arr$[i$];
                    if (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) {
                        Class pt = method.getParameterTypes()[0];

                        try {
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = this.objectFactory.getExtension(pt, property);
                            if (object != null) {
                                method.invoke(instance, object);
                            }
                        } catch (Exception var9) {
                            logger.error("fail to inject via method " + method.getName() + " of interface " + this.type.getName() + ": " + var9.getMessage(), var9);
                        }
                    }
                }
            }
        } catch (Exception var10) {
            logger.error(var10.getMessage(), var10);
        }

        return instance;
    }

這里通過反射獲取方法遍歷找到setter方法注入依賴的對象,依賴的對象通過objectFactory獲取,有兩種SpiExtensionFactory和SpringExtensionFactory,就是為了獲取一個對象

AOP的實現(xiàn)就在這一行代碼:

instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));

這里的wapperClass實際上就是一個 擁有構(gòu)造函數(shù)中參數(shù)是目標(biāo)接口的 類,這里使用了裝飾者設(shè)計模式,以上面的Robert接口為例,新建一個RobotCheckWapper類實現(xiàn)aop功能,再在機(jī)器人say helle之前檢查是否攜帶武器,如下

package com.pigcoffe.spi;

import com.alibaba.dubbo.common.extension.Adaptive;


public class RobotCheckWapper implements Robot {
    private final Robot robot;
    public RobotCheckWapper(Robot robot){
        this.robot = robot;
    }

    @Override
    public void sayHello() {
        System.out.println("--->say hello之前檢查下是否帶有武器 ---->");
        robot.sayHello();
    }
}

在配置文件增加一行:
optimusPrime = com.pigcoffe.spi.OptimusPrime
bumblebee = com.pigcoffe.spi.Bumblebee
bumblebeeWapper = com.pigcoffe.spi.RobotCheckWapper

運(yùn)行之前的DubboSPITest測試類打印如下:

--->say hello之前檢查下是否帶有武器 ---->
Hello, I am Optimus Prime.
--->say hello之前檢查下是否帶有武器 ---->
Hello, I am Bumblebee.

是不是以及完成了AOP的功能,換成日志記錄或者時間統(tǒng)計也就類似了。

5.總結(jié)

本文介紹了什么是SPI,什么是Dubbo SPI,以及基本使用;
對比了dubbo SPI相比jdk SPI有什么優(yōu)點;
簡單介紹了dubbo IOC和AOP的實現(xiàn)原理;
下一篇要介紹dubbo的擴(kuò)展機(jī)制@Adaptie注解還有@Active注解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容