Dubbo 擴展機制——SPI

1. 簡介

Dubbo 良好的擴展性與兩個方面密不可分,一是整個框架中針對不同的場景五嫂,恰到好處的使用了各種設(shè)計模式颗品,二是接下來要講的加載機制∑兜迹基于 Dubbo SPI 加載機制抛猫,讓整個框架的接口和具體實現(xiàn)類解耦蟆盹,從而奠定了整個框架良好可擴展性的基礎(chǔ)孩灯。

2. Java SPI

在講解 Dubbo SPI 之前,我們先了解下 Java SPI 是怎么使用的逾滥。SPI 全稱 Service Provider Interface峰档,起初是提供給廠商做插件開發(fā)的。Java SPI 使用了策略模式寨昙,一個接口多種實現(xiàn)讥巡。我們只聲明接口,具體的實現(xiàn)并不在程序中直接確定舔哪,而是由程序之外的配置掌控欢顷,用于具體實現(xiàn)的裝配。具體步驟如下:

(1) 定義一個接口及對應(yīng)的方法捉蚤。
(2)編寫該接口的一個實現(xiàn)類抬驴。
(3)在 META-INF/services/ 目錄下,創(chuàng)建一個以接口全路徑命名的文件缆巧,如 com.test.spi.PrintService布持。
(4)文件內(nèi)容為具體實現(xiàn)類的全路徑名,如果有多個陕悬,則用分行符分割题暖。
(5)在代碼中通過 java.util.ServiceLoader 來加載所有的實現(xiàn)類。

3. Dubbo SPI

與 Java SPI 相比捉超,Dubbo SPI 做了一定的改進(jìn)和優(yōu)化:

  1. JDK 標(biāo)準(zhǔn)的 SPI 會一次性實例化擴展點的所有實現(xiàn)胧卤,如果沒用上也加載,則浪費資源拼岳。而 Dubbo SPI 只是加載配置文件中的類枝誊,而不會立即全部初始化。
  2. 增加了對擴展 IOC 和 AOP 的支持裂问,一個擴展可以直接 setter 注入其他擴展侧啼。

3.1 代碼示例

聲明擴展點:

package com.alibaba.dubbo.examples.spi.api;

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

@SPI("print")
public interface PrintService {

    void printInfo();
}

聲明擴展點的實現(xiàn)類:

public class PrintServiceImpl implements PrintService {

    @Override
    public void printInfo() {
        System.out.println("hello,world");
    }
}

在 META-INF/dubbo/ 目錄下牛柒,新建配置文件 com.alibaba.dubbo.examples.spi.api.PrintService,內(nèi)容如下:

print=com.alibaba.dubbo.examples.spi.impl.PrintServiceImpl

3.2 擴展點的配置規(guī)范

Dubbo SPI 和 Java SPI 類似痊乾,需要在 META-INF/dubbo/ 下放置對應(yīng)的 SPI 配置文件皮壁,文件名稱需要命名為接口的全路徑名。配置的內(nèi)容為 key=擴展點實現(xiàn)類的全路徑名哪审,如果有多個實現(xiàn)類則使用換行符分隔蛾魄。其中 key 為 Dubbo SPI 注解中傳入的參數(shù)。另外湿滓,Dubbo SPI 還兼容了 Java SPI 的配置路徑滴须,在 Dubbo 啟動時,會默認(rèn)掃描這三個目錄下的配置文件:META-INF/services/叽奥、META-INF/dubbo/扔水、META-INF/dubbo/internal。

3.3 擴展點的特性

從 Dubbo 官方文檔中可以知道朝氓,擴展類一共包含四種特性:自動包裝魔市、自動加載、自適應(yīng)和自動激活赵哲。

1. 自動包裝
自動包裝是一種擴展類待德,ExtensionLoader在加載擴展時,如果發(fā)現(xiàn)這個擴展類包含其他擴展點實例作為構(gòu)造函數(shù)的參數(shù)枫夺,則這個擴展類就會被認(rèn)定為是Wrapper類将宪,例如:

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }
......
}

