什么是SPI
SPI全稱Service Provider Interface,是Java提供的一種接口擴(kuò)展機(jī)制。通過該機(jī)制可以將接口的定義與接口的實現(xiàn)分離,實現(xiàn)代碼解耦。
使用方式
SPI的使用方法很簡單,只需要按如下步驟即可:
-
定義接口
package cn.bdqfork.spi; /** * @author bdq * @since 2020/3/2 */ public interface UserService { void sayHello(); } -
實現(xiàn)接口
package cn.bdqfork.spi; /** * @author bdq * @since 2020/3/2 */ public class UserServiceImpl implements UserService { @Override public void sayHello() { System.out.println("hello"); } } -
編寫擴(kuò)展文件
在META-INF/services目錄下創(chuàng)建一個以接口全類名命名的文件,即com.test.service.UserService文件。
- src -main -resources - META-INF - services - cn.bdqfork.spi.UserService文件的內(nèi)容為實現(xiàn)類的全類名。
cn.bdqfork.spi.UserServiceImpl -
使用ServiceLoader加載服務(wù)
package cn.bdqfork.spi; import java.util.ServiceLoader; /** * @author bdq * @since 2020/3/2 */ public class Main { public static void main(String[] args) { ServiceLoader<UserService> userServices = ServiceLoader.load(UserService.class); for (UserService userService : userServices) { userService.sayHello(); } } }
SPI的缺點(diǎn)
雖然SPI機(jī)制可以很方便的將接口與其實現(xiàn)分離,但是卻有兩個缺點(diǎn):
- 在ServiceLoader加載的時候,會一次性將所有的Service都加載到JVM中,包括并不會用到的一些擴(kuò)展。
- 多線程并發(fā)訪問的時候會有線程問題。
SPI的實現(xiàn)原理
SPI機(jī)制的原理實際上不是很難,整個加載擴(kuò)展服務(wù)的過程如下:
- 掃描META-INF/services目錄下的文件。
- 讀取文件內(nèi)容,加載服務(wù)實現(xiàn)的Class。
- 實例化服務(wù)實現(xiàn)。
- 返回服務(wù)實例。
實現(xiàn)一個簡單SPI
了解了SPI的實現(xiàn)原理之后,便可以很簡單的實現(xiàn)一個SPI,下面筆者介紹一下筆者實現(xiàn)的一個SPI,參考了Dubbo的SPI實現(xiàn),但整體原理還是差不多的。
首先定義一個ExtensionLoader類以及相關(guān)屬性:
/**
* SPI擴(kuò)展
*
* @author bdq
* @since 2019-08-20
*/
public class ExtensionLoader<T> {
/**
* 掃描路徑
*/
private static final String PREFIX = "META-INF/extensions/";
/**
* 緩存
*/
private static final Map<String, ExtensionLoader<?>> CACHES = new ConcurrentHashMap<>();
/**
* 擴(kuò)展Class名稱緩存
*/
private final Map<Class<T>, String> classNames = new ConcurrentHashMap<>();
/**
* 擴(kuò)展Class緩存
*/
private final Map<String, Class<T>> extensionClasses = new ConcurrentHashMap<>();
/**
* 擴(kuò)展實例緩存
*/
private volatile Map<String, T> cacheExtensions;
/**
* 默認(rèn)擴(kuò)展名
*/
private String defaultName;
/**
* 擴(kuò)展服務(wù)類型
*/
private Class<T> type;
private ExtensionLoader(Class<T> type) {
this.type = type;
}
// ......
}
然后實現(xiàn)加載擴(kuò)展的方法,主要實現(xiàn)了擴(kuò)展實現(xiàn)類的加載,具體代碼如下:
private void loadExtensionClasses() {
if (classNames.size() > 0) {
return;
}
try {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 加載擴(kuò)展文件
Enumeration<URL> urlEnumeration = classLoader.getResources(PREFIX + type.getName());
while (urlEnumeration.hasMoreElements()) {
URL url = urlEnumeration.nextElement();
if (url.getPath().isEmpty()) {
throw new IllegalArgumentException("Extension path " + PREFIX + type.getName() + " don't exsist !");
}
// 讀取文件內(nèi)容
if (url.getProtocol().equals("file") || url.getProtocol().equals("jar")) {
URLConnection urlConnection = url.openConnection();
Reader reader = new InputStreamReader(urlConnection.getInputStream());
BufferedReader bufferedReader = new BufferedReader(reader);
// 逐行讀取
String line;
while ((line = bufferedReader.readLine()) != null) {
if (line.equals("")) {
continue;
}
// 過濾注釋
if (line.contains("#")) {
line = line.substring(0, line.indexOf("#"));
}
// 解析key=value
String[] values = line.split("=");
String name = values[0].trim();
String impl = values[1].trim();
if (extensionClasses.containsKey(name)) {
throw new IllegalStateException("Duplicate extension named " + name);
}
// 加載Class
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) classLoader.loadClass(impl);
// 緩存Class
classNames.putIfAbsent(clazz, name);
extensionClasses.putIfAbsent(name, clazz);
}
}
}
} catch (Exception e) {
throw new IllegalArgumentException("Fail to get extension class from " + PREFIX + type.getName() + "!", e);
}
}
實例化所有的擴(kuò)展,并緩存到Map中。
/**
* 獲取所有擴(kuò)展
*
* @return Map<String, T>
*/
public Map<String, T> getExtensions() {
if (cacheExtensions == null) {
cacheExtensions = new ConcurrentHashMap<>();
loadExtensionClasses();
for (Map.Entry<String, Class<T>> entry : extensionClasses.entrySet()) {
Class<T> clazz = entry.getValue();
T instance;
try {
instance = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
cacheExtensions.putIfAbsent(entry.getKey(), instance);
}
}
return Collections.unmodifiableMap(cacheExtensions);
}
提供按名獲取擴(kuò)展和獲取默認(rèn)擴(kuò)展的方法。
/**
* 根據(jù)extensionName獲取擴(kuò)展實例
*
* @param extensionName 擴(kuò)展名稱
* @return T
*/
public T getExtension(String extensionName) {
T extension = getExtensions().get(extensionName);
if (extension != null) {
return extension;
}
throw new IllegalStateException("No extension named " + extensionName + " for class " + type.getName() + "!");
}
/**
* 根據(jù)extensionName獲取擴(kuò)展實例
*
* @return T
*/
public T getDefaultExtension() {
T extension = getExtensions().get(defaultName);
if (extension != null) {
return extension;
}
throw new IllegalStateException("No default extension named " + defaultName + " for class " + type.getName() + "!");
}
最后提供一個工廠方法,創(chuàng)建ExtensionLoader實例。
/**
* 獲取擴(kuò)展接口對應(yīng)的ExtensionLoader
*
* @param clazz 擴(kuò)展接口
* @param <T> Class類型
* @return ExtensionLoader<T>
*/
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> clazz) {
String className = clazz.getName();
if (!clazz.isInterface()) {
throw new IllegalArgumentException("Fail to create ExtensionLoader for class " + className
+ ", class is not Interface !");
}
SPI spi = clazz.getAnnotation(SPI.class);
if (spi == null) {
throw new IllegalArgumentException("Fail to create ExtensionLoader for class " + className
+ ", class is not annotated by @SPI !");
}
ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) CACHES.get(className);
if (extensionLoader == null) {
CACHES.putIfAbsent(className, new ExtensionLoader<>(clazz));
extensionLoader = (ExtensionLoader<T>) CACHES.get(className);
extensionLoader.defaultName = spi.value();
}
return extensionLoader;
}
SPI注解的定義如下:
/**
* 注解在擴(kuò)展類上,表示可以擴(kuò)展
*
* @author bdq
* @since 2019/9/21
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SPI {
/**
* 默認(rèn)擴(kuò)展名
*/
String value() default "";
}
以上是一個簡單SPI的實現(xiàn)代碼,使用方法也很簡單,跟JDK的使用方法差不多,區(qū)別是需要使用@SPI注解在服務(wù)接口上表明擴(kuò)展需求,且配置文件存儲在META-INF/extensions文件夾下,具體使用方法如下。
-
首先定義接口。
package com.github.bdqfork.core.extension; /** * @author bdq * @since 2020/2/22 */ @SPI public interface IExtensionTest { } -
實現(xiàn)接口
package com.github.bdqfork.core.extension; /** * @author bdq * @since 2020/2/22 */ public class ExtensionTestImpl1 implements IExtensionTest { } package com.github.bdqfork.core.extension; /** * @author bdq * @since 2020/2/22 */ public class ExtensionTestImpl2 implements IExtensionTest { } -
編寫配置文件
在META-INF/services目錄下創(chuàng)建一個以接口全類名命名的文件,即com.github.bdqfork.core.extension.ExtensionLoaderTest文件。
- src -main -resources - META-INF - extensions - com.github.bdqfork.core.extension.IExtensionTest文件內(nèi)容如下:
imp1=com.github.bdqfork.core.extension.ExtensionTestImpl1 imp2=com.github.bdqfork.core.extension.ExtensionTestImpl2這里為每一個擴(kuò)展實例提供了名稱。
-
通過ExtensionLoader獲取擴(kuò)展實例
package com.github.bdqfork.core.extension; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class ExtensionLoaderTest { @Test public void getExtension() { ExtensionLoader<IExtensionTest> extensionLoader = ExtensionLoader.getExtensionLoader(IExtensionTest.class); IExtensionTest iExtensionTest = extensionLoader.getExtension("imp1"); assert iExtensionTest != null; } }
以上就是一個簡單SPI的實現(xiàn),在筆者的ioc容器festival和rpc框架hamal中均使用到了SPI,提供擴(kuò)展服務(wù),感興趣的同學(xué)歡迎查看筆者的項目。