寫在前面
SPI機制能夠非常方便的為某個接口動態(tài)指定其實現(xiàn)類象颖,在某種程度上,這也是某些框架具有高度可擴展性的基礎(chǔ)姆钉。今天说订,我們就從源碼級別深入探討下Java中的SPI機制。
SPI的概念
SPI在Java中的全稱為Service Provider Interface潮瓶,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機制陶冷,是Java提供的一套用來被第三方實現(xiàn)或者擴展的API,它可以用來啟用框架擴展和替換組件毯辅。
JAVA SPI = 基于接口的編程+策略模式+配置文件的動態(tài)加載機制
SPI的使用場景
Java是一種面向?qū)ο笳Z言埂伦,雖然Java8開始支持函數(shù)式編程和Stream,但是總體來說思恐,還是面向?qū)ο蟮恼Z言沾谜。在使用Java進行面向?qū)ο箝_發(fā)時,一般會推薦使用基于接口的編程胀莹,程序的模塊與模塊之前不會直接進行實現(xiàn)類的硬編碼基跑。而在實際的開發(fā)過程中,往往一個接口會有多個實現(xiàn)類描焰,各實現(xiàn)類要么實現(xiàn)的邏輯不同媳否,要么使用的方式不同,還有的就是實現(xiàn)的技術(shù)不同。為了使調(diào)用方在調(diào)用接口的時候篱竭,明確的知道自己調(diào)用的是接口的哪個實現(xiàn)類力图,或者說為了實現(xiàn)在模塊裝配的時候不用在程序里動態(tài)指明,這就需要一種服務(wù)發(fā)現(xiàn)機制室抽。Java中的SPI加載機制能夠滿足這樣的需求搪哪,它能夠自動尋找某個接口的實現(xiàn)類。
大量的框架使用了Java的SPI技術(shù)坪圾,如下:
(1)JDBC加載不同類型的數(shù)據(jù)庫驅(qū)動
(2)日志門面接口實現(xiàn)類加載晓折,SLF4J加載不同提供商的日志實現(xiàn)類
(3)Spring中大量使用了SPI
- 對servlet3.0規(guī)范
- 對ServletContainerInitializer的實現(xiàn)
- 自動類型轉(zhuǎn)換Type Conversion SPI(Converter SPI、Formatter SPI)等
(4)Dubbo里面有很多個組件兽泄,每個組件在框架中都是以接口的形成抽象出來漓概!具體的實現(xiàn)又分很多種,在程序執(zhí)行時根據(jù)用戶的配置來按需取接口的實現(xiàn)
SPI的使用
當服務(wù)的提供者病梢,提供了接口的一種實現(xiàn)后胃珍,需要在Jar包的META-INF/services/目錄下,創(chuàng)建一個以接口的名稱(包名.接口名的形式)命名的文件蜓陌,在文件中配置接口的實現(xiàn)類(完整的包名+類名)觅彰。
當外部程序通過java.util.ServiceLoader類裝載這個接口時,就能夠通過該Jar包的META/Services/目錄里的配置文件找到具體的實現(xiàn)類名钮热,裝載實例化填抬,完成注入。同時隧期,SPI的規(guī)范規(guī)定了接口的實現(xiàn)類必須有一個無參構(gòu)造方法飒责。
SPI中查找接口的實現(xiàn)類是通過java.util.ServiceLoader,而在java.util.ServiceLoader類中有一行代碼如下:
// 加載具體實現(xiàn)類信息的前綴仆潮,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目錄下
private static final String PREFIX = "META-INF/services/";
這也就是說宏蛉,我們必須將接口的配置文件寫到Jar包的META/Services/目錄下。
SPI實例
這里性置,給出一個簡單的SPI使用實例拾并,演示在Java程序中如何使用SPI動態(tài)加載接口的實現(xiàn)類。
注意:實例是基于Java8進行開發(fā)的鹏浅。
1.創(chuàng)建Maven項目
在IDEA中創(chuàng)建Maven項目spi-demo辟灰,如下:
2.編輯pom.xml
<?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">
<artifactId>spi-demo</artifactId>
<groupId>io.binghe.spi</groupId>
<packaging>jar</packaging>
<version>1.0.0-SNAPSHOT</version>
<modelVersion>4.0.0</modelVersion>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.創(chuàng)建類加載工具類
在io.binghe.spi.loader包下創(chuàng)建MyServiceLoader,MyServiceLoader類中直接調(diào)用JDK的ServiceLoader類加載Class篡石。代碼如下所示。
package io.binghe.spi.loader;
import java.util.ServiceLoader;
/**
* @author binghe
* @version 1.0.0
* @description 類加載工具
*/
public class MyServiceLoader {
/**
* 使用SPI機制加載所有的Class
*/
public static <S> ServiceLoader<S> loadAll(final Class<S> clazz) {
return ServiceLoader.load(clazz);
}
}
4.創(chuàng)建接口
在io.binghe.spi.service包下創(chuàng)建接口MyService西采,作為測試接口凰萨,接口中只有一個方法,打印傳入的字符串信息。代碼如下所示:
package io.binghe.spi.service;
/**
* @author binghe
* @version 1.0.0
* @description 定義接口
*/
public interface MyService {
/**
* 打印信息
*/
void print(String info);
}
5.創(chuàng)建接口的實現(xiàn)類
(1)創(chuàng)建第一個實現(xiàn)類MyServiceA
在io.binghe.spi.service.impl包下創(chuàng)建MyServiceA類胖眷,實現(xiàn)MyService接口武通。代碼如下所示:
package io.binghe.spi.service.impl;
import io.binghe.spi.service.MyService;
/**
* @author binghe
* @version 1.0.0
* @description 接口的第一個實現(xiàn)
*/
public class MyServiceA implements MyService {
@Override
public void print(String info) {
System.out.println(MyServiceA.class.getName() + " print " + info);
}
}
(2)創(chuàng)建第二個實現(xiàn)類MyServiceB
在io.binghe.spi.service.impl包下創(chuàng)建MyServiceB類,實現(xiàn)MyService接口珊搀。代碼如下所示:
package io.binghe.spi.service.impl;
import io.binghe.spi.service.MyService;
/**
* @author binghe
* @version 1.0.0
* @description 接口第二個實現(xiàn)
*/
public class MyServiceB implements MyService {
@Override
public void print(String info) {
System.out.println(MyServiceB.class.getName() + " print " + info);
}
}
6.創(chuàng)建接口文件
在項目的src/main/resources目錄下創(chuàng)建META/Services/目錄冶忱,在目錄中創(chuàng)建io.binghe.spi.service.MyService文件,注意:文件必須是接口MyService的全名境析,之后將實現(xiàn)MyService接口的類配置到文件中囚枪,如下所示:
io.binghe.spi.service.impl.MyServiceA
io.binghe.spi.service.impl.MyServiceB
7.創(chuàng)建測試類
在項目的io.binghe.spi.main包下創(chuàng)建Main類,該類為測試程序的入口類劳淆,提供一個main()方法链沼,在main()方法中調(diào)用ServiceLoader類加載MyService接口的實現(xiàn)類。并通過Java8的Stream將結(jié)果打印出來沛鸵,如下所示:
package io.binghe.spi.main;
import io.binghe.spi.loader.MyServiceLoader;
import io.binghe.spi.service.MyService;
import java.util.ServiceLoader;
import java.util.stream.StreamSupport;
/**
* @author binghe
* @version 1.0.0
* @description 測試的main方法
*/
public class Main {
public static void main(String[] args){
ServiceLoader<MyService> loader = MyServiceLoader.loadAll(MyService.class);
StreamSupport.stream(loader.spliterator(), false).forEach(s -> s.print("Hello World"));
}
}
8.測試實例
運行Main類中的main()方法括勺,打印出的信息如下所示:
io.binghe.spi.service.impl.MyServiceA print Hello World
io.binghe.spi.service.impl.MyServiceB print Hello World
Process finished with exit code 0
通過打印信息可以看出,通過Java SPI機制正確加載出接口的實現(xiàn)類曲掰,并調(diào)用接口的實現(xiàn)方法疾捍。
源碼解析
這里,主要是對SPI的加載流程涉及到的java.util.ServiceLoader的源碼的解析栏妖。
進入java.util.ServiceLoader的源碼乱豆,可以看到ServiceLoader類實現(xiàn)了java.lang.Iterable接口,如下所示底哥。
public final class ServiceLoader<S> implements Iterable<S>
說明ServiceLoader類是可以遍歷迭代的咙鞍。
java.util.ServiceLoader類中定義了如下的成員變量:
// 加載具體實現(xiàn)類信息的前綴,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目錄下
private static final String PREFIX = "META-INF/services/";
// 需要加載的接口
private final Class<S> service;
// 類加載器趾徽,用于加載以接口命名的文件中配置的接口的實現(xiàn)類
private final ClassLoader loader;
// 創(chuàng)建ServiceLoader時采用的訪問控制上下文環(huán)境
private final AccessControlContext acc;
// 用來緩存已經(jīng)加載的接口實現(xiàn)類续滋,其中,Key是接口實現(xiàn)類的完整類名孵奶,Value為實現(xiàn)類對象
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 用于延遲加載實現(xiàn)類的迭代器
private LazyIterator lookupIterator;
可以看到ServiceLoader類中定義了加載前綴為“META-INF/services/”疲酌,所以,接口文件必須要在項目的src/main/resources目錄下的META-INF/services/目錄下創(chuàng)建了袁。
從MyServiceLoader類調(diào)用ServiceLoader.load(clazz)方法進入源碼朗恳,如下所示:
//根據(jù)類的Class對象加載指定的類,返回ServiceLoader對象
public static <S> ServiceLoader<S> load(Class<S> service) {
//獲取當前線程的類加載器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
//動態(tài)加載指定的類载绿,將類加載到ServiceLoader中
return ServiceLoader.load(service, cl);
}
方法中調(diào)用了ServiceLoader.load(service, cl)方法粥诫,繼續(xù)跟蹤代碼,如下所示:
//通過ClassLoader加載指定類的Class崭庸,并將返回結(jié)果封裝到ServiceLoader對象中
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
return new ServiceLoader<>(service, loader);
}
可以看到ServiceLoader.load(service, cl)方法中怀浆,調(diào)用了ServiceLoader類的構(gòu)造方法谊囚,繼續(xù)跟進代碼,如下所示:
//構(gòu)造ServiceLoader對象
private ServiceLoader(Class<S> svc, ClassLoader cl) {
//如果傳入的Class對象為空执赡,則判處空指針異常
service = Objects.requireNonNull(svc, "Service interface cannot be null");
//如果傳入的ClassLoader為空镰踏,則通過ClassLoader.getSystemClassLoader()獲取,否則直接使用傳入的ClassLoader
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
繼續(xù)跟reload()方法沙合,如下所示奠伪。
//重新加載
public void reload() {
//清空保存加載的實現(xiàn)類的LinkedHashMap
providers.clear();
//構(gòu)造延遲加載的迭代器
lookupIterator = new LazyIterator(service, loader);
}
繼續(xù)跟進懶加載迭代器的構(gòu)造函數(shù),如下所示首懈。
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
可以看到绊率,會將需要加載的接口的Class對象和類加載器賦值給LazyIterator的成員變量。
當我們在程序中迭代獲取對象實例時猜拾,首先在成員變量providers中查找是否有緩存的實例對象即舌。如果存在則直接返回,否則調(diào)用lookupIterator延遲加載迭代器進行加載挎袜。
迭代器進行邏輯判斷的代碼如下所示:
//迭代ServiceLoader的方法
public Iterator<S> iterator() {
return new Iterator<S>() {
//獲取保存實現(xiàn)類的LinkedHashMap<String,S>的迭代器
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
//判斷是否有下一個元素
public boolean hasNext() {
//如果knownProviders存在元素顽聂,則直接返回true
if (knownProviders.hasNext())
return true;
//返回延遲加載器是否存在元素
return lookupIterator.hasNext();
}
//獲取下一個元素
public S next() {
//如果knownProviders存在元素,則直接獲取
if (knownProviders.hasNext())
return knownProviders.next().getValue();
//獲取延遲迭代器lookupIterator中的元素
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
LazyIterator加載類的流程如下代碼所示
//判斷是否擁有下一個實例
private boolean hasNextService() {
//如果擁有下一個實例盯仪,直接返回true
if (nextName != null) {
return true;
}
//如果實現(xiàn)類的全名為null
if (configs == null) {
try {
//獲取全文件名紊搪,文件相對路徑+文件名稱(包名+接口名)
String fullName = PREFIX + service.getName();
//類加載器為空,則通過ClassLoader.getSystemResources()方法獲取
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()) {
//如果configs中沒有更過的元素耀石,則直接返回false
if (!configs.hasMoreElements()) {
return false;
}
//解析包結(jié)構(gòu)
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
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");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
//通過c.newInstance()生成對象實例
S p = service.cast(c.newInstance());
//將生成的對象實例保存到緩存中(LinkedHashMap<String,S>)
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
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);
}
}
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的類爸黄,如下所示:
package java.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
public final class ServiceLoader<S> implements Iterable<S> {
// 加載具體實現(xiàn)類信息的前綴滞伟,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目錄下
private static final String PREFIX = "META-INF/services/";
// 需要加載的接口
private final Class<S> service;
// 類加載器,用于加載以接口命名的文件中配置的接口的實現(xiàn)類
private final ClassLoader loader;
// 創(chuàng)建ServiceLoader時采用的訪問控制上下文環(huán)境
private final AccessControlContext acc;
// 用來緩存已經(jīng)加載的接口實現(xiàn)類炕贵,其中梆奈,Key是接口實現(xiàn)類的完整類名,Value為實現(xiàn)類對象
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 用于延遲加載實現(xiàn)類的迭代器
private LazyIterator lookupIterator;
//重新加載
public void reload() {
//清空保存加載的實現(xiàn)類的LinkedHashMap
providers.clear();
//構(gòu)造延遲加載的迭代器
lookupIterator = new LazyIterator(service, loader);
}
//構(gòu)造ServiceLoader對象
private ServiceLoader(Class<S> svc, ClassLoader cl) {
//如果傳入的Class對象為空称开,則判處空指針異常
service = Objects.requireNonNull(svc, "Service interface cannot be null");
//如果傳入的ClassLoader為空亩钟,則通過ClassLoader.getSystemClassLoader()獲取,否則直接使用傳入的ClassLoader
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
private static void fail(Class<?> service, String msg, Throwable cause)
throws ServiceConfigurationError
{
throw new ServiceConfigurationError(service.getName() + ": " + msg,
cause);
}
private static void fail(Class<?> service, String msg)
throws ServiceConfigurationError
{
throw new ServiceConfigurationError(service.getName() + ": " + msg);
}
private static void fail(Class<?> service, URL u, int line, String msg)
throws ServiceConfigurationError
{
fail(service, u + ":" + line + ": " + msg);
}
// Parse a single line from the given configuration file, adding the name
// on the line to the names list.
//
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
List<String> names)
throws IOException, ServiceConfigurationError
{
String ln = r.readLine();
if (ln == null) {
return -1;
}
int ci = ln.indexOf('#');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 1;
}
private Iterator<String> parse(Class<?> service, URL u)
throws ServiceConfigurationError
{
InputStream in = null;
BufferedReader r = null;
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
return names.iterator();
}
// Private inner class implementing fully-lazy provider lookupload
private class LazyIterator
implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
//判斷是否擁有下一個實例
private boolean hasNextService() {
//如果擁有下一個實例鳖轰,直接返回true
if (nextName != null) {
return true;
}
//如果實現(xiàn)類的全名為null
if (configs == null) {
try {
//獲取全文件名清酥,文件相對路徑+文件名稱(包名+接口名)
String fullName = PREFIX + service.getName();
//類加載器為空,則通過ClassLoader.getSystemResources()方法獲取
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()) {
//如果configs中沒有更過的元素焰轻,則直接返回false
if (!configs.hasMoreElements()) {
return false;
}
//解析包結(jié)構(gòu)
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
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");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
//通過c.newInstance()生成對象實例
S p = service.cast(c.newInstance());
//將生成的對象實例保存到緩存中(LinkedHashMap<String,S>)
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
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);
}
}
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);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
//迭代ServiceLoader的方法
public Iterator<S> iterator() {
return new Iterator<S>() {
//獲取保存實現(xiàn)類的LinkedHashMap<String,S>的迭代器
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
//判斷是否有下一個元素
public boolean hasNext() {
//如果knownProviders存在元素,則直接返回true
if (knownProviders.hasNext())
return true;
//返回延遲加載器是否存在元素
return lookupIterator.hasNext();
}
//獲取下一個元素
public S next() {
//如果knownProviders存在元素昆雀,則直接獲取
if (knownProviders.hasNext())
return knownProviders.next().getValue();
//獲取延遲迭代器lookupIterator中的元素
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
//通過ClassLoader加載指定類的Class鹦马,并將返回結(jié)果封裝到ServiceLoader對象中
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
//根據(jù)類的Class對象加載指定的類胧谈,返回ServiceLoader對象
public static <S> ServiceLoader<S> load(Class<S> service) {
//獲取當前線程的類加載器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
//動態(tài)加載指定的類,將類加載到ServiceLoader中
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
ClassLoader prev = null;
while (cl != null) {
prev = cl;
cl = cl.getParent();
}
return ServiceLoader.load(service, prev);
}
/**
* Returns a string describing this service.
*
* @return A descriptive string
*/
public String toString() {
return "java.util.ServiceLoader[" + service.getName() + "]";
}
}
SPI總結(jié)
最后荸频,對Java提供的SPI機制進行簡單的總結(jié)。
優(yōu)點:
能夠?qū)崿F(xiàn)項目解耦客冈,使得第三方服務(wù)模塊的裝配控制的邏輯與調(diào)用者的業(yè)務(wù)代碼分離旭从,而不是耦合在一起。應(yīng)用程序可以根據(jù)實際業(yè)務(wù)情況啟用框架擴展或替換框架組件场仲。
缺點:
- 多個并發(fā)多線程使用ServiceLoader類的實例是不安全的
- 雖然ServiceLoader也算是使用的延遲加載和悦,但是基本只能通過遍歷全部獲取,也就是接口的實現(xiàn)類全部加載并實例化一遍渠缕。