您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“Java如何實現簡單SPI”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Java如何實現簡單SPI”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
標注提供SPI
能力接口的注解
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface SPI { /** * value * @return value */ String value() default ""; }
標準SPI
實現的注解@Join
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Join { }
SPI
實現是一個懶加載的過程,只有當通過get
方法獲取擴展的實例時才會加載擴展,并創建擴展實例,這里我們定義一個集合用于緩存擴展類,擴展對象等,代碼如下:
@Slf4j @SuppressWarnings("all") public class ExtensionLoader<T> { /** * SPI配置擴展的文件位置 * 擴展文件命名格式為 SPI接口的全路徑名,如:com.redick.spi.test.TestSPI */ private static final String DEFAULT_DIRECTORY = "META-INF/log-helper/"; /** * 擴展接口 {@link Class} */ private final Class<T> tClass; /** * 擴展接口 和 擴展加載器 {@link ExtensionLoader} 的緩存 */ private static final Map<Class<?>, ExtensionLoader<?>> MAP = new ConcurrentHashMap<>(); /** * 保存 "擴展" 實現的 {@link Class} */ private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>(); /** * "擴展名" 對應的 保存擴展對象的Holder的緩存 */ private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>(); /** * 擴展class 和 擴展點的實現對象的緩存 */ private final Map<Class<?>, Object> joinInstances = new ConcurrentHashMap<>(); /** * 擴展點默認的 "名稱" 緩存 */ private String cacheDefaultName; // 省略代碼后面介紹 }
public static<T> ExtensionLoader<T> getExtensionLoader(final Class<T> tClass) { // 參數非空校驗 if (null == tClass) { throw new NullPointerException("tClass is null !"); } // 參數應該是接口 if (!tClass.isInterface()) { throw new IllegalArgumentException("tClass :" + tClass + " is not interface !"); } // 參數要包含@SPI注解 if (!tClass.isAnnotationPresent(SPI.class)) { throw new IllegalArgumentException("tClass " + tClass + "without @" + SPI.class + " Annotation !"); } // 從緩存中獲取擴展加載器,如果存在直接返回,如果不存在就創建一個擴展加載器并放到緩存中 ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) MAP.get(tClass); if (null != extensionLoader) { return extensionLoader; } MAP.putIfAbsent(tClass, new ExtensionLoader<>(tClass)); return (ExtensionLoader<T>) MAP.get(tClass); }
public ExtensionLoader(final Class<T> tClass) { this.tClass = tClass; }
獲取SPI擴展對象是懶加載過程,第一次去獲取的時候是沒有的,會觸發從問家中加載資源,通過反射創建對象,并緩存起來。
public T getJoin(String cacheDefaultName) { // 擴展名 文件中的key if (StringUtils.isBlank(cacheDefaultName)) { throw new IllegalArgumentException("join name is null"); } // 擴展對象存儲緩存 Holder<Object> objectHolder = cachedInstances.get(cacheDefaultName); // 如果擴展對象的存儲是空的,創建一個擴展對象存儲并緩存 if (null == objectHolder) { cachedInstances.putIfAbsent(cacheDefaultName, new Holder<>()); objectHolder = cachedInstances.get(cacheDefaultName); } // 從擴展對象的存儲中獲取擴展對象 Object value = objectHolder.getT(); // 如果對象是空的,就觸發創建擴展,否則直接返回擴展對象 if (null == value) { synchronized (cacheDefaultName) { value = objectHolder.getT(); if (null == value) { // 創建擴展對象 value = createExtension(cacheDefaultName); objectHolder.setT(value); } } } return (T) value; }
反射方式創建擴展對象的實例
private Object createExtension(String cacheDefaultName) { // 根據擴展名字獲取擴展的Class,從Holder中獲取 key-value緩存,然后根據名字從Map中獲取擴展實現Class Class<?> aClass = getExtensionClasses().get(cacheDefaultName); if (null == aClass) { throw new IllegalArgumentException("extension class is null"); } Object o = joinInstances.get(aClass); if (null == o) { try { // 創建擴展對象并放到緩存中 joinInstances.putIfAbsent(aClass, aClass.newInstance()); o = joinInstances.get(aClass); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return o; }
public Map<String, Class<?>> getExtensionClasses() { // 擴區SPI擴展實現的緩存,對應的就是擴展文件中的 key - value Map<String, Class<?>> classes = cachedClasses.getT(); if (null == classes) { synchronized (cachedClasses) { classes = cachedClasses.getT(); if (null == classes) { // 加載擴展 classes = loadExtensionClass(); // 緩存擴展實現集合 cachedClasses.setT(classes); } } } return classes; }
加載擴展實現Class,就是從文件中獲取擴展實現的Class,然后緩存起來
public Map<String, Class<?>> loadExtensionClass() { // 擴展接口tClass,必須包含SPI注解 SPI annotation = tClass.getAnnotation(SPI.class); if (null != annotation) { String v = annotation.value(); if (StringUtils.isNotBlank(v)) { // 如果有默認的擴展實現名,用默認的 cacheDefaultName = v; } } Map<String, Class<?>> classes = new HashMap<>(16); // 從文件加載 loadDirectory(classes); return classes; } private void loadDirectory(final Map<String, Class<?>> classes) { // 文件名 String fileName = DEFAULT_DIRECTORY + tClass.getName(); try { ClassLoader classLoader = ExtensionLoader.class.getClassLoader(); // 讀取配置文件 Enumeration<URL> urls = classLoader != null ? classLoader.getResources(fileName) : ClassLoader.getSystemResources(fileName); if (urls != null) { // 獲取所有的配置文件 while (urls.hasMoreElements()) { URL url = urls.nextElement(); // 加載資源 loadResources(classes, url); } } } catch (IOException e) { log.error("load directory error {}", fileName, e); } } private void loadResources(Map<String, Class<?>> classes, URL url) { // 讀取文件到Properties,遍歷Properties,得到配置文件key(名字)和value(擴展實現的Class) try (InputStream inputStream = url.openStream()) { Properties properties = new Properties(); properties.load(inputStream); properties.forEach((k, v) -> { // 擴展實現的名字 String name = (String) k; // 擴展實現的Class的全路徑 String classPath = (String) v; if (StringUtils.isNotBlank(name) && StringUtils.isNotBlank(classPath)) { try { // 加載擴展實現Class,就是想其緩存起來,緩存到集合中 loadClass(classes, name, classPath); } catch (ClassNotFoundException e) { log.error("load class not found", e); } } }); } catch (IOException e) { log.error("load resouces error", e); } } private void loadClass(Map<String, Class<?>> classes, String name, String classPath) throws ClassNotFoundException { // 反射創建擴展實現的Class Class<?> subClass = Class.forName(classPath); // 擴展實現的Class要是擴展接口的實現類 if (!tClass.isAssignableFrom(subClass)) { throw new IllegalArgumentException("load extension class error " + subClass + " not sub type of " + tClass); } // 擴展實現要有Join注解 Join annotation = subClass.getAnnotation(Join.class); if (null == annotation) { throw new IllegalArgumentException("load extension class error " + subClass + " without @Join" + "Annotation"); } // 緩存擴展實現Class Class<?> oldClass = classes.get(name); if (oldClass == null) { classes.put(name, subClass); } else if (oldClass != subClass) { log.error("load extension class error, Duplicate class oldClass is " + oldClass + "subClass is" + subClass); } }
public static class Holder<T> { private volatile T t; public T getT() { return t; } public void setT(T t) { this.t = t; } }
定義SPI接口
@SPI public interface TestSPI { void test(); }
擴展實現1和2
@Join public class TestSPI1Impl implements TestSPI { @Override public void test() { System.out.println("test1"); } } @Join public class TestSPI2Impl implements TestSPI { @Override public void test() { System.out.println("test2"); } }
在resources文件夾下創建META-INF/log-helper文件夾,并創建擴展文件
文件名稱(接口全路徑名):com.redick.spi.test.TestSPI
文件內容
testSPI1=com.redick.spi.test.TestSPI1Impl
testSPI2=com.redick.spi.test.TestSPI2Impl
動態使用測試
public class SpiExtensionFactoryTest { @Test public void getExtensionTest() { TestSPI testSPI = ExtensionLoader.getExtensionLoader(TestSPI.class).getJoin("testSPI1"); testSPI.test(); } }
測試結果:
test1
public class SpiExtensionFactoryTest { @Test public void getExtensionTest() { TestSPI testSPI = ExtensionLoader.getExtensionLoader(TestSPI.class).getJoin("testSPI2"); testSPI.test(); } }
測試結果:
test2
讀到這里,這篇“Java如何實現簡單SPI”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。