ProtocolFilterWrapper繼承了Protocol接口,同時其構(gòu)造函數(shù)中又注入了一個Protocol類型的參數(shù)橡庞。因此ProtocolFilterWrapper會被認(rèn)定為Wrapper類较坛。這是一種裝飾模式,把通用的抽象邏輯封裝或?qū)ψ宇愡M(jìn)行增強毙死,讓子類可以更加專注具體的實現(xiàn)燎潮。

2. 自動加載
除了在構(gòu)造函數(shù)中傳入其他擴展實例,我們還經(jīng)常使用setter方法設(shè)置屬性值扼倘,如果某個擴展類是另一個擴展點類的成員屬性确封,并且擁有setter方法,那么框架也會自動注入對應(yīng)的擴展點實例再菊。ExtensionLoader在執(zhí)行擴展點初始化的時候爪喘,會自動通過setter方法注入對應(yīng)的實現(xiàn)類。這里有個問題纠拔,如果擴展類屬性是一個接口秉剑,他有多種實現(xiàn),那么具體注入哪個呢稠诲?這就涉及第三個特性——自適應(yīng)侦鹏。

3. 自適應(yīng)
在 Dubbo SPI 中诡曙,我們使用@Adaptive注解,可以動態(tài)地通過 URL 中的參數(shù)來確定要使用哪個具體的實現(xiàn)類略水。從而解決自動加載中的實例注入問題价卤。@Adaptive注解使用示例如下:

@SPI("netty")
public interface Transporter {

    @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;

    @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
    Client connect(URL url, ChannelHandler handler) throws RemotingException;

}

@Adaptive傳入兩個 Constants 中的參數(shù),他們的值分別是"server"渊涝、“transporter”慎璧。當(dāng)外部調(diào)用Transporter#bind方法時,會動態(tài)從傳入的參數(shù)"URL"中提取 key 參數(shù)“server”的 value 值跨释,如果能匹配上某個擴展實現(xiàn)類則直接使用對應(yīng)的實現(xiàn)類胸私;如果未匹配上,則繼續(xù)通過第二個 key 參數(shù)“transporter”提取 value 的值鳖谈。如果都未匹配上岁疼,則拋出異常。也就是說蚯姆,如果@Adaptive中傳入了多個參數(shù)五续,則依次進(jìn)行實現(xiàn)類的匹配洒敏,直到最后拋出異常龄恋。

這種動態(tài)尋找實現(xiàn)類的方式比較靈活,但只能激活一個具體的實現(xiàn)類凶伙,如果需要激活多個實現(xiàn)類郭毕,如 Filter 可以同時有多個過濾器;根據(jù)不同的條件函荣,同時激活多個實現(xiàn)類显押,如何實現(xiàn)?這就涉及最后一個特性——自動激活傻挂。

4. 自動激活
使用@Activate注解乘碑,可以標(biāo)記對應(yīng)的擴展點默認(rèn)被激活啟用。該注解還可以通過傳入不同的參數(shù)金拒,設(shè)置擴展點在不同條件下被自動激活兽肤。主要的使用場景是某個擴展點的多個實例需要同時啟用(比如 Filter 擴展點)。

3.4 擴展點注解

@SPI注解一般使用在接口上绪抛,作用是標(biāo)記這個接口是一個 Dubbo SPI 接口资铡,既是一個擴展點。注解中有一個 value 屬性幢码,表示這個接口的默認(rèn)實現(xiàn)類笤休。例如上面的@SPI("netty"),我們可以看到Transporter接口使用Netty作為默認(rèn)實現(xiàn)症副。

@Adaptive注解可以標(biāo)注在類店雅、接口政基、方法上。如果標(biāo)注在接口的方法上闹啦,則可以通過參數(shù)動態(tài)的獲取實現(xiàn)類捍掺。方法級別注解在第一次getExtension時迷扇,會自動生成和編譯一個動態(tài)的Adaptive類,從而達(dá)到動態(tài)實現(xiàn)類的效果。例如:Transporter接口在 bind 和 connect 兩個方法上添加了@Adaptive注解瘤袖。Dubbo 在初始化擴展點時,會自動生成一個Transporter$Adaptive類粥帚,里面會實現(xiàn)這兩個方法漆撞,方法里面會有一些通用的抽象邏輯,通過@Adaptive傳入的參數(shù)挚歧,找到并調(diào)用真正的實現(xiàn)類扛稽。當(dāng)注解放在實現(xiàn)類上時,則整個實現(xiàn)類會直接作為默認(rèn)實現(xiàn)滑负,不再自動生成的Adaptive類在张。在擴展點接口的多個實現(xiàn)里,只能有一個實現(xiàn)類可以加@Adaptive注解矮慕,否則拋出異常帮匾。

