一裸删、SPI簡(jiǎn)介
SPI 全稱為 (Service Provider Interface) 兼呵,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制满败。 目前有不少框架用它來(lái)做服務(wù)的擴(kuò)展發(fā)現(xiàn), 簡(jiǎn)單來(lái)說(shuō)粪摘,它就是一種動(dòng)態(tài)替換發(fā)現(xiàn)的機(jī)制瀑晒, 舉個(gè)例子來(lái)說(shuō), 有個(gè)接口赶熟,想運(yùn)行時(shí)動(dòng)態(tài)的給它添加實(shí)現(xiàn)瑰妄,你只需要添加一個(gè)實(shí)現(xiàn)陷嘴,而后映砖,把新加的實(shí)現(xiàn),描述給JDK知道就行啦(通過(guò)改一個(gè)文本文件即可)我們經(jīng)常遇到的就是java.sql.Driver接口灾挨,其他不同廠商可以針對(duì)同一接口做出不同的實(shí)現(xiàn)邑退,mysql和postgresql都有不同的實(shí)現(xiàn)提供給用戶,而Java的SPI機(jī)制可以為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)
SPI 機(jī)制的思想來(lái)源正是:開閉原則劳澄,對(duì)擴(kuò)展開放地技,對(duì)修改關(guān)閉。對(duì)于一個(gè)特定的程序秒拔,如 Java 的 java.sql.Driver莫矗,就是一個(gè)接口,Mysql 的 Driver 實(shí)現(xiàn)就是通過(guò) SPI 機(jī)制被程序加載的砂缩。不難想到作谚,其他 Driver 的實(shí)現(xiàn)也可以依靠 SPI 機(jī)制去實(shí)現(xiàn)。
<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1gg8d7uxz52j30gq037dfp.jpg" alt="image-20200205095809050" style="zoom:80%;float:left" />
類圖中庵芭,接口對(duì)應(yīng)定義的抽象SPI接口妹懒;實(shí)現(xiàn)方實(shí)現(xiàn)SPI接口;調(diào)用方依賴SPI接口双吆。
SPI接口的定義在調(diào)用方眨唬,在概念上更依賴調(diào)用方会前;組織上位于調(diào)用方所在的包中;實(shí)現(xiàn)位于獨(dú)立的包中匾竿。
當(dāng)接口屬于實(shí)現(xiàn)方的情況瓦宜,實(shí)現(xiàn)方提供了接口和實(shí)現(xiàn),這個(gè)用法很常見岭妖,屬于API調(diào)用歉提。我們可以引用接口來(lái)達(dá)到調(diào)用某實(shí)現(xiàn)類的功能。
二区转、Java SPI
當(dāng)服務(wù)的提供者提供了一種接口的實(shí)現(xiàn)之后苔巨,需要在classpath下的META-INF/services/目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件,這個(gè)文件里的內(nèi)容就是這個(gè)接口的具體的實(shí)現(xiàn)類废离。當(dāng)其他的程序需要這個(gè)服務(wù)的時(shí)候侄泽,就可以通過(guò)查找這個(gè)jar包(一般都是以jar包做依賴)的META-INF/services/中的配置文件,配置文件中有接口的具體實(shí)現(xiàn)類名蜻韭,可以根據(jù)這個(gè)類名進(jìn)行加載實(shí)例化悼尾,就可以使用該服務(wù)了。JDK中查找服務(wù)實(shí)現(xiàn)的工具類是:java.util.ServiceLoader肖方。
2.1 SPI接口
package tim.wang.sourcecode.spi;
/**
* 定義一個(gè)接口和多個(gè)實(shí)現(xiàn)
* @author wangjun
* @date 2020-06-28
*/
public interface Robot {
void sayHello();
}
定義了一個(gè)機(jī)器人接口闺魏,有一個(gè)sayHello方法
2.2 SPI具體實(shí)現(xiàn)
package tim.wang.sourcecode.spi;
/**
* 擎天柱實(shí)現(xiàn)
* @author wangjun
* @date 2020-06-28
*/
public class OptimusPrime implements Robot{
@Override
public void sayHello() {
System.out.println("hello, I am Optimus Prime.");
}
}
package tim.wang.sourcecode.spi;
/**
* 大黃蜂 實(shí)現(xiàn)
* @author wangjun
* @date 2020-06-28
*/
public class Bumblebee implements Robot{
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee");
}
}
2.3 增加META-INF目錄文件
Resource下面創(chuàng)建META-INF/services 目錄里創(chuàng)建一個(gè)以服務(wù)接口命名的文件
<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1gg8dsr8ufmj30g603idfp.jpg" alt="image-20200205095809050" style="zoom:80%;float:left" />
tim.wang.sourcecode.spi.Bumblebee
tim.wang.sourcecode.spi.OptimusPrime
2.4 增加RobotService
package tim.wang.sourcecode.spi;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.stream.StreamSupport;
/**
* @author wangjun
* @date 2020-06-28
*/
public class RobotService {
public Robot getRobot() {
ServiceLoader<Robot> serializers = ServiceLoader.load(Robot.class);
final Optional<Robot> serializer = StreamSupport.stream(serializers.spliterator(), false)
.findFirst();
return serializer.orElse(new OptimusPrime());
}
public static void main(String[] args) {
RobotService robotService = new RobotService();
Robot robot = robotService.getRobot();
robot.sayHello();
}
}
2.5 SPI的用途
數(shù)據(jù)庫(kù)DriverManager、Spring俯画、ConfigurableBeanFactory等都用到了SPI機(jī)制析桥,這里以數(shù)據(jù)庫(kù)DriverManager為例,看一下其實(shí)現(xiàn)的內(nèi)幕艰垂。
DriverManager是jdbc里管理和注冊(cè)不同數(shù)據(jù)庫(kù)driver的工具類泡仗。針對(duì)一個(gè)數(shù)據(jù)庫(kù),可能會(huì)存在著不同的數(shù)據(jù)庫(kù)驅(qū)動(dòng)實(shí)現(xiàn)猜憎。我們?cè)谑褂锰囟ǖ尿?qū)動(dòng)實(shí)現(xiàn)時(shí)娩怎,不希望修改現(xiàn)有的代碼,而希望通過(guò)一個(gè)簡(jiǎn)單的配置就可以達(dá)到效果胰柑。 在使用mysql驅(qū)動(dòng)的時(shí)候截亦,會(huì)有一個(gè)疑問(wèn),DriverManager是怎么獲得某確定驅(qū)動(dòng)類的柬讨?我們?cè)谶\(yùn)用Class.forName("com.mysql.jdbc.Driver")加載mysql驅(qū)動(dòng)后崩瓤,就會(huì)執(zhí)行其中的靜態(tài)代碼把driver注冊(cè)到DriverManager中,以便后續(xù)的使用姐浮。
在JDBC4.0之前谷遂,連接數(shù)據(jù)庫(kù)的時(shí)候,通常會(huì)用Class.forName("com.mysql.jdbc.Driver")
這句先加載數(shù)據(jù)庫(kù)相關(guān)的驅(qū)動(dòng)卖鲤,然后再進(jìn)行獲取連接等的操作肾扰。而JDBC4.0之后不需要Class.forName
來(lái)加載驅(qū)動(dòng)畴嘶,直接獲取連接即可,這里使用了Java的SPI擴(kuò)展機(jī)制來(lái)實(shí)現(xiàn)集晚。
在java中定義了接口java.sql.Driver窗悯,并沒有具體的實(shí)現(xiàn),具體的實(shí)現(xiàn)都是由不同廠商來(lái)提供的偷拔。
2.6 Mysql DriverManager實(shí)現(xiàn)
我們?cè)趺慈ゴ_定使用哪個(gè)數(shù)據(jù)庫(kù)連接的驅(qū)動(dòng)呢蒋院?這里就涉及到使用Java的SPI擴(kuò)展機(jī)制來(lái)查找相關(guān)驅(qū)動(dòng)的東西了,關(guān)于驅(qū)動(dòng)的查找其實(shí)都在DriverManager中莲绰,DriverManager是Java中的實(shí)現(xiàn)欺旧,用來(lái)獲取數(shù)據(jù)庫(kù)連接,在DriverManager中有一個(gè)靜態(tài)代碼塊如下:
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
可以看到其內(nèi)部的靜態(tài)代碼塊中有一個(gè)loadInitialDrivers
方法蛤签,loadInitialDrivers
用法用到了上文提到的spi工具類ServiceLoader
:
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
遍歷使用SPI獲取到的具體實(shí)現(xiàn)辞友,實(shí)例化各個(gè)實(shí)現(xiàn)類。在遍歷的時(shí)候震肮,首先調(diào)用driversIterator.hasNext()
方法称龙,這里會(huì)搜索classpath下以及jar包中所有的META-INF/services目錄下的java.sql.Driver文件,并找到文件中的實(shí)現(xiàn)類的名字戳晌,此時(shí)并沒有實(shí)例化具體的實(shí)現(xiàn)類鲫尊。
2.7 SPI解決的問(wèn)題場(chǎng)景描述
在我們?cè)O(shè)計(jì)一套API供別人調(diào)用的時(shí)候,如果同一個(gè)功能的要求特別多沦偎,或者同一個(gè)接口要面對(duì)很復(fù)雜的業(yè)務(wù)場(chǎng)景疫向,這個(gè)時(shí)候我們?cè)撛趺崔k呢?
- 其一:我們可以規(guī)范不同的系統(tǒng)調(diào)用扛施,也就是傳遞一個(gè)系統(tǒng)標(biāo)識(shí)鸿捧;
- 其二:我們?cè)趦?nèi)部編碼的時(shí)候可以使用不同的條件判斷語(yǔ)句進(jìn)行處理;
- 其三:我們可以寫幾個(gè)策略類來(lái)來(lái)應(yīng)對(duì)這個(gè)復(fù)雜的業(yè)務(wù)邏輯疙渣,比如同一個(gè)功能的實(shí)現(xiàn),A實(shí)現(xiàn)類與B實(shí)現(xiàn)類的邏輯一點(diǎn)也不一樣堆巧,但是目標(biāo)是一樣的妄荔,這個(gè)時(shí)候使用策略類是毋庸置疑的?
三谍肤、Spring SPI
Spring中使用的類是SpringFactoriesLoader啦租,在org.springframework.core.io.support包中
- SpringFactoriesLoader 會(huì)掃描 classpath 中的 META-INF/spring.factories文件。
- SpringFactoriesLoader 會(huì)加載并實(shí)例化 META-INF/spring.factories 中的制定類型
- META-INF/spring.factories 內(nèi)容必須是 properties 的Key-Value形式荒揣,多值以逗號(hào)隔開篷角。
3.1 使用
和 SPI 不同,由于 SpringFactoriesLoader 中的配置文件格式是 properties
文件系任,因此恳蹲,不需要要像 SPI 中那樣為每個(gè)服務(wù)都創(chuàng)建一個(gè)文件虐块, 而是選擇直接把所有服務(wù)都扔到 META-INF/spring.factories
文件中。
com.fsx.serviceloader.IService=com.fsx.serviceloader.HDFSService,com.fsx.serviceloader.LocalService
// 若有非常多個(gè)需要換行 可以這么寫
// 前面是否頂頭沒關(guān)系(Spring在4.x版本修復(fù)了這個(gè)bug)
com.fsx.serviceloader.IService=\
com.fsx.serviceloader.HDFSService,\
com.fsx.serviceloader.LocalService
public static void main(String[] args) throws IOException {
List<IService> services = SpringFactoriesLoader.loadFactories(IService.class, Main.class.getClassLoader());
List<String> list = SpringFactoriesLoader.loadFactoryNames(IService.class, Main.class.getClassLoader());
System.out.println(list); //[com.fsx.serviceloader.HDFSService, com.fsx.serviceloader.LocalService]
System.out.println(services); //[com.fsx.serviceloader.HDFSService@794cb805, com.fsx.serviceloader.LocalService@4b5a5ed1]
}
使用細(xì)節(jié):
-
spring.factories
內(nèi)容的key不只能是接口嘉蕾,也可以是抽象類贺奠、具體的類。但是有個(gè)原則:=
后面必須是key的實(shí)現(xiàn)類(子類) - key還可以是注解错忱,比如
SpringBoot
中的的key:org.springframework.boot.autoconfigure.EnableAutoConfiguration
儡率,它就是一個(gè)注解 - 文件的格式需要保證正確,否則會(huì)返回
[]
(不會(huì)報(bào)錯(cuò)) -
=
右邊必須不是抽象類以清,必須能夠?qū)嵗铡G矣锌盏臉?gòu)造函數(shù)~ -
loadFactories
依賴方法loadFactoryNames
。loadFactoryNames
方法只拿全類名掷倔,loadFactories
拿到全類名后會(huì)立馬實(shí)例化 -
此處特別注意:
loadFactories
實(shí)例化完成所有實(shí)例后箕肃,會(huì)調(diào)用AnnotationAwareOrderComparator.sort(result)
排序,所以它是支持Ordered
接口排序的今魔,這個(gè)特點(diǎn)特別的重要勺像。
3.2 原理剖析
因?yàn)镾pring的這個(gè)配置文件和上面的不一樣,它的名字是固定的spring.factories
错森,里面的內(nèi)容是key-value形式吟宦,因此一個(gè)文件里可以定義N多個(gè)鍵值對(duì)。我認(rèn)為它比源生JDK的SPI是更加靈活些的~
它主要暴露了兩個(gè)方法:loadFactories
和loadFactoryNames
// @since 3.2
public final class SpringFactoriesLoader {
...
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
...
// 核心方法如下:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 讀取到資源文件涩维,遍歷
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 此處使用的是URLResource把這個(gè)資源讀進(jìn)來(lái)~~~
UrlResource resource = new UrlResource(url);
// 可以看到殃姓,最終它使用的還是PropertiesLoaderUtils,只能使鍵值對(duì)的形式哦~~~ 當(dāng)然xml也是被支持的
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
// 使用逗號(hào),分隔成數(shù)組瓦阐,遍歷 名稱就出來(lái)了~~~
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);
}
}
}
四蜗侈、Spring-Boot-Starter
SpringBoot的 @SpringBootApplication 注解,里面還有一個(gè) @EnableAutoConfiguration 注解睡蟋,開啟自動(dòng)配置的踏幻。
可以發(fā)現(xiàn)這個(gè)自動(dòng)配置注解在另一個(gè)工程,而這個(gè)工程里也有個(gè)spring.factories文件戳杀,如下圖:
<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ggo0tmvup6j30d80c3q3j.jpg" alt="image-20200205095809050" style="zoom:80%;float:left" />
我們知道在SpringBoot的目錄下有個(gè)spring.factories文件该面,里面都是一些接口的具體實(shí)現(xiàn)類,可以由SpringFactoriesLoader加載信卡。
那么同樣的道理隔缀,spring-boot-autoconfigure模塊也能動(dòng)態(tài)加載了“剑看一下其中的內(nèi)容:
<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ggo1gm8smej30le0gyq4p.jpg" alt="image-20200205095809050" style="zoom:80%;float:left" />
為了證明這一點(diǎn)猾瘸,我們看一下 dubbo-spring-boot-starter 的結(jié)構(gòu)
<img src="https://tva1.sinaimg.cn/large/007S8ZIlgy1ggo1gm8smej30le0gyq4p.jpg" alt="image-20200205095809050" style="zoom:80%;float:left" />
它的關(guān)鍵在于引入了一個(gè) autoconfigure 依賴。這個(gè)里面配置了Spring的 spring.factories 其中只有一個(gè)配置
<?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>
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();
}
}
/**
* 自定義配置
*/
public class CustomConfig {
private String value = "Default";
}
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.demo.CustomAutoConfiguration
五、Dubbo SPI
我們首先通過(guò) ExtensionLoader 的 getExtensionLoader 方法獲取一個(gè) ExtensionLoader 實(shí)例牵触,然后再通過(guò) ExtensionLoader 的 getExtension 方法獲取拓展類對(duì)象淮悼。這其中,getExtensionLoader 方法用于從緩存中獲取與拓展類對(duì)應(yīng)的 ExtensionLoader荒吏,若緩存未命中敛惊,則創(chuàng)建一個(gè)新的實(shí)例。該方法的邏輯比較簡(jiǎn)單绰更,本章就不進(jìn)行分析了瞧挤。下面我們從 ExtensionLoader 的 getExtension 方法作為入口,對(duì)拓展類對(duì)象的獲取過(guò)程進(jìn)行詳細(xì)的分析儡湾。
- 對(duì)Dubbo進(jìn)行擴(kuò)展特恬,不需要改動(dòng)Dubbo的源碼
- 自定義的Dubbo的擴(kuò)展點(diǎn)實(shí)現(xiàn),是一個(gè)普通的Java類徐钠,Dubbo沒有引入任何Dubbo特有的元素癌刽,對(duì)代碼侵入性幾乎為零。
- 將擴(kuò)展注冊(cè)到Dubbo中尝丐,只需要在ClassPath中添加配置文件显拜。使用簡(jiǎn)單。而且不會(huì)對(duì)現(xiàn)有代碼造成影響爹袁。符合開閉原則远荠。
- dubbo的擴(kuò)展機(jī)制設(shè)計(jì)默認(rèn)值:@SPI("dubbo") 代表默認(rèn)的spi對(duì)象
- Dubbo的擴(kuò)展機(jī)制支持IoC,AoP等高級(jí)功能
- Dubbo的擴(kuò)展機(jī)制能很好的支持第三方IoC容器,默認(rèn)支持Spring Bean失息,可自己擴(kuò)展來(lái)支持其他容器譬淳,比如Google的Guice。
- 切換擴(kuò)展點(diǎn)的實(shí)現(xiàn)盹兢,只需要在配置文件中修改具體的實(shí)現(xiàn)邻梆,不需要改代碼。使用方便绎秒。
六浦妄、ServiceLoader類分析
ServiceLoader.class是一個(gè)工具類,根據(jù)META-INF/services/xxxInterfaceName下面的文件名,加載具體的實(shí)現(xiàn)類.
從load(Search.class)進(jìn)去,我們來(lái)扒一下這個(gè)類,下面主要是貼代碼,分析都在代碼注釋內(nèi).
- 可以看到,里面并沒有很多邏輯,主要邏輯都交給了LazyIterator這類
/*
*入口, 獲取一下當(dāng)前類的類加載器,然后調(diào)用下一個(gè)靜態(tài)方法
*/
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
/*
*這個(gè)也沒有什么邏輯,直接調(diào)用構(gòu)造方法
*/
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
/**
* 也沒有什么邏輯,直接調(diào)用reload
*/
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
/**
* 直接實(shí)例化一個(gè)懶加載的迭代器
*/
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
- LazyIterator這個(gè)迭代器只需要關(guān)心hasNext()和next(), hasNext()里面又只是單純地調(diào)用hasNextService(). 不用說(shuō), next()里面肯定也只是單純地調(diào)用了nextService();
private boolean hasNextService() {
if (nextName != null) {
// nextName不為空,說(shuō)明加載過(guò)了,而且服務(wù)不為空
return true;
}
// configs就是所有名字為PREFIX + service.getName()的資源
if (configs == null) {
try {
// PREFIX是 /META-INF/services
// service.getName() 是接口的全限定名稱
String fullName = PREFIX + service.getName();
// loader == null, 說(shuō)明是bootstrap類加載器
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 通過(guò)名字加載所有文件資源
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
//遍歷所有的資源,pending用于存放加載到的實(shí)現(xiàn)類
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
//遍歷完所有的文件了,直接返回
return false;
}
// parse方法主要調(diào)用了parseLine,功能:
// 1. 分析每個(gè)PREFIX + service.getName() 目錄下面的所有文件
// 2. 判斷每個(gè)文件是否是合法的java類的全限定名稱,如果是就add到pending變量中
pending = parse(service, configs.nextElement());
}
// 除了第一次進(jìn)來(lái),后面每次調(diào)用都是直接到這一步了
nextName = pending.next();
return true;
}
- 再來(lái)看看nextService干了啥
private S nextService() {
// 校驗(yàn)一下
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 嘗試一下是否能加載該類
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,"Provider " + cn + " not found");
}
// 是不是service的子類,或者同一個(gè)類
if (!service.isAssignableFrom(c)) {
fail(service,"Provider " + cn + " not a subtype");
}
try {
// 實(shí)例化這個(gè)類, 然后向上轉(zhuǎn)一下
S p = service.cast(c.newInstance());
// 緩存起來(lái),避免重復(fù)加載
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,"Provider " + cn + " could not be instantiated",x);
}
throw new Error(); // This cannot happen
}
https://cloud.tencent.com/developer/article/1497777
https://juejin.im/post/5d9998d5f265da5bab5bc0f2#heading-5
https://www.cnblogs.com/itplay/p/9927892.html
https://www.cnblogs.com/LUA123/p/12460869.html
https://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html