JDK SPI 、Spring SPI、Dubbo SPI機制
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質(zhì)文章,第一時間送達
JDK SPI機制
SPI(Service Provider Interface),是一種將服務(wù)接口與服務(wù)實現(xiàn)分離以達到解耦可拔插、大大提升了程序可擴展性的機制。
約定(我覺得稱之為規(guī)范更合適):
1. 制定統(tǒng)一的規(guī)范(比如 java.sql.Driver)
2. 服務(wù)提供商提供這個規(guī)范具體的實現(xiàn),在自己jar包的META-INF/services/目錄里創(chuàng)建一個以服務(wù)接口命名的文件,內(nèi)容是實現(xiàn)類的全命名(比如:com.mysql.jdbc.Driver)。
3. 平臺引入外部模塊的時候,就能通過該jar包META-INF/services/目錄下的配置文件找到該規(guī)范具體的實現(xiàn)類名,然后裝載實例化,完成該模塊的注入。
這個機制最大的優(yōu)點就是無須在代碼里指定,進而避免了代碼污染,實現(xiàn)了模塊的可拔插。
JDK SPI的一個典型案例就是 java.sql.Driver 了

我們最熟悉的代碼
// 加載驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
// 獲取連接
Connection connection = DriverManager.getConnection("url", "user", "password");
我們進入DriverManager類,里面有個靜態(tài)代碼塊,這部分的內(nèi)容會先執(zhí)行。
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
加載并初始化驅(qū)動
private static void loadInitialDrivers() {
// ...
// 如果驅(qū)動被打包作為服務(wù)提供者,則加載它。
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 1. load
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 2. 獲取Loader的迭代器
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
// 3. 調(diào)用next方法
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
// ...
}
看 ServiceLoader#load方法
public static <S> ServiceLoader<S> load(Class<S> service) {
// 獲取類加載器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// load
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){
// new 一個 ServiceLoader,參數(shù)為服務(wù)接口Class和類加載器
return new ServiceLoader<>(service, loader);
}
可以看出,上面主要是獲取類加載器并新建ServiceLoader的過程,沒有加載實現(xiàn)類的動作。現(xiàn)在看:ServiceLoader#iterator 方法
public Iterator<S> iterator() {
// 這里是個Iterator的匿名內(nèi)部類,重寫了一些方法
return new Iterator<S>() {
// 已存在的提供者
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
// 先檢查緩存
if (knownProviders.hasNext())
return true;
// 緩存沒有,走 java.util.ServiceLoader.LazyIterator#hasNext 方法
return lookupIterator.hasNext();
}
public S next() {
// 同樣先檢查緩存
if (knownProviders.hasNext())
return knownProviders.next().getValue();
// 緩存沒有,走 java.util.ServiceLoader.LazyIterator#next 方法
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
看 ServiceLoader.LazyIterator#hasNext 方法
// java.util.ServiceLoader.LazyIterator#hasNext
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 獲取全路徑:META-INF/services/java.sql.Driver
String fullName = PREFIX + service.getName();
// 加載資源
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
// 這里負責解析前面加載的配置信息
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 解析的返回值是一個 Iterator<String> 類型,其中的String代表文件里配置的實現(xiàn)類全限定名,比如:com.mysql.jdbc.Driver
pending = parse(service, configs.nextElement());
}
// 把nextName初始化
nextName = pending.next();
return true;
}
看 ServiceLoader.LazyIterator#next 方法
// java.util.ServiceLoader.LazyIterator#next
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
// 進入 java.util.ServiceLoader.LazyIterator#nextService 方法
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
// com.mysql.jdbc.Driver
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 加載 com.mysql.jdbc.Driver Class
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
// 實例化并轉(zhuǎn)換成 com.mysql.jdbc.Driver 對象
S p = service.cast(c.newInstance());
// 添加到緩存
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
所以,DriverManager做了什么事:
// 1. 獲取類加載器并創(chuàng)建ServiceLoader對象
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 2. 創(chuàng)建迭代器并覆蓋hasNext和next方法
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
// 3. 這個方法主要是讀取配置文件,獲取其中實現(xiàn)類的全限定名。
while(driversIterator.hasNext()) {
// 4. 這個方法主要是根據(jù)全限定名去生成一個實例
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
另外你也能發(fā)現(xiàn),ServiceLoader只提供了遍歷的方式來獲取目標實現(xiàn)類,沒有提供按需加載的方法,這也是常說的不足之處。
自己實現(xiàn)
定義一個接口
package com.demo.spi;
public interface Funny {
void deal();
}
寫一個實現(xiàn)類
package com.demo.spi;
public class FunnyImpl implements Funny {
@Override
public void deal() {
System.out.println("FunnyImpl");
}
}
配置

文件內(nèi)容

執(zhí)行測試
@Test
public void test(){
ServiceLoader<Funny> serviceLoader = ServiceLoader.load(Funny.class);
serviceLoader.forEach(Funny::deal);
}
輸出

Spring SPI機制
對于Spring的SPI機制主要體現(xiàn)在SpringBoot中。我們知道SpringBoot的啟動包含new SpringApplication和執(zhí)行run方法兩個過程,new的時候有這么個邏輯:(getSpringFactoriesInstances)

這個方法走到里面,無非 1. 加載類的全限定名列表。2. 根據(jù)類名通過反射實例化。

重點在于:SpringFactoriesLoader.loadFactoryNames(type, classLoader)
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 獲取類的全限定名
String factoryClassName = factoryClass.getName();
// 1. 執(zhí)行l(wèi)oadSpringFactories,這里只傳入了類加載器,肯定是要獲取全部配置
// 2. getOrDefault,獲取指定接口的實現(xiàn)類名稱列表,如果沒有則返回一個空列表
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 先檢查緩存
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 獲取類路徑下所有META-INF/spring.factories
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 把加載的配置轉(zhuǎn)換成Map<String, List<String>>格式
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
它還提供了實例化的方法:SpringFactoriesLoader.loadFactories(factoryClass, classLoader) 利用反射實現(xiàn),理解起來也不困難,不做解釋了
對比ServiceLoader:
1. 都是XXXLoader。命名格式都一樣。
2. 一個是加載 META-INF/services/ 目錄下的配置;一個是加載 META-INF/spring.factories 固定文件的配置。思路都一樣。
3. 兩個都是利用ClassLoader和ClassName來完成操作的。不同的是Java的ServiceLoader加載配置和實例化都是自己來實現(xiàn),并且不能按需加載;SpringFactoriesLoader既可以單獨加載配置然后按需實例化也可以實例化全部。
如何實現(xiàn)一個Spring-Boot-Starter?
先看一下SpringBoot的相關(guān)要點。

這個是SpringBoot的 @SpringBootApplication 注解,里面還有一個 @EnableAutoConfiguration 注解,開啟自動配置的。

可以發(fā)現(xiàn)這個自動配置注解在另一個工程,而這個工程里也有個spring.factories文件,如下圖:

我們知道在SpringBoot的目錄下有個spring.factories文件,里面都是一些接口的具體實現(xiàn)類,可以由SpringFactoriesLoader加載。
那么同樣的道理,spring-boot-autoconfigure模塊也能動態(tài)加載了。看一下其中的內(nèi)容:

我們看到有個接口【org.springframework.boot.autoconfigure.EnableAutoConfiguration】,有N個配置類,都是自動配置相關(guān)的,那么我們可以猜一猜,是不是模仿一下這樣的配置,我們就可以絲滑進入了?
為了證明這一點,我們看一下 dubbo-spring-boot-starter 的結(jié)構(gòu)

你會發(fā)現(xiàn)starter項目沒有實現(xiàn)類,只有個pom文件。

它的關(guān)鍵在于引入了一個 autoconfigure 依賴。

這個里面配置了Spring的 spring.factories 其中只有一個配置

看到這里你是不是感覺到了什么:
1. 新建一個只有pom的starter工程,引入寫好的自動配置模塊。
2. 自動配置模塊配置 spring.factories 文件,格式如上。
3. 具體實現(xiàn)自動配置邏輯。
這樣當SpringBoot啟動加載配置類的時候,就會把這些第三方的配置一起加載。
所以我認為 Starter的作用就是在啟動之前把第三方的配置加載到容器中,具體表現(xiàn)就是無須用戶手動配置即可使用。【如果不準確望指正】
具體實現(xiàn)一個starter
兩個maven工程,目錄如下:

custom-spring-boot-autoconfigure↓↓↓↓↓
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>custom-spring-boot-autoconfigure</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
</dependencies>
</project>
CustomAutoConfiguration
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet")
public class CustomAutoConfiguration {
@Bean
public CustomConfig customConfig(){
System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!!第三方自定義的配置");
return new CustomConfig();
}
}
CustomConfig
/**
* 自定義配置
*/
public class CustomConfig {
private String value = "Default";
}
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.demo.CustomAutoConfiguration
一共四個文件
custom-spring-boot-starter↓↓↓↓
只有pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>custom-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.demo</groupId>
<artifactId>custom-spring-boot-autoconfigure</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
最后把這兩個工程install

下面是新建一個SpringBoot項目,然后引入咱們自定義的starter依賴。
<dependency>
<groupId>com.demo</groupId>
<artifactId>custom-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
然后啟動就可以啦,可以看見,用戶引入依賴之后無需手動配置就可以使用第三方插件

看下工程依賴

Dubbo SPI機制
上面介紹了兩種SPI,到Dubbo這里就不難理解了吧?
思路都是處理類+約定配置 ,在Dubbo里,約定擴展配置在 META-INF/dubbo/internal/ 目錄下

在Dubbo源碼里也可以發(fā)現(xiàn),核心類是 ExtensionLoader
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
getExtensionLoader
// 這個方法就是獲取一個特定類型的ExtensionLoader的實例
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 an interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
// 先從緩存中取
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
// 緩存沒有則新建
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
下面看getExtension方法
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
// 獲取默認的擴展類
return getDefaultExtension();
}
// Holder用來持有目標對象
final Holder<Object> holder = getOrCreateHolder(name);
// 獲取實例
Object instance = holder.get();
// 沒有的話,雙重檢查并創(chuàng)建實例
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
看createExtension方法
private T createExtension(String name) {
// 1. 獲取擴展Class
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);
}
// 2. IOC注入依賴
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
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 + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
1. 獲取所有擴展類
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;
}
// 對于loadDirectory,每個目錄有兩個執(zhí)行邏輯,因為目前要兼容alibaba老版本
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 加載目錄:META-INF/dubbo/internal/
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
// 加載目錄:META-INF/dubbo/
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
// 加載目錄:META-INF/services/
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
// 文件路徑,比如 META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
String fileName = dir + type;
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();
// 加載資源(讀取文件內(nèi)容)
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
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) {
// 截取 # 之前的字符串,# 之后的內(nèi)容為注釋,需要忽略
line = line.substring(0, ci);
}
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
// 按照 = 分割,前面是name,后面是類全限定名
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
// 加載Class
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}
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 occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
// 檢測目標類上是否有 Adaptive 注解
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 設(shè)置 cachedAdaptiveClass緩存
cacheAdaptiveClass(clazz);
// 檢測 clazz 是否是 Wrapper 類型
} else if (isWrapperClass(clazz)) {
// 存儲 clazz 到 cachedWrapperClasses 緩存中
cacheWrapperClass(clazz);
} else {
// 檢測 clazz 是否有默認的構(gòu)造方法,如果沒有,則拋出異常
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
// 如果 name 為空,則嘗試從 Extension 注解中獲取 name,或使用小寫的類名作為 name
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 (ArrayUtils.isNotEmpty(names)) {
// 存儲 name 到 Activate 注解對象的映射關(guān)系
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 存儲 Class 到名稱的映射關(guān)系
cacheName(clazz, n);
// 存儲名稱到 Class 的映射關(guān)系
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
}
這部分有些注釋直接拿的官網(wǎng),總結(jié)下來這部分:
1. 根據(jù)約定的路徑比如【META-INF/dubbo/internal/】加載文件URl。
2. 讀取并解析每一個文件內(nèi)容,具體為按行讀取,去掉注釋,按等號分割為 name-class全限定名鍵值對。
3. 根據(jù)類全限定名生成Class對象,根據(jù)Class對象的的特點進行相關(guān)的緩存以及name到Class對象的緩存。
2. IOC注入依賴
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
// 這里判斷方法是否是setter方法,Dubbo目前只處理setter的IOC
if (isSetter(method)) {
// 如果標注了@DisableInject,則不進行注入
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 獲取 setter 方法參數(shù)類型
Class<?> pt = method.getParameterTypes()[0];
// 基本類型跳過
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 獲取屬性名,比如 setName 方法對應(yīng)屬性名 name
String property = getSetterProperty(method);
// 從 ObjectFactory 中獲取依賴對象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通過反射調(diào)用 setter 方法設(shè)置object依賴
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
這部分容易理解,核心部分利用反射實現(xiàn),其它前置處理做了一些校驗。
而使用Dubbo SPI的話也是一樣的套路,擴展類實現(xiàn) + 配置 + ExtensionLoader
作者 | 露娜妹
來源 | cnblogs.com/LUA123/p/12460869.html