3.5 ExtensionLoader 工作原理

ExtensionLoader是整個擴展機制的主要邏輯類,邏輯入口可以分為getExtension痴鳄、getAdaptiveExtension瘟斜、getActivateExtension三個,分別是獲取普通擴展類痪寻、獲取自適應(yīng)擴展類螺句、獲取自動激活擴展類。

3.5.1 getExtension 的實現(xiàn)原理

當(dāng)調(diào)用getExtension(String name)方法時橡类,會先檢查緩存中是否有現(xiàn)成的數(shù)據(jù)蛇尚,沒有則調(diào)用createExtension開始創(chuàng)建。如果 name 為 “true”顾画,則加載并返回默認(rèn)擴展類取劫。

在調(diào)用createExtension開始創(chuàng)建的過程中,也會檢查緩存中是否有配置信息亲雪,如果不存在擴展類勇凭,則會到 META-INF/services/、META-INF/dubbo/义辕、META-INF/dubbo/internal/ 這幾路徑中讀取所有配置文件虾标,得到對應(yīng)擴展點實現(xiàn)類的全稱。擴展點配置信息加載過程源碼如下:

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

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

檢查是否有@SPI注解璧函。如果有傀蚌,則獲取注解中的 value 值,作為擴展點默認(rèn)實現(xiàn)蘸吓。然后加載路徑下的配置文件loadDirectory善炫。

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            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);
        }
    }

通過getResources或者getSystemResources得到配置文件,然后遍歷并解析配置文件库继,得到擴展實現(xiàn)類箩艺,并加入緩存。加載完擴展點配置后宪萄,再通過反射獲得所有擴展實現(xiàn)類并緩存起來艺谆。注意,此處僅僅是把 Class 加載到 JVM 中拜英,但并沒有做 Class 初始化静汤。在加載 Class 文件時,會根據(jù) Class 上的注解來判斷擴展點類型居凶,再根據(jù)類型分類做緩存虫给,此處邏輯在ExtensionLoader#loadClass

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
         ...
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            // 如果時自適應(yīng)類,則緩存
            if (cachedAdaptiveClass == null) {
                cachedAdaptiveClass = clazz;
            } else if (!cachedAdaptiveClass.equals(clazz)) {
                // 如果多個自適應(yīng)實現(xiàn)類侠碧,則拋出異常
                throw new IllegalStateException("More than 1 adaptive class found: "
                        + cachedAdaptiveClass.getClass().getName()
                        + ", " + clazz.getClass().getName());
            }
        } else if (isWrapperClass(clazz)) {
            // 如果是包裝類(Wrapper)抹估,則直接直接加入包裝擴展類的 Set 集合
            Set<Class<?>> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                wrappers = cachedWrapperClasses;
            }
            wrappers.add(clazz);
        } else {
            // 不是自適應(yīng)類,也不是包裝類舆床,只剩下普通擴展類了
            clazz.getConstructor();
            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) {
                    // 如果擴展類有 @Activate 注解棋蚌,則加入自動激活類緩存
                    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());
                    }
                }
            }
        }
    }

加載完畢擴展信息之后,完成了createExtension的第一步:

 private T createExtension(String name) {
        // 根據(jù) key 獲取對應(yīng)的擴展點實現(xiàn)類
        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);
            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);
        }
    }

下一步就是根據(jù)傳入的 name 找到對應(yīng)的類并通過Class.forName方法進(jìn)行初始化挨队,并為其注入依賴的其他擴展類(自動加載特性),我們看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())) {
                       // 找到 setter 方法
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            // 通過字符串截取蒿往,獲得小寫開頭的類名盛垦。如 setTestService,截取之后是 testService
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            // 根據(jù) key 獲取對應(yīng)的擴展點實例
                            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;
    }

