簡單瞭解SPI機制
阿新 • • 發佈:2021-07-08
1. Java SPI機制
- SPI只是一個簡寫,全名為Service Provider Interface,可以理解成服務介面提供者,一般用於針對廠商或者外掛的場景下,在java.util.ServiceLoader的文件裡有比較詳細的介紹。
- 簡單的總結java SPI機制的思想,系統裡抽象的各個某塊往往會有不同的實現方案或者有不同的服務廠商來提供實現,比如常見的日誌模組,XML解析模組,JSON解析處理模組,資料庫JDBC模組等,Java是面向物件設計的,一般推薦模組之間基於介面進行程式設計,這也是基於可插拔原則的一種設計理念
- Java SPI 就是提供這樣的一個機制:為某個介面尋找服務實現的機制。有點類似IOC的思想,就是將裝配的控制權移到程式之外,在模組化設計中這個機制尤其重要。
- Java SPI的具體約定為: 當服務的提供者,提供了服務介面的一種實現之後,在jar包的META-INF/services/目錄裡同時建立一個以服務介面命名的檔案。該檔案裡就是實現該服務介面的具體實現類。而當外部程式裝配這個模組的時候,就能通過該jar包META-INF/services/裡的配置檔案找到具體的實現類名,並裝載例項化,完成模組的注入。
- 基於這樣一個約定就能很好的找到服務介面的實現類,而不需要再程式碼裡制定。jdk提供服務實現查詢的一個工具類:java.util.ServiceLoader
JDBC SPI使用例子: MySQL VS Oracle
自定義 SPI使用例子
/*@Description: 使用Java SPI機制--定義介面 */ public interface SpiService { void doWork(); } /* @Description: 使用Java SPI機制--介面實現 */ public class StuSpiService implements SpiService{ @Override public void doWork() { System.out.println("我是一名學生,正在寫作業完成功課"); } } public class TeaSpiService implementsSpiService{ @Override public void doWork() { System.out.println("我是一名老師,我正在備課"); } } 定義SPI提供服務檔案 resource/META-INF/services/com.java.interview.spi.SpiService com.java.interview.spi.StuSpiService com.java.interview.spi.TeaSpiService
/* @Description: 使用Java SPI機制--具體使用 */ public class SpiApp { public static void main(String[] args) { // 使用ServiceLoader載入SpiService指定實現類 ServiceLoader<SpiService> load = ServiceLoader.load(SpiService.class); Iterator<SpiService> iterator = load.iterator(); while (iterator.hasNext()) { SpiService next = iterator.next(); next.doWork(); } } }
我是一名學生,正在寫作業完成功課
我是一名老師,我正在備課
java SPI原始碼追蹤解析
public static void main(String[] args) { // 使用ServiceLoader載入SpiService指定實現類 ServiceLoader<SpiService> load = ServiceLoader.load(SpiService.class); Iterator<SpiService> iterator = load.iterator(); while (iterator.hasNext()) { SpiService next = iterator.next(); next.doWork(); } }
Service.load方法追蹤
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){ return new ServiceLoader<>(service, loader); } 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(); } public void reload() { providers.clear(); // LazyIterator是內部Iterator介面的一個實現 lookupIterator = new LazyIterator(service, loader); }
Service.iterator方法追蹤: 獲取迭代器返回自定義實現的LazyIterator
public Iterator<S> iterator() { return new Iterator<S>() { Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); public boolean hasNext() { if (knownProviders.hasNext()) return true; return lookupIterator.hasNext(); } public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; }
LazyIterator迭代方法追蹤
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); } }
private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); 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()) { return false; } 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 { S p = service.cast(c.newInstance()); providers.put(cn, p); // 將介面實現類例項放入集合中 return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // Th
java SPI存在的不足
- 通過對Java SPI 原始碼追蹤分析,發現Java SPI 在查詢擴充套件實現類的時候遍歷 SPI 的配置檔案並且將實現類全部例項化,假設一個實現類初始化過程比較消耗資源且耗時,但是你的程式碼裡面又用不上它,這就產生了資源的浪費,所以說 Java SPI 無法按需載入實現類
2. Dubbo SPI機制
Dubbo SPI設計
待續...........
3. SpringBoot SPI機制
- 在springboot的自動裝配過程中,最終會載入META-INF/spring.factories檔案,而載入的過程是由SpringFactoriesLoader載入的。從CLASSPATH下的每個Jar包中搜尋所有META-INF/spring.factories配置檔案,然後將解析properties檔案,找到指定名稱的配置後返回。需要注意的是,其實這裡不僅僅是會去ClassPath路徑下查詢,會掃描所有路徑下的Jar包,只不過這個檔案只會在Classpath下的jar包中。
public abstract class SpringFactoriesLoader { /** * The location to look for factories.<p>Can be present in multiple JAR files. * -- 工廠設定的配置檔案存放位置 可以在Jar包中包含 */ public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
// 用來儲存不同的classLoader類載入器載入的資料 private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>(); /** * Load and instantiate the factory implementations of the given type from * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader. * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}. * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames} */ public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) { Assert.notNull(factoryClass, "'factoryClass' must not be null"); ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { classLoaderToUse = SpringFactoriesLoader.class.getClassLoader(); } List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse); if (logger.isTraceEnabled()) { logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames); } List<T> result = new ArrayList<>(factoryNames.size()); for (String factoryName : factoryNames) { result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse)); } AnnotationAwareOrderComparator.sort(result); return result; } /** * Load the fully qualified class names of factory implementations of the * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given * class loader.*/ public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } @SuppressWarnings("unchecked") private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) { try { Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader); if (!factoryClass.isAssignableFrom(instanceClass)) { throw new IllegalArgumentException( "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]"); } return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance(); } catch (Throwable ex) { throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex); } } }
- META-INF/spring.factories 如果採用SpringBoot預設的配置,該檔案處於spring-boot-autoconfigure 子模組下 resource/META-INF/spring.factories檔案,資料以key-value鍵值對呈現
- 關鍵Key對應類詳情
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ Class<?>[] exclude() default {}; /** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */ String[] excludeName() default {}; }
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> { void initialize(C var1); }
@FunctionalInterface public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { void onApplicationEvent(E var1); }
@FunctionalInterface public interface AutoConfigurationImportListener extends EventListener { /** * Handle an auto-configuration import event. * @param event the event to respond to */ void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event); }
@FunctionalInterface public interface AutoConfigurationImportFilter { /** * Apply the filter to the given auto-configuration class candidates. * @param autoConfigurationClasses the auto-configuration classes being considered. * Implementations should not change the values in this array. * @param autoConfigurationMetadata access to the meta-data generated by the * auto-configure annotation processor * @return a boolean array indicating which of the auto-configuration classes should * be imported. The returned array must be the same size as the incoming * {@code autoConfigurationClasses} parameter. Entries containing {@code false} will * not be imported. */ boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata); }
@FunctionalInterface public interface TemplateAvailabilityProvider { /** * Returns {@code true} if a template is available for the given {@code view}. * @param view the view name * @param environment the environment * @param classLoader the class loader * @param resourceLoader the resource loader * @return if the template is available */ boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader,ResourceLoader resourceLoader); }
@FunctionalInterface public interface FailureAnalyzer { /** * Returns an analysis of the given {@code failure}, or {@code null} if no analysis * was possible. * @param failure the failure * @return the analysis or {@code null} */ FailureAnalysis analyze(Throwable failure); }