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)化:
- JDK 標(biāo)準(zhǔn)的 SPI 會一次性實例化擴展點的所有實現(xiàn)胧卤,如果沒用上也加載,則浪費資源拼岳。而 Dubbo SPI 只是加載配置文件中的類枝誊,而不會立即全部初始化。
- 增加了對擴展 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
,還有SpiExtensionFactory
和SpringExtensionFactory
兩個工廠朦乏。也就是說球及,我們除了可以從 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 的上下文又是什么時候被保存的呢吕粗?我們可以通過代碼搜索得知,在ReferenceBean
和ServiceBean
中會調(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)造方法中就獲取了其他所有擴展類工廠并緩存起來桃熄,包括SpringExtensionFactory
和SpiExtensionFactory
先口。被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
(JavassistCompiler
和JdkCompiler
)。由于Compiler
接口上采用@SPI("javassist")
月匣,說明 Javassist 編譯器作為默認(rèn)編譯器。