一文帶你徹底了解Dubbo的SPI機(jī)制

文章目錄

Java SPI使用

Dubbo為何自己實(shí)現(xiàn)一套SPI

Dubbo SPI初體驗(yàn)

SPI

IOC

Aop

什么是包裝類?

AOP增強(qiáng)

@Adaptive

一些需要注意的地方

@Activate

結(jié)尾

在分布式系統(tǒng)中服務(wù)的調(diào)用,就要涉及到RPC。而提起RPC,想到最多的就是dubbo。了解dubbo的工作原理,有助于我們更好的使用它。

打開(kāi)下載的dubbo工程,我發(fā)現(xiàn)在dubbo的各個(gè)子模塊,有很多這樣的類似代碼:ExtensionLoader.getExtensionLoader(xxx.class),經(jīng)過(guò)我一番百度后發(fā)現(xiàn)他們指向一個(gè)名次:SPI。官網(wǎng)中表示Dubbo采用微內(nèi)核+SPI,使得有特殊需求的接入方可以自定義擴(kuò)展,做定制的二次開(kāi)發(fā)。好像解釋了什么,但是又好像還不清楚它是干什么的,不過(guò)不要急,接下來(lái)我們就從例子使用到dubbo的源碼來(lái)剖析spi的神秘面紗。

Java SPI使用

在了解dubbo spi之前,我們就不得不提一下Java SPI。SPI的全稱是service provider interface,起初是提供給廠商做插件開(kāi)發(fā)的 ,它使用了策略模式, 一個(gè)接口多種實(shí)現(xiàn)。我們只聲明接口, 具體的實(shí)現(xiàn)并不在程序中直接確定, 而是由程序之外的配置掌控 。啥意思呢?我們搞個(gè)例子就明白了:

//定義一個(gè)接口

public interface DoWork {
    void doWork();
}

//兩個(gè)實(shí)現(xiàn)類
public class WriteBug implements DoWork {
    @Override
    public void doWork() {
        System.out.println("寫(xiě)bug");
    }
}
public class WriteCode implements DoWork {

    @Override
    public void doWork() {
        System.out.println("寫(xiě)程序");
    }
}

在META-INF.services目錄下創(chuàng)建一個(gè)文件,文件名即為接口的全路徑名:com.alibaba.dubbo.demo.provider.JavaSPI.DoWork:這個(gè)樣子;

文件中的內(nèi)容:

com.alibaba.dubbo.demo.provider.JavaSPI.WriteBug
com.alibaba.dubbo.demo.provider.JavaSPI.WriteCode

準(zhǔn)備工作已經(jīng)做完了,接下來(lái)測(cè)試一下效果如何:

  public static void main(String[] s){
        //獲取到DoWork的所有實(shí)現(xiàn)
        ServiceLoader<DoWork> serviceServiceLoader = ServiceLoader.load(DoWork.class);
        for (DoWork doWork : serviceServiceLoader){
                doWork.doWork();
        }
    }

運(yùn)行結(jié)果:

寫(xiě)bug
寫(xiě)程序

可以看到執(zhí)行了全部實(shí)現(xiàn)類的方法,因此我們可以理解為spi實(shí)際上就是在文件中記錄接口都有哪些實(shí)現(xiàn)類,然后根據(jù)接口名來(lái)實(shí)例化它的實(shí)現(xiàn)類。

Dubbo為何自己實(shí)現(xiàn)一套SPI

但是實(shí)際上dubbo并沒(méi)有采用Java的SPI,而是自己實(shí)現(xiàn)了一套SPI機(jī)制。既然有Java的SPI,為什么dubbo不用呢?

我們觀察上面的例子,可以發(fā)現(xiàn)其實(shí)Java SPI實(shí)際上是將接口所有的實(shí)現(xiàn)類都加載出來(lái)了,如果項(xiàng)目過(guò)大,那么會(huì)加載全部的實(shí)現(xiàn)類,那肯定會(huì)有一些實(shí)現(xiàn)類實(shí)現(xiàn)類沒(méi)有用上,這樣就造成了浪費(fèi)。

另一方面,Java的SPI功能也比較單一,dubbo的spi在此基礎(chǔ)上還實(shí)現(xiàn)了ioc、aop等功能,這些我會(huì)在下面從源碼的角度來(lái)分析這些功能。

Dubbo SPI初體驗(yàn)

既然我們上面看了Java的SPI如何使用,那這個(gè)dubbo的spi我們也是需要看看的。

在dubbo中,約定文件是放在以下三個(gè)目錄中:

META-INF/services/

META-INF/dubbo/

META-INF/dubbo/internal/

大部分還是比較類似的,先定義一個(gè)接口、兩個(gè)實(shí)現(xiàn)類,要注意接口上面需要加@SPI注解的。

//注意加上@SPI注解
@SPI
public interface DoWork {
    void doWork();
}

public class WriteBug implements DoWork {

    @Override
    public void doWork() {
        System.out.println("寫(xiě)bug");
    }
}
public class WriteCode implements  DoWork{


    @Override
    public void doWork() {
        System.out.println("寫(xiě)程序");
    }
}

dubbo spi文件內(nèi)容與java spi文件內(nèi)容略微不太相同,可以理解為key-value的形式,value就是實(shí)現(xiàn)類的全路徑名,這個(gè)key呢可以理解為這個(gè)類名的簡(jiǎn)稱,這個(gè)要記得,下面會(huì)用得到:

bug=com.alibaba.dubbo.demo.provider.DubboSPI.WriteBug
code=com.alibaba.dubbo.demo.provider.DubboSPI.WriteCode

public static void main(String[] args) {        
    ExtensionLoader<DoWork> extensionLoader = ExtensionLoader.getExtensionLoader(DoWork.class);  
    //這個(gè)code就是上面說(shuō)的"簡(jiǎn)稱"       
      DoWork code = extensionLoader.getExtension("code");        
      code.doWork();       
      DoWork bug = extensionLoader.getExtension("bug");        
      bug.doWork();    
}

ExtensionLoader類包含了整個(gè)SPI的核心方法,包括像下面的獲取@Adaptive、@Active注解的信息等,都是在這個(gè)類中實(shí)現(xiàn)的。

getExtensionLoader方法可以獲得對(duì)應(yīng)接口的Extension加載器,這個(gè)方法也是比較簡(jiǎn)單的;

@SuppressWarnings("unchecked")
    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)) {
            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) {
            //緩存中沒(méi)有的話,就去new一個(gè)放到這個(gè)map中,然后再獲取返回
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

其實(shí)就是從緩存中拿,如果緩存中有,那么就取緩存中的,緩存中沒(méi)有,那么就new一個(gè)放進(jìn)去。

ok,現(xiàn)在獲取到了擴(kuò)展實(shí)現(xiàn)類加載器,接下來(lái)就看如何通過(guò)執(zhí)行g(shù)etExtension(很重要,可以標(biāo)記為五星重要程度,后面還會(huì)用到的)來(lái)獲取到對(duì)應(yīng)的實(shí)例。

public T getExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        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;
    }

代碼行數(shù)雖然比較多,實(shí)際上都是從緩存中取,看是否有值,沒(méi)有值的話就去創(chuàng)建。因?yàn)槲覀儸F(xiàn)在是首次加載,那肯定是沒(méi)值的,所以就要到這個(gè)createExtension方法中:

private T createExtension(String name) {
        Class<?> clazz = getExtensionClasses().get(name);//獲取名稱對(duì)應(yīng)的class
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                //通過(guò)反射來(lái)創(chuàng)建實(shí)例
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);//依賴注入
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                for (Class<?> wrapperClass : wrapperClasses) {//對(duì)于那些wrapper類型的,進(jìn)行包裝一下
                    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);
        }
    }

這里的核心方法就兩個(gè):getExtensionClasses、injectExtension。這兩個(gè)方法在后面的@Adaptive、@Active注解中都會(huì)用到。

首先是這個(gè)getExtensionClasses(非常重要,切記記得這個(gè)方法,后面還會(huì)用到他的)方法:

private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {//又是經(jīng)典的雙檢鎖。
                classes = cachedClasses.get();
                if (classes == null) {
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

可以看到他其實(shí)也是這個(gè)從緩存中拿,沒(méi)有的話就創(chuàng)建,核心方法就是這個(gè)loadExtensionClasses方法。

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

還記得最開(kāi)始的時(shí)候說(shuō)的dubbo會(huì)掃描三個(gè)目錄下的文件么?沒(méi)錯(cuò)就是這三個(gè)loadDirectory方法里面的目錄。

最終調(diào)用的loadClass加載方法:

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)) {//如果是自適應(yīng)的話,只會(huì)有一個(gè)
            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中,這個(gè)是可以有多個(gè)的。
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {//說(shuō)明既不是@Adaptive,也不是裝飾類,他只是普通的或者是active。
            clazz.getConstructor();//如果沒(méi)有默認(rèn)構(gòu)造,那么就報(bào)錯(cuò)
            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);//設(shè)置class與名字的映射
                    }
                    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());
                    }
                }
            }
        }
    }






在這里我給大家總結(jié)一下這個(gè)方法:先看這個(gè)類上面有沒(méi)有@Adaptive注解。如果有@Adaptive注解,那么就將它緩存起來(lái),原則上一個(gè)接口只允許有一個(gè)@Adaptive,這是為啥呢?我們可以看上面的代碼,如果有多個(gè)@Adaptive注解的話,他是會(huì)拋出異常的。

然后再看這個(gè)接口的實(shí)現(xiàn)類是否有以Wrapper結(jié)尾的實(shí)現(xiàn)類,比如Protocol的其中一個(gè)實(shí)現(xiàn)類:ProtocolFilterWrapper,如果是的話,就會(huì)放到一個(gè)set集合中。如果這兩種都不是的話,那么就是普通的擴(kuò)展類,將他們存到一個(gè)map中返回就可以了。在存到這個(gè)map的時(shí)候,是以類對(duì)應(yīng)的簡(jiǎn)寫(xiě)為key,類的路徑為value。

IOC

