SPI的全稱(chēng)是Service Provider Interface, 直譯過(guò)來(lái)就是"服務(wù)提供接口", 聽(tīng)起來(lái)挺別扭的, 所以我試著去理解了一下, 就將它翻譯為"服務(wù)提供商接口"吧.
我們都知道, 一個(gè)接口是可以有很多種實(shí)現(xiàn)的. 例如搜索,可以是搜索系統(tǒng)的硬盤(pán),也可以是搜索數(shù)據(jù)庫(kù).系統(tǒng)的設(shè)計(jì)者為了降低耦合,并不想在硬編碼里面寫(xiě)死具體的搜索方式,而是希望由服務(wù)提供者來(lái)選擇使用哪種搜索方式, 這個(gè)時(shí)候就可以選擇使用SPI機(jī)制.
JDK中的SPI
讓我們通過(guò)一個(gè)非常簡(jiǎn)單的例子,來(lái)認(rèn)識(shí)一下java里面的SPI機(jī)制.
- 定義一個(gè)搜索接口Search
package com.north.spilat.service;
import java.util.List;
public interface Search {
List<String> search(String keyword);
}
- 實(shí)現(xiàn)接口從數(shù)據(jù)庫(kù)查詢(xún)
package com.north.spilat.service.impl;
import com.north.spilat.service.Search;
import java.util.List;
/** * @author laihaohua */
public class DatabaseSearch implements Search {
@Override public List<String> search(String keyword) {
System.out.println("now use database search. keyword:" + keyword); return null;
}
}
- 實(shí)現(xiàn)接口從文件系統(tǒng)查詢(xún)
package com.north.spilat.service.impl;
import com.north.spilat.service.Search;
import java.util.List;
/** * @author laihaohua */
public class FileSearch implements Search {
@Override public List<String> search(String keyword) {
System.out.println("now use file system search. keyword:" + keyword); return null;
}
}
- 在
src\main\resources創(chuàng)建一個(gè)目錄META-INF\services\com.north.spilat.service.Search - 然后 在
com.north.spilat.service.Search下面創(chuàng)建兩個(gè)文件,以上面接口的具體實(shí)現(xiàn)類(lèi)的全限定名稱(chēng)為文件名,即:
com.north.spilat.service.impl.DatabaseSearch
com.north.spilat.service.impl.FileSearch
整個(gè)工程目錄如下:
image
- 在
新建一個(gè)main方法測(cè)試一下
package com.north.spilat.main;
import com.north.spilat.service.Search;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Main { public static void main(String[] args) {
System.out.println("Hello World!");
ServiceLoader<Search> s = ServiceLoader.load(Search.class);
Iterator<Search> searchList = s.iterator(); while (searchList.hasNext()) {
Search curSearch = searchList.next();
curSearch.search("test");
}
}
}
運(yùn)行一下,輸出如下:
Hello World!now use database search. keyword:testnow use file system search. keyword:test
如你所見(jiàn), SPI機(jī)制已經(jīng)定義好了加載服務(wù)的流程框架, 你只需要按照約定, 在META-INF/services目錄下面, 以接口的全限定名稱(chēng)為名創(chuàng)建一個(gè)文件夾(com.north.spilat.service.Search), 文件夾下再放具體的實(shí)現(xiàn)類(lèi)的全限定名稱(chēng)(com.north.spilat.service.impl.DatabaseSearch), 系統(tǒng)就能根據(jù)這些文件,加載不同的實(shí)現(xiàn)類(lèi).
再回到上面的main方法,其實(shí)沒(méi)有什么特別的,除了一句
ServiceLoader.load(Search.class);
ServiceLoader.class是一個(gè)工具類(lèi),根據(jù)META-INF/services/xxxInterfaceName下面的文件名,加載具體的實(shí)現(xiàn)類(lèi).
從load(Search.class)進(jìn)去,我們來(lái)扒一下這個(gè)類(lèi)
- 可以看到,里面并沒(méi)有很多邏輯,主要邏輯都交給了LazyIterator這類(lèi)
/* *入口, 獲取一下當(dāng)前類(lèi)的類(lèi)加載器,然后調(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è)也沒(méi)有什么邏輯,直接調(diào)用構(gòu)造方法 */
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
/** * 也沒(méi)有什么邏輯,直接調(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就是所有的實(shí)現(xiàn)類(lèi)文件名字
if (configs == null) {
try {
// PREFIX是 /META-INF/services
// service.getName() 是接口的全限定名稱(chēng)
String fullName = PREFIX + service.getName();
// loader == null, 說(shuō)明是bootstrap類(lèi)加載器
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()) {
// 該目錄下什么文件都沒(méi)有
return false;
}
//就是判斷一下configs.nextElement()的格式是不是對(duì)的
pending = parse(service, configs.nextElement());
}
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 {
// 嘗試一下是否能加載該類(lèi)
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,"Provider " + cn + " not found");
}
// 是不是service的子類(lèi),或者同一個(gè)類(lèi)
if (!service.isAssignableFrom(c)) {
fail(service,"Provider " + cn + " not a subtype");
}
try {
// 實(shí)例化這個(gè)類(lèi), 然后向上轉(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
}
從上面的代碼就可以看出來(lái), 所謂的懶加載,就是等到調(diào)用hasNext()再查找服務(wù), 調(diào)用next()才實(shí)例化服務(wù)類(lèi).
JDK的SPI大概就是這么一個(gè)邏輯了, 服務(wù)提供商安裝約定,將具體的實(shí)現(xiàn)類(lèi)名稱(chēng)放到/META-INF/services/xxx下, ServiceLoader就可以根據(jù)服務(wù)提供者的意愿, 加載不同的實(shí)現(xiàn)了, 避免硬編碼寫(xiě)死邏輯, 從而達(dá)到解耦的目的.
當(dāng)然, 從上面這個(gè)簡(jiǎn)單的例子可能大家會(huì)看不出來(lái),SPI是如何達(dá)到解耦的效果的. 所以下面, 我們一起來(lái)看看,開(kāi)源框架中是怎么利用SPI機(jī)制來(lái)解耦的. 體會(huì)一下SPI的魅力.
springboot 中的SPI
以前還在實(shí)習(xí)的時(shí)候,老大就跟我說(shuō)過(guò)一段話(huà),他說(shuō)你沒(méi)事可以多點(diǎn)研究開(kāi)源框架,因?yàn)檫@些開(kāi)源代碼每天都不知道被人擼幾遍,所以他們的代碼從設(shè)計(jì)到實(shí)現(xiàn),都是非常優(yōu)秀的,可以從中學(xué)到不少東西.
而spring框架這些年來(lái),基本上可以說(shuō)是開(kāi)源界扛把子,江湖上無(wú)人不知無(wú)人不曉.其源碼的設(shè)計(jì)也是出了名的優(yōu)雅,超高拓展性超低耦合性.
那它是怎么解耦的呢? 拓展點(diǎn)機(jī)制便是其中法寶之一(注意用詞,是之一 哈-_-)
從神奇的starter說(shuō)起
剛剛接觸springboot的時(shí)候, 真的覺(jué)得各種spring-xx-starter和xx-spring-starter非常的神奇. 為什么在pom文件添加一個(gè)依賴(lài)就能引入一個(gè)復(fù)雜的插件了呢? 帶著這個(gè)疑問(wèn),我開(kāi)始了我的走進(jìn)科學(xué)之旅.
dubbo框架聽(tīng)說(shuō)在國(guó)內(nèi)用的公司挺多的,所以這里, 我們就以dubbo-spring-boot-starter為例,來(lái)看看springboot中是如何高效解耦的.
回想一下, 如果我們要在springboot工程里面引入dubbo模塊, 需要怎么做.
- 在pom文件引入dubbo-spring-boot-starter的依賴(lài).
<dependency>
<groupId>com.alibaba.spring.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency></pre>
-
在application.properties文件配置好dubbo相關(guān)參數(shù)image.png
spring.dubbo.server=true spring.dubbo.application.name=north-spilat-server
#
spring.dubbo.registry.id=defaultRegistry
#
spring.dubbo.registry.address=127.0.0.1 #
spring.dubbo.registry.port=2181 #
spring.dubbo.registry.protocol=zookeeper
#
spring.dubbo.protocol.name=dubbo
#
spring.dubbo.protocol.port=20881 #
spring.dubbo.module.name=north-spilat-server
#
spring.dubbo.consumer.check=false #
spring.dubbo.provider.timeout=3000 #
spring.dubbo.consumer.retries=0 #
spring.dubbo.consumer.timeout=3000
- 在spring-boot的啟動(dòng)類(lèi)加上對(duì)應(yīng)的注解
package com.north.spilat.main;
import com.alibaba.dubbo.spring.boot.annotation.EnableDubboConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
/** * @author laihaohua */
@SpringBootApplication
@ComponentScan(basePackages = {"com.north.*"})
@EnableDubboConfiguration public class SpringBootMain { public static void main(String[] args) {
SpringApplication.run(SpringBootMain.class, args);
}
}
- 定義接口, 實(shí)現(xiàn)并調(diào)用
接口
package com.north.spilat.service;
/** * @author laihaohua */
public interface DubboDemoService {
String test(String params);
}
實(shí)現(xiàn)接口
package com.north.spilat.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.north.spilat.service.DubboDemoService;
import org.springframework.stereotype.Repository;
/** * @author laihaohua */
@Service
@Repository("dubboDemoService")
public class DubboDemoServiceImpl implements DubboDemoService {
@Override
public String test(String params) {
return System.currentTimeMillis() + "-" + params ;
}
}
寫(xiě)個(gè)controller調(diào)用dubbo接口
package com.north.spilat.controller;
import com.north.spilat.service.DubboDemoService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/** * @author laihaohua */
@RestController public class HelloWorldController {
@Resource
private DubboDemoService dubboDemoService;
@RequestMapping("/saveTheWorld")
public String index(String name) {
return dubboDemoService.test(name);
}
}
做完以上4步(zookeeper等環(huán)境自己裝一下)后, 啟動(dòng)SpringBootMain類(lèi), 一個(gè)帶有dubbo模塊的springboot工程就這樣搭好了, 真的就這么簡(jiǎn)單.
但是事情越簡(jiǎn)單,背后就越不簡(jiǎn)單.一定是有人默默地為我們做了很多事. 這個(gè)人就是"dubbo-spring-boot-starter"
dubbo-spring-boot-starter的奧秘