injectExtension方法體實現(xiàn)了類似 Spring 的 IoC 機制瓤漏,其實現(xiàn)原理比較簡單:首先通過反射獲取類的所有方法腾夯,然后遍歷以字符串“set”開頭的方法,得到 set 方法的參數(shù)類型蔬充,再通過ExtensionFactory尋找參數(shù)類型相同的擴展類實例蝶俱,如果找到就設(shè)置進(jìn)去。

當(dāng)擴展類初始化之后饥漫,會檢查一次包裝擴展類Set<Class<?>> cachedWrapperClasses榨呆,查找包含與擴展點類型相同的構(gòu)造函數(shù),為其注入剛初始化的擴展類庸队。從源碼可知积蜻,包裝類的構(gòu)造函數(shù)注入也是通過injectExtension方法實現(xiàn)的闯割。

3.5.2 getAdaptiveExtension 的實現(xiàn)原理

由之前的流程我們可以知道,getAdaptiveExtension()方法中竿拆,會為擴展點接口自動生成實現(xiàn)類字符串宙拉,實現(xiàn)類主要包含以下邏輯:為接口中每個有@Adaptive注解的方法生成默認(rèn)實現(xiàn)(沒有注解的方法則生成空實現(xiàn)),每個默認(rèn)實現(xiàn)都會從 URL 中提取@Adaptive參數(shù)值丙笋,并且以此為依據(jù)動態(tài)加載擴展點谢澈。然后框架會使用不同的編譯器,把實現(xiàn)類字符串編譯為自適應(yīng)類并返回御板。

如果一個接口上既有@SPI("impl")注解澳化,方法上又有@Adaptive("impl2")注解,那么會使用哪個 key 作為默認(rèn)實現(xiàn)呢稳吮?其優(yōu)先通過@Adaptive注解傳入的 key 去查找擴展實現(xiàn)類缎谷;如果沒有找到,則通過@SPI注解中 key 去查找灶似;如果@SPI注解中沒有默認(rèn)值列林,則會采用駝峰規(guī)則,把類名轉(zhuǎn)化為 key酪惭,再去查找希痴。

駝峰規(guī)則:比如類名為 SimpleExt,則轉(zhuǎn)化后的 key 為 simple.ext春感。

3.5.3 getActivateExtension 的實現(xiàn)原理

由于@Activate使用場景較少砌创,并且實現(xiàn)原理較為簡單,感興趣的同學(xué)自行去了解鲫懒。

3.6 ExtensionFactory 的實現(xiàn)原理

ExtensionFactory類似于Spring中的BeanFactory嫩实,它是擴展類的 bean 工廠。我們看下它的實現(xiàn)類:


既然工廠接口有多個實現(xiàn)窥岩,那么是怎么確定使用哪個工廠實現(xiàn)的呢甲献?我們可以看到AdaptiveExtensionFactory這個實現(xiàn)類工廠上有@Adaptive注解。因此颂翼,AdaptiveExtensionFactory會作為默認(rèn)實現(xiàn)類晃洒。

除了AdaptiveExtensionFactory,還有SpiExtensionFactorySpringExtensionFactory兩個工廠朦乏。也就是說球及,我們除了可以從 Dubbo SPI 管理的容器中獲取擴展點實例,還可以從 Spring 容器獲取呻疹。

我們先看SpringExtensionFactory

public class SpringExtensionFactory implements ExtensionFactory {

    private static final Set<ApplicationContext> contexts = new ConcurrentHashSet<ApplicationContext>();

    public static void addApplicationContext(ApplicationContext context) {
        contexts.add(context);
        BeanFactoryUtils.addApplicationListener(context, shutdownHookListener);
    }
    ...
    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {
        // 先根據(jù)名稱去獲取
        for (ApplicationContext context : contexts) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
        // 再根據(jù)類型去獲取
        for (ApplicationContext context : contexts) {
            try {
                return context.getBean(type);
            } catch (NoUniqueBeanDefinitionException multiBeanExe) {
                logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type.");
            } catch (NoSuchBeanDefinitionException noBeanExe) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
                }
            }
        }

        logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean.");

        return null;
    }

}

該工廠提供了保存 Spring 上下文的靜態(tài)方法吃引,可以把 Spring 上下文保存在 Set 集合中,當(dāng)調(diào)用getExtension獲取擴展類時,會遍歷 Set 集合中的所有 Spring 上下际歼,先根據(jù)名字依次去獲取惶翻,如果沒有獲取到,再根據(jù)類型去獲取鹅心。

