spi 02-spi 的實戰解決 slf4j 包衝突問題git
spi 03-spi jdk 實現源碼解析github
spi 04-spi dubbo 實現源碼解析spring
spi 05-dubbo adaptive extension 自適應拓展api
學習了 java 的 SPI 和 dubbo 的 SPI 實現以後,但願實現一個屬於本身的 SPI 框架。併發
但願具備以下特性:框架
(1)相似 dubbo 的真正的惰性加載,而不是遍歷一堆一須要的實例ide
(2)併發安全考慮
(3)支持基於名稱獲取實現類,後期能夠添加更多的特性支持。相似 spring 的 IOC
(4)儘量的簡化實現
@SPI public interface Say { void say(); }
public class SayBad implements Say { @Override public void say() { System.out.println("bad"); } }
public class SayGood implements Say { @Override public void say() { System.out.println("good"); } }
在 META-INF/services/
文件夾下定義文件 com.github.houbb.spi.bs.spi.Say
內容以下:
good=com.github.houbb.spi.bs.spi.impl.SayGood bad=com.github.houbb.spi.bs.spi.impl.SayBad
ExtensionLoader<Say> loader = SpiBs.load(Say.class); Say good = loader.getExtension("good"); Say bad = loader.getExtension("bad"); good.say(); bad.say();
日誌輸出:
good bad
├─annotation │ SPI.java │ ├─api │ │ IExtensionLoader.java │ │ │ └─impl │ ExtensionLoader.java │ ├─bs │ SpiBs.java │ ├─constant │ SpiConst.java │ └─exception SpiException.java
@SPI
相似於 dubbo 的註解,標識一個接口爲 SPI 接口。
這樣嚴格控制,便於後期拓展和管理,下降代碼複雜度。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface SPI { }
核心引導類
public final class SpiBs { private SpiBs(){} /** * 拓展加載類 map * @since 0.0.1 */ private static final Map<Class, ExtensionLoader> EX_LOADER_MAP = new ConcurrentHashMap<>(); /** * 加載實例 * @param clazz 類 * @param <T> 泛型 * @return 結果 * @since 0.0.1 */ @SuppressWarnings("unchecked") public static <T> ExtensionLoader<T> load(Class<T> clazz) { ArgUtil.notNull(clazz, "clazz"); ExtensionLoader extensionLoader = EX_LOADER_MAP.get(clazz); if(EX_LOADER_MAP.get(clazz) != null) { return extensionLoader; } // DLC synchronized (EX_LOADER_MAP) { extensionLoader = EX_LOADER_MAP.get(clazz); if(extensionLoader == null) { extensionLoader = new ExtensionLoader(clazz); } } return extensionLoader; } }
import com.github.houbb.heaven.annotation.CommonEager; import com.github.houbb.heaven.annotation.ThreadSafe; import com.github.houbb.heaven.response.exception.CommonRuntimeException; import com.github.houbb.heaven.util.common.ArgUtil; import com.github.houbb.heaven.util.lang.StringUtil; import com.github.houbb.heaven.util.util.CollectionUtil; import com.github.houbb.spi.annotation.SPI; import com.github.houbb.spi.api.IExtensionLoader; import com.github.houbb.spi.constant.SpiConst; import com.github.houbb.spi.exception.SpiException; import java.io.*; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 默認實現 * @author binbin.hou * @since 0.0.1 */ @ThreadSafe public class ExtensionLoader<T> implements IExtensionLoader<T> { /** * 接口定義 * @since 0.0.1 */ private final Class<T> spiClass; /** * 類加載器 * @since 0.0.1 */ private final ClassLoader classLoader; /** * 緩存的對象實例 * @since 0.0.1 */ private final Map<String, T> cachedInstances = new ConcurrentHashMap<>(); /** * 實例別名 map * @since 0.0.1 */ private final Map<String, String> classAliasMap = new ConcurrentHashMap<>(); public ExtensionLoader(Class<T> spiClass, ClassLoader classLoader) { spiClassCheck(spiClass); ArgUtil.notNull(classLoader, "classLoader"); this.spiClass = spiClass; this.classLoader = classLoader; // 初始化配置 this.initSpiConfig(); } public ExtensionLoader(Class<T> spiClass) { this(spiClass, Thread.currentThread().getContextClassLoader()); } /** * 獲取對應的拓展信息 * * @param alias 別名 * @return 結果 * @since 0.0.1 */ @Override public T getExtension(String alias) { ArgUtil.notEmpty(alias, "alias"); //1. 獲取 T instance = cachedInstances.get(alias); if(instance != null) { return instance; } // DLC synchronized (cachedInstances) { instance = cachedInstances.get(alias); if(instance == null) { instance = createInstance(alias); cachedInstances.put(alias, instance); } } return instance; } /** * 實例 * @param name 名稱 * @return 實例 * @since 0.0.1 */ @SuppressWarnings("unchecked") private T createInstance(String name) { String className = classAliasMap.get(name); if(StringUtil.isEmpty(className)) { throw new SpiException("SPI config not found for spi: " + spiClass.getName() +" with alias: " + name); } try { Class clazz = Class.forName(className); return (T) clazz.newInstance(); } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { throw new SpiException(e); } } /** * 參數校驗 * * 1. 不能爲 null * 2. 必須是接口 * 3. 必須指定 {@link com.github.houbb.spi.annotation.SPI} 註解 * @param spiClass spi 類 * @since 0.0.1 */ private void spiClassCheck(final Class<T> spiClass) { ArgUtil.notNull(spiClass, "spiClass"); if(!spiClass.isInterface()) { throw new SpiException("Spi class is not interface, " + spiClass); } if(!spiClass.isAnnotationPresent(SPI.class)) { throw new SpiException("Spi class is must be annotated with @SPI, " + spiClass); } } /** * 初始化配置文件名稱信息 * * 只加載當前類的文件信息 * @since 0.0.1 */ private void initSpiConfig() { // 文件的讀取 String fullName = SpiConst.JDK_DIR+this.spiClass.getName(); try { Enumeration<URL> urlEnumeration = this.classLoader .getResources(fullName); // 沒有更多元素 if(!urlEnumeration.hasMoreElements()) { throw new SpiException("SPI config file for class not found: " + spiClass.getName()); } // 獲取第一個元素 URL url = urlEnumeration.nextElement(); List<String> allLines = readAllLines(url); // 構建 map if(CollectionUtil.isEmpty(allLines)) { throw new SpiException("SPI config file for class is empty: " + spiClass.getName()); } for(String line : allLines) { String[] lines = line.split(SpiConst.SPLITTER); classAliasMap.put(lines[0], lines[1]); } } catch (IOException e) { throw new SpiException(e); } } /** * 讀取每一行的內容 * @param url url 信息 * @return 結果 * @since 0.0.1 */ @CommonEager private List<String> readAllLines(final URL url) { ArgUtil.notNull(url, "url"); List<String> resultList = new ArrayList<>(); try(InputStream is = url.openStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { // 按行讀取信息 String line; while ((line = br.readLine()) != null) { resultList.add(line); } } catch (IOException e) { throw new CommonRuntimeException(e); } return resultList; } }
其餘的只是一些常量定義等。
完整代碼見:
這裏只實現了最基本的功能,能夠後續添加更多豐富的特性。
還有一個問題就是,咱們能不能像使用 jdk spi 那樣,去使用 google auto,直接一個註解,幫咱們省略掉文件建立的過程呢?
固然是能夠的,下一節,咱們就來實現一個這樣的功能。