上圖是dubbo-spring-boot-starter.jar包的結(jié)構(gòu). 內(nèi)容還真不少, 但是聰明的你肯定想到了, 既然我們上一節(jié)說(shuō)到了SPI是跟META-INF息息相關(guān)的,那我們這一節(jié)也必然是這樣. 因此, 這里我們先看一下META-INF目錄下面有什么.
dubbo/com.alibaba.dubbo.rpc.InvokerListener
dubbosubscribe=com.alibaba.dubbo.spring.boot.listener.ConsumerSubscribeListener
這個(gè)目錄下的文件只有一行,看著和上面的jdk的SPI真的是像.沒(méi)錯(cuò), 這的確是一種拓展點(diǎn), 是dubbo里面的一種拓展點(diǎn)約定, 但是我們這里也不深入, 有機(jī)會(huì)可以另開(kāi)一篇討論一下(這題超綱了)
- spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.dubbo.spring.boot.DubboAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboProviderAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboConsumerAutoConfiguration
org.springframework.context.ApplicationListener=\
com.alibaba.dubbo.spring.boot.context.event.DubboBannerApplicationListener
哇哇哇,文件就是以spring命名,文件內(nèi)容還涉及到這么多spring類(lèi). 確認(rèn)過(guò)眼神, 我遇上對(duì)的...文件. 但是別急, 下面還有一個(gè)spring.providers文件
- spring.providers
provides: dubbo-spring-boot-starter
spring.providers就這么簡(jiǎn)單的一句, 有點(diǎn)失望了.所以我們還是來(lái)關(guān)注一下spring.factories吧.