我們可以先看下這個(gè)injectExtension中有這么一段方法:

    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                for (Method method : instance.getClass().getMethods()) {
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
  /

可以看到,它是先獲取到這個(gè)實(shí)例對(duì)應(yīng)類中的全部方法,然后呢,依次循環(huán)遍歷,對(duì)于那些以set開(kāi)頭、只有一個(gè)參數(shù)、并且為public的方法,我們就認(rèn)為這是需要對(duì)這個(gè)實(shí)例進(jìn)行屬性注入的部分。

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

這個(gè)其實(shí)就是要獲取到需要注入的屬性,就是將第四個(gè)字符設(shè)置為小寫(xiě),后面的不動(dòng)進(jìn)行分割。比如setUserName,分割完畢后就是userName,這樣有屬性,又有類路徑,就可以通過(guò)反射對(duì)原先的實(shí)例進(jìn)行屬性注入,也就完成了依賴注入。

Aop

這里分為兩部分:判斷是否為包裝類以及對(duì)目標(biāo)對(duì)象進(jìn)行增強(qiáng)。

什么是包裝類?

舉個(gè)簡(jiǎn)單的栗子:

public class Son implements Parent {
    private Parent parent;


    public Son(Parent parent) {
        this.parent = parent;
    }
}

在dubbo中是這個(gè)樣子的:



也就是實(shí)現(xiàn)類的構(gòu)造方法的參數(shù),為他自己所實(shí)現(xiàn)的接口。而且在dubbo中。包裝類都是以Wrapper結(jié)尾的。

緩存包裝類

緩存包裝類是在ExtensionLoader.loadClass中:

else if (isWrapperClass(clazz)) {//包裝類的話,可以放到set中,這個(gè)是可以有多個(gè)的。

            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        }

private boolean isWrapperClass(Class<?> clazz) {
        try {
            Constructor<?> constructor = clazz.getConstructor(type);//看類的構(gòu)造參數(shù)中,有沒(méi)有對(duì)應(yīng)接口的參數(shù),如果有,就說(shuō)明是包裝類
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

通過(guò)遍歷所有的實(shí)現(xiàn)類,找出這些包裝類,然后放入緩存中。

AOP增強(qiáng)

@SuppressWarnings("unchecked")
    private T createExtension(String name) {
        Map<String, Class<?>> extensionClasses = getExtensionClasses();
        Class<?> clazz = extensionClasses.get(name);//獲取名稱對(duì)應(yīng)的class
        if (clazz == null) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                //通過(guò)反射來(lái)創(chuàng)建實(shí)例
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);//依賴注入
            //獲取包裝類集合
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                //遍歷
                for (Class<?> wrapperClass : wrapperClasses) {
                    //對(duì)返回的對(duì)象進(jìn)行包裝增強(qiáng)
                    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);
        }
    }

@Adaptive

@SPI注解那里存的是默認(rèn)值,這個(gè)會(huì)在動(dòng)態(tài)生成編譯的類中,會(huì)設(shè)置默認(rèn)值

從上面的例子中可以看到,通過(guò)SPi,我們可以每次只加載對(duì)應(yīng)接口的實(shí)現(xiàn)類,這樣可以減少加載全部實(shí)現(xiàn)類帶來(lái)的開(kāi)銷。但是我又有一個(gè)問(wèn)題了,我不想在啟動(dòng)項(xiàng)目的時(shí)候加載這些擴(kuò)展類,而是希望在運(yùn)行時(shí)根據(jù)參數(shù),來(lái)動(dòng)態(tài)加載擴(kuò)展類,這個(gè)該怎么做呢?

此時(shí)@Adaptive注解就派上用場(chǎng)了。

@Adaptive有兩種用法:一種是添加到類上,另一種則是添加到方法上。

如果添加到類上,表示該類是接口的適配器。但是實(shí)際上,注解添加到類上的是很少的,大部分都是添加到方法上的。目前只有AdaptiveExtensionFactory和AdaptiveCompiler是添加到類上的。

在這里我們就以這個(gè)AdaptiveExtensionFactory為例,來(lái)看下這個(gè):





AdaptiveExtensionFactory的類上有這個(gè)@Adaptive注解,在執(zhí)行構(gòu)造方法的時(shí)候,就會(huì)將ExtensionLoader對(duì)應(yīng)的實(shí)現(xiàn)類加載到一個(gè)List中緩存起來(lái),這樣的話在getExtension的時(shí)候,傳入要取的class,key,就可以直接從這個(gè)list中取出對(duì)應(yīng)的值。

2.事實(shí)上大部分情況下都是注解到方法上的,他會(huì)根據(jù)接口的信息,來(lái)動(dòng)態(tài)拼接成一個(gè)代理類。為了更清楚了解@Adaptive的實(shí)現(xiàn),我們就從源碼的角度,以RegistryFactory為例子來(lái)看下這個(gè)過(guò)程到底是啥樣的:



方法起始于ExtensionLoader.getAdaptiveExtension:

public T getAdaptiveExtension() {
       //從緩存中拿,沒(méi)有的話就創(chuàng)建
       instance = createAdaptiveExtension();
      cachedAdaptiveInstance.set(instance);
    
        return (T) instance;
    }

 private T createAdaptiveExtension() {
     //主要方法為getAdaptiveExtensionClass
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    }

 private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();//通過(guò)這個(gè)方法來(lái)獲取到Adaptive注解的類。
        if (cachedAdaptiveClass != null) {//如果有,那么就說(shuō)明有@Adaptive注解添加到類上了。
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();//沒(méi)有的話就創(chuàng)建。注解在方法上的話,那么就需要進(jìn)行編譯。
    }






這里我省略了一些無(wú)關(guān)緊要的代碼,保留了核心代碼,getExtensionClasses這個(gè)方法是不是感覺(jué)很熟悉呢?沒(méi)錯(cuò),在上面我們也介紹過(guò)他的源碼了,這里就不多說(shuō)了,直接ctrl+f搜索就可以了。

因?yàn)檫@個(gè)接口上的@Adaptive是在接口上的,所以cachedAdaptiveClass是為空的,這里就需要執(zhí)行createAdaptiveExtensionClass去創(chuàng)建實(shí)現(xiàn)類了。

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

這個(gè)code里面就是包含了代理實(shí)現(xiàn)類的所有信息,他長(zhǎng)這個(gè)樣子:



看起來(lái)不太直接,而且也沒(méi)辦法調(diào)試,我們可以將他復(fù)制下來(lái),ctrl+alt+l進(jìn)行格式化一下,這樣在下次啟動(dòng)程序的時(shí)候,斷點(diǎn)就可以進(jìn)入這個(gè)類中了。



進(jìn)入這個(gè)方法中,根據(jù)傳入的URL中的參數(shù)值,來(lái)調(diào)用getExtension方法,最終得到需要的擴(kuò)展類。

PS:在dubbo3.0的時(shí)候項(xiàng)目中就已經(jīng)放了這些$Adaptive的類了。

一些需要注意的地方

當(dāng)@Adaptive添加到方法上的時(shí)候,要保證方法中的參數(shù)至少有一個(gè)為URL。

但是我們發(fā)現(xiàn)有的方法中的參數(shù),只有一個(gè)invoker,比如這樣的:



這個(gè)方法中就沒(méi)有URL,那這是咋回事?

通過(guò)查看拼接生成的代理類,我們發(fā)現(xiàn),通過(guò)Invoker是可以得到URL的:



所以從某種意義上來(lái)說(shuō)的話,填入了Invoker也相當(dāng)于填入了URL。

關(guān)于SPI注解與@Adaptive注解中的值。

SPI注解后面的值是默認(rèn)的值,當(dāng)URL中的值為空的時(shí)候,就使用默認(rèn)的值,比如上面圖片中的:

String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());

這個(gè)dubbo就是默認(rèn)值,也是@SPI中的值。

而@Adaptive添加到方法上時(shí),后面的參數(shù)表示在加載時(shí)優(yōu)先使用的值。

@Activate

這個(gè)注解是擴(kuò)展點(diǎn)自動(dòng)激活加載,在這個(gè)注解上可以設(shè)置group、value、order等值。

這些值主要表示注解在的類、方法上所屬的分組、執(zhí)行順序等,當(dāng)傳入的參數(shù)符合直接中的值的時(shí)候,可以進(jìn)行激活。

這個(gè)注解對(duì)應(yīng)的源碼,其實(shí)大部分在上面都已經(jīng)說(shuō)過(guò)了,總的來(lái)說(shuō),并不是特別的難,源碼在這里,大家可以先看一下:

 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);
        if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
          //1
            getExtensionClasses();
            for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Activate activate = entry.getValue();
                if (isMatchGroup(group, activate.group())) {
                    if (!names.contains(name)
                            && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
                            && isActive(activate, url)) {
                        //2
                        T ext = getExtension(name);
                        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)) {
                if (Constants.DEFAULT_KEY.equals(name)) {
                    if (!usrs.isEmpty()) {
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
                  //3
                    T ext = c(name);
                    usrs.add(ext);
                }
            }
        }
        if (!usrs.isEmpty()) {
            exts.addAll(usrs);
        }
        return exts;
    }

他的核心方法就是我在里面標(biāo)記的這幾個(gè),其余的呢,都是循環(huán)啦,取值之類的操作,沒(méi)有什么太多的復(fù)雜邏輯。

結(jié)尾

以上就是dubbo spi的核心內(nèi)容了,因?yàn)閟pi貫穿了dubbo的所有核心功能,所以要想看懂dubbo的源碼,那么就需要先了解SPI這個(gè)前置任務(wù)。大家可以一邊對(duì)照著文章,一邊打斷點(diǎn),這樣對(duì)SPI也會(huì)了解更深入。

作者:java知路


歡迎關(guān)注微信公眾號(hào) :java知路