那么 Spring 的上下文又是什么時候被保存的呢吕粗?我們可以通過代碼搜索得知,在ReferenceBeanServiceBean中會調(diào)用靜態(tài)方法保存 Spring 上下文旭愧,即一個服務(wù)被發(fā)布或者被引用的時候颅筋,對應(yīng)的 Spring 上下文會被保存下來。

我們再看SpiExtensionFactory:

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}

主要是獲取擴展點接口對應(yīng)的 Adaptive 實現(xiàn)類输枯。例如:某個擴展點實現(xiàn)類 ClassA 上有@Adaptive注解议泵,則使用SpiExtensionFactory#getExtension會直接返回 ClassA 實例。

我們再看AdaptiveExtensionFactory:

/**
 * AdaptiveExtensionFactory
 * 該類的作用是管理其他的 ExtensionFactory
 */
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;

    /**
     * 構(gòu)造方法會加載其他擴展工廠
     */
    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        /*
         * 獲取 getExtensionClasses() 是不會加載被 @Adaptive 注解的實現(xiàn)類的
         * 即這里只是把其他兩個 ExtensionFactory 放入 factories 中
         */
        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;
    }

}

這個默認(rèn)工廠在構(gòu)造方法中就獲取了其他所有擴展類工廠并緩存起來桃熄,包括SpringExtensionFactorySpiExtensionFactory先口。被AdaptiveExtensionFactory緩存的工廠會通過TreeSet進(jìn)行自然排序,SPI 排在前面瞳收,Spring 排在后面碉京。當(dāng)調(diào)用getExtension方法時,會遍歷所有的工廠螟深,先從 SPI 容器獲取擴展類谐宙;如果沒有找到,再從 Spring 容器中查找界弧。我們可以理解為凡蜻,AdaptiveExtensionFactory持有了所有的工廠實現(xiàn),它的getExtension方法只是遍歷它持有的所有工廠垢箕,最終還是調(diào)用 SPI 或者 Spring 工廠實現(xiàn)的getExtension方法划栓。

4. 小結(jié)

我們沒有講擴展點的動態(tài)編譯,其實現(xiàn)手法跟ExtensionFactory類似舰讹,其采用含有@Adaptive注解的AdaptiveCompiler作為Compiler的默認(rèn)實現(xiàn)茅姜,主要作用是為了管理其他Compiler(JavassistCompilerJdkCompiler)。由于Compiler接口上采用@SPI("javassist")月匣,說明 Javassist 編譯器作為默認(rèn)編譯器。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奋姿,一起剝皮案震驚了整個濱河市锄开,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌称诗,老刑警劉巖萍悴,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡癣诱,警方通過查閱死者的電腦和手機计维,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撕予,“玉大人鲫惶,你說我怎么就攤上這事∈德眨” “怎么了欠母?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吆寨。 經(jīng)常有香客問我赏淌,道長,這世上最難降的妖魔是什么啄清? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任六水,我火速辦了婚禮,結(jié)果婚禮上辣卒,老公的妹妹穿的比我還像新娘掷贾。我一直安慰自己,他們只是感情好添寺,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布胯盯。 她就那樣靜靜地躺著,像睡著了一般计露。 火紅的嫁衣襯著肌膚如雪博脑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天票罐,我揣著相機與錄音叉趣,去河邊找鬼。 笑死该押,一個胖子當(dāng)著我的面吹牛疗杉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蚕礼,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼烟具,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了奠蹬?” 一聲冷哼從身側(cè)響起朝聋,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎囤躁,沒想到半個月后冀痕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荔睹,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年言蛇,在試婚紗的時候發(fā)現(xiàn)自己被綠了僻他。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡腊尚,死狀恐怖吨拗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情跟伏,我是刑警寧澤丢胚,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站受扳,受9級特大地震影響携龟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜勘高,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一峡蟋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧华望,春花似錦蕊蝗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宾抓,卻和暖如春子漩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背石洗。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工幢泼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人讲衫。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓缕棵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親涉兽。 傳聞我的和親對象是個殘疾皇子招驴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容