在IDEA里面搜一下spring.factories這個(gè)文件. 不搜不知道, 一搜嚇一跳. 原來(lái)基本上每一個(gè)springboot相關(guān)的jar包里面都會(huì)有一個(gè)這樣的文件.
物理學(xué)家在做實(shí)驗(yàn)之前, 總是喜歡推理一番, 得到一個(gè)預(yù)測(cè)的結(jié)論, 然后再通過(guò)實(shí)驗(yàn)結(jié)果來(lái)證實(shí)或推翻預(yù)測(cè)的結(jié)論.
因此, 基于JDK里面的SPI機(jī)制, 在這里我們也可以做一個(gè)大膽的預(yù)測(cè):spring框架里面一定是有一個(gè)類(lèi)似于ServiceLoader的類(lèi), 專(zhuān)門(mén)從META-INF/spring.factories里面的配置,加載特定接口的實(shí)現(xiàn).
結(jié)果不用說(shuō), 這個(gè)預(yù)測(cè)肯定是準(zhǔn)確, 不然我上面這么多字不就白寫(xiě)啦. 但是怎么證明我們的預(yù)測(cè)是準(zhǔn)確的呢. 讓我們也來(lái)做一次"實(shí)驗(yàn)".
springboot的啟動(dòng)過(guò)程
要弄清楚springboot的啟動(dòng)過(guò)程, 最好的辦法就研讀它的源碼了.
而springboot的代碼還是非常"人性化"的, 不像其他開(kāi)源框架,不知道從何看起.springboot明明確確地告訴你了, 它的入口就是main方法.因此, 讀springboot的代碼, 還算是比較愜意的.

上圖就是一個(gè)springboot工程的啟動(dòng)過(guò)程.首先是連續(xù)兩個(gè)重載的靜態(tài)run方法, 靜態(tài)run方法內(nèi)部會(huì)調(diào)用構(gòu)造方法實(shí)例化SpringApplication對(duì)象, 構(gòu)造方法內(nèi)部是調(diào)用initialiaze()進(jìn)行初始化的,實(shí)例化,再調(diào)用一個(gè)成員方法run()來(lái)正式啟動(dòng). 可見(jiàn), 整個(gè)啟動(dòng)過(guò)程主要的邏輯都在initialiaze方法和成員run方法內(nèi)部了.
看一下initialiaze()的邏輯
@SuppressWarnings({ "unchecked", "rawtypes" })
private void initialize(Object[] sources) {
// sources一般是Configuration類(lèi)或main方法所在類(lèi) // 可以有多個(gè)
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
// 判斷是否是web環(huán)境
// classLoader能加載到
// "javax.servlet.Servlet",
// "org.springframework.web.context.ConfigurableWebApplicationContext"
// 這兩個(gè)類(lèi)就是web環(huán)境
this.webEnvironment = deduceWebEnvironment();
// 加載initializers 和listeners // getSpringFactoriesInstances顧名思義,
// 就是加載某個(gè)接口的工廠(chǎng)實(shí)例,
// 看起來(lái)像是我們要找的"ServiceLoader"了
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 找到main方法所在的類(lèi)
this.mainApplicationClass = deduceMainApplicationClass();
}
運(yùn)氣還算不錯(cuò),"嫌疑犯"getSpringFactoriesInstances就露出水面了, 來(lái)看看它的邏輯
/** * 參數(shù)type就是要加載的接口的class */
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) { // 直接調(diào)用重載方法
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,Class<?>[] parameterTypes, Object... args) {
// 獲取當(dāng)前線(xiàn)程的classLoader
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
// 翻譯一下原文注釋就是用names來(lái)去重
// 注意這里, 我們尋找的"ServiceLoader"終于出現(xiàn)了
// 就是SpringFactoriesLoader
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader)); // 是用java反射來(lái)實(shí)例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 根據(jù)@Order注解來(lái)排一下序
AnnotationAwareOrderComparator.sort(instances);
// 返回這個(gè)接口的所有實(shí)現(xiàn)實(shí)例
return instances;
}
然后很快就找到了我們想找的SpringFactoriesLoader, 而且這個(gè)類(lèi)非常小, 代碼比JDK的ServiceLoader還少. 那我們仔細(xì)看一下他里面都有啥.
- FACTORIES_RESOURCE_LOCATION 正是指向我們上面所說(shuō)的
META-INF/spring.factories - loadFactories, 從META-INF/spring.factories查找指定的接口實(shí)現(xiàn)類(lèi)并實(shí)例化, 其中查找是通過(guò)調(diào)用loadFactoryNames
- loadFactoryNames從指定的位置查找特定接口的實(shí)現(xiàn)類(lèi)的全限定名稱(chēng)
- instantiateFactory 實(shí)例化
這個(gè)類(lèi)就是springboot里面的"ServiceLoader",它提供了一種機(jī)制,可以讓服務(wù)提供商指定某種接口的實(shí)現(xiàn)(可以是多個(gè)),例如上面的ApplicationContextInitializer.class和ApplicationListener.class接口, 如果我們想在我們的模塊里面指定我們的實(shí)現(xiàn),或者想在現(xiàn)有的代碼上加上我們的某個(gè)實(shí)現(xiàn),就可以在/META-INF/spring.factories里面指定. 等一下下面我會(huì)寫(xiě)一個(gè)具體的例子, 可以讓大家更好的理解一下.
/** * 省略import**/
public abstract class SpringFactoriesLoader {
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
/** * The location to look for factories.
* 查找工廠(chǎng)實(shí)現(xiàn)類(lèi)的位置
* <p>Can be present in multiple JAR files.
* 可以在多個(gè)jar包中
* 這不就是我們一直在尋找的META-INF/spring.factories嘛
* 終于找到了 */
public static final String FACTORIES_RESOURCE_LOCATION ="META-INF/spring.factories";
/** * 查找 并實(shí)例化指定的工廠(chǎng)類(lèi)實(shí)現(xiàn) */
public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
Assert.notNull(factoryClass, "'factoryClass'
must not be null");
ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 最終是調(diào)用loadFactoryNames
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
List<T> result = new ArrayList<T>(factoryNames.size()); for (String factoryName : factoryNames) {
// 一個(gè)個(gè)的實(shí)例化
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
// 排序
AnnotationAwareOrderComparator.sort(result);
return result;
}
/** * 從META-INF/spring.factories查找指定接口的實(shí)現(xiàn)類(lèi)的
* 全限定類(lèi)名稱(chēng) */
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
// 接口的類(lèi)名稱(chēng)
String factoryClassName = factoryClass.getName();
try {
//從META-INF/spring.factories加載文件資源
Enumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
// 一個(gè)url代表一個(gè)spring.factories文件
URL url = urls.nextElement();
// 加載所有的屬性, 一般是 xxx接口=impl1,impl2 這種形式的
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
// 根據(jù)接口名獲取的類(lèi)似"impl1,impl2"的字符串
String factoryClassNames = properties.getProperty(factoryClassName)
// 以逗號(hào)分隔,轉(zhuǎn)化成列表
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
// 返回實(shí)現(xiàn)類(lèi)名的列表
return result;
} catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
/** * 根據(jù)類(lèi)名的全限定名稱(chēng)實(shí)例化 */
@SuppressWarnings("unchecked")
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
try {
// 查找類(lèi)
Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
// 校驗(yàn)是不是該接口類(lèi)或該接口類(lèi)的實(shí)現(xiàn)類(lèi)
if (!factoryClass.isAssignableFrom(instanceClass)) {
throw new IllegalArgumentException( "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
}
Constructor<?> constructor = instanceClass.getDeclaredConstructor();
ReflectionUtils.makeAccessible(constructor); // 反射實(shí)例化
return (T) constructor.newInstance();
} catch (
Throwable ex) { throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
}
}
}
看完SpringFactoriesLoader這個(gè)類(lèi), initialize()方法的邏輯也就看完了. 接著再看另外一個(gè)重要方法run(String... args)
/** * Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext} */
public ConfigurableApplicationContext run(String... args) {
// 用于監(jiān)測(cè)啟動(dòng)時(shí)長(zhǎng)等等
StopWatch stopWatch = new StopWatch();
stopWatch.start(); // springboot的上下文
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
// 配置headless模式
configureHeadlessProperty();
// 啟動(dòng)監(jiān)聽(tīng)器, 可以配置到spring.factories中去
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 封裝參數(shù)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 配置environment
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// 打印banner
Banner printedBanner = printBanner(environment);
// 創(chuàng)建上下文
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
// 先初始化上下文
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// spring 經(jīng)典的refresh()過(guò)程, 大部分的邏輯都在里面
// 這里不再深入, 讀者可以自行研讀代碼或搜索引擎
refreshContext(context);
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
} return context;
} catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex); throw new IllegalStateException(ex);
}
}
這個(gè)方法就是springboot啟動(dòng)的主要邏輯了,內(nèi)容很多,如果要全部說(shuō)清楚的話(huà), 恐怕再寫(xiě)幾遍博客也說(shuō)不完(給人家springboot一點(diǎn)最起碼的尊重好不好, 想一篇文章就理解透徹人家整個(gè)框架,人家不要面子的呀).所以這里就不會(huì)再深入,對(duì)于本文,只要知道這個(gè)run()方法是啟動(dòng)的主要邏輯就可以了, 另外記住
context = createApplicationContext();
refreshContext(context);
這兩行代碼,等下我們還會(huì)看到它的.
dubbo-spring-boot-starter的原理
上面說(shuō)了很多, 但是為什么springboot引入一個(gè)starter的依賴(lài),就能引入一個(gè)復(fù)雜的模塊內(nèi). 這里通過(guò)dubbo-spring-boot-starter來(lái)研究一下.
我們回顧一下dubbo-spring-boot-starter里面spring.factories. 可以發(fā)現(xiàn)里面配置了兩個(gè)接口, 一個(gè)是EnableAutoConfiguration,一個(gè)是ApplicationListener.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.dubbo.spring.boot.DubboAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboProviderAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboConsumerAutoConfiguration
org.springframework.context.ApplicationListener=\
com.alibaba.dubbo.spring.boot.context.event.DubboBannerApplicationListener
監(jiān)聽(tīng)器看名稱(chēng)就知道了是用于啟動(dòng)的時(shí)候打印banner, 所以這里暫時(shí)不看, 我們先來(lái)看一下EnableAutoConfiguration是哪里用到的.
從main方法開(kāi)始一路debug,終于在A(yíng)utoConfigurationImportSelector類(lèi)中發(fā)現(xiàn)了一行代碼:
SpringFactoriesLoader.loadFactoryNames( getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())
其中g(shù)etSpringFactoriesLoaderFactoryClass()就是返回EnableAutoConfiguration.class
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
/** * Return the class used by {@link SpringFactoriesLoader} to load configuration
* candidates.
* @return the factory class */
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
如下圖可以發(fā)現(xiàn),EnableAutoConfiguration.class的實(shí)現(xiàn)會(huì)有很多, 只要你在spring.fatories配置了,它都會(huì)給你加載進(jìn)來(lái)

加載了之后,又干嘛呢, 往下看,可以發(fā)現(xiàn)大概流程是這樣:
- this.reader.loadBeanDefinitions(configClasses); 把這些類(lèi)讀進(jìn)來(lái)準(zhǔn)備解析
- registerBeanDefinition注冊(cè)到beanDefinitionNames
- spring的refresh()最后有一步是finishBeanFactoryInitialization(beanFactory), 這一步時(shí)初始化所有的單例對(duì)象, 最后會(huì)從beanDefinitionNames讀取所有的BeanDefinition,也包括了上面的所有EnableAutoConfiguration實(shí)現(xiàn), 進(jìn)行實(shí)例化
- 實(shí)例化EnableAutoConfiguration的具體實(shí)現(xiàn)的時(shí)候,會(huì)執(zhí)行里面的具體邏輯, 以DubboAutoConfiguration為例, 會(huì)定義DubboServer和DubboHealthIndicator兩個(gè)bean, 就可以把dubbo接入進(jìn)來(lái)了.
實(shí)現(xiàn)一個(gè)spring-boot-starter
清楚了原理之后, 要實(shí)現(xiàn)一個(gè)自己的starter就很簡(jiǎn)單了.
假設(shè)我有一個(gè)組件,非常牛逼,具有拯救世界的能力, 你的系統(tǒng)接入后,也就具有了拯救世界的能力了. 那怎么讓你的spring-boot系統(tǒng)可以快速接入這個(gè)牛逼的組件呢. 我來(lái)實(shí)現(xiàn)一個(gè)starter, 你依賴(lài)我這個(gè)starter就可以了
首先定義一個(gè)拯救世界的接口
package com.north.lat.service; /** * @author laihaohua */
public interface SaveTheWorldService { /** * 拯救世界
* @param name 留名
* @return
*/ String saveTheWorld(String name);
}
抽象類(lèi)
package com.north.lat.service;
import lombok.extern.log4j.Log4j;
import java.util.Random;
/** * @author laihaohua */
@Log4j
public abstract class AbstractSaveTheWorldService implements SaveTheWorldService {
private final static Random RANDOM = new Random();
private final static String SUCCESS_MSG = "你不要問(wèn)這是什么, 總之就好厲害.";
private final static String FAIL_MSG = "拯救世界是個(gè)高風(fēng)險(xiǎn)行業(yè)";
@Override public String saveTheWorld(String name) {
int randomInt = RANDOM.nextInt(100);
String msg; if((randomInt + 1) > getDieRate()){
msg = SUCCESS_MSG +"," + name + "拯救了這個(gè)世界!";
}else{
msg = FAIL_MSG + "," + name + ",你失敗了,下輩子再來(lái)吧";
}
log.info(msg);
return msg;
}
/** * 指定死亡率
* @return
*/
public abstract int getDieRate();
}
以英雄角色去拯救世界
package com.north.lat.service.impl; import com.north.lat.service.AbstractSaveTheWorldService;
/** * 英雄拯救世界
* @author laihaohua */
public class HeroSaveTheWorldImpl extends AbstractSaveTheWorldService {
private final static int DIE_RATE = 1;
@Override
public int getDieRate() {
return DIE_RATE;
}
}
普通人去拯救世界
package com.north.lat.service.impl;
import com.north.lat.service.AbstractSaveTheWorldService;
/** * 普通人拯救世界
* @author laihaohua */
public class CommonSaveTheWorldServiceImpl extends AbstractSaveTheWorldService {
private final static int DIE_RATE = 99;
@Override
public int getDieRate() {
return DIE_RATE;
}
}
好, 我們這個(gè)超級(jí)牛逼的組件就誕生了, 下面為接入springboot準(zhǔn)備一下, 實(shí)現(xiàn)一個(gè)NbAutoConfiguration如下:
package com.north.lat;
import com.north.lat.service.SaveTheWorldService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.SpringFactoriesLoader;
import java.util.List;
/**
* @author laihaohua
* 注入environment和applicationContext 以便做一些后續(xù)操作
*/
@Configuration
@ConditionalOnClass(SaveTheWorldService.class)
public class NbAutoConfiguration implements EnvironmentAware,ApplicationContextAware,BeanDefinitionRegistryPostProcessor {
private Environment environment;
private ApplicationContext applicationContext;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 我這里是從spring.factories加載了SaveTheWorldService的所有實(shí)現(xiàn),
List<SaveTheWorldService> saveTheWorldServices = SpringFactoriesLoader.loadFactories(SaveTheWorldService.class, this.getClass().getClassLoader());
// 然后用BeanDefinitionRegistry 注冊(cè)到BeanDefinitions
saveTheWorldServices.forEach(saveTheWorldService->{
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(saveTheWorldService.getClass());
beanDefinition.setLazyInit(false);
beanDefinition.setAbstract(false);
beanDefinition.setAutowireCandidate(true);
beanDefinition.setScope("singleton");
registry.registerBeanDefinition(saveTheWorldService.getClass().getSimpleName(), beanDefinition);
});
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
再配置一下spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.north.lat.NbAutoConfiguration
com.north.lat.service.SaveTheWorldService=\
com.north.lat.service.impl.CommonSaveTheWorldServiceImpl,com.north.lat.service.impl.HeroSaveTheWorldImpl
這樣就完成了,項(xiàng)目結(jié)構(gòu)如下圖所示:

那該怎么接入呢? 我們?cè)趧倓偟膕pilat工程接入一下試試:
依賴(lài)jar包,這樣就完成接入了
<dependency>
<groupId>com.north.lat</groupId>
<artifactId>niubility-spring-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency></pre>
所謂的完成接入是指, spring中已經(jīng)注入了SaveTheWorldService的所有實(shí)現(xiàn), 即CommonSaveTheWorldServiceImpl和HeroSaveTheWorldImpl. 我們?cè)赾ontroller中調(diào)用一下
package com.north.spilat.controller;
import com.north.lat.service.SaveTheWorldService;
import com.north.lat.service.impl.CommonSaveTheWorldServiceImpl;
import com.north.lat.service.impl.HeroSaveTheWorldImpl;
import com.north.spilat.service.DubboDemoService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/** * @author laihaohua */
@RestController
public class HelloWorldController {
@Resource
private CommonSaveTheWorldServiceImpl commonSaveTheWorldService;
@Resource
private HeroSaveTheWorldImpl heroSaveTheWorld;
@Resource
private DubboDemoService dubboDemoService; private static final String HERO = "laihaohua";
@RequestMapping("/saveTheWorld")
public String index(String name) {
SaveTheWorldService saveTheWorldService;
if(HERO.equals(name)){
saveTheWorldService = heroSaveTheWorld;
}else {
saveTheWorldService = commonSaveTheWorldService;
}
dubboDemoService.test(name);
return saveTheWorldService.saveTheWorld(name);
}
}
運(yùn)行結(jié)果如下:


最后,如果后續(xù)版本有了一個(gè)更厲害的SaveTheWorldService的實(shí)現(xiàn), 那我直接就spring.factories里面新增一個(gè)配置,然后調(diào)用方只需要改動(dòng)版本號(hào)即可, 從而實(shí)現(xiàn)了高度解耦
總結(jié)
SPI機(jī)制在各種開(kāi)源框架中都是非常常見(jiàn)的, 有的簡(jiǎn)單點(diǎn)的就直接采用了JDK中的ServiceLoader,如IntelliJ IDEA的插件開(kāi)發(fā)中就應(yīng)用到了. 有些框架復(fù)雜一點(diǎn)的, JDK的SPI已經(jīng)滿(mǎn)足不了, 就自己改造一下, 如spring-boot的SpringFactoriesLoader和dubbo中的ExtensionLoader等, 但是其實(shí)背后的原理都是大同小異.
因此, 了解熟悉一下這些機(jī)制, 一方面可以讓我們更清楚開(kāi)源框架的運(yùn)行原理,少走彎路; 另一方面,也可以作為我們?nèi)粘?xiě)代碼和系統(tǒng)設(shè)計(jì)的一種參考,從而寫(xiě)出更加優(yōu)雅的代碼.
最后,畢竟個(gè)人認(rèn)知有限, 若文中有錯(cuò)誤之處,還望提出,謝謝大家支持。
原文:https://www.cnblogs.com/lhh-north/p/9571441.html
本文所有代碼可以參見(jiàn):
https://github.com/NorthWard/spilat
https://github.com/NorthWard/niubility-spring-starter

