精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Java SPI機制初探

開發 前端
如果classpath中存在多個jar包都申明了同一個接口的相同實現類,或者同一個jar包被不同類加載器加載多次,會導致同一個實現類被多次加載和實例化,可能導致單例失效或資源沖突。

一、概述

    1. 什么是SPI

    2. 對比API有什么區別

    3. SPI有什么用

    4. SPI工作機制

二、ServiceLoader

    1. 源碼解析

    2. 小結

三、SPI實際應用場景

    1. JDBC

    2. Spring

    3. Dubbo

四、總結

一、概述

什么是SPI

SPI 即 Service Provider Interface ,也就是“服務提供者的接口”。

SPI 將服務接口和具體的服務實現分離開來,將服務調用方和服務實現者解耦,能夠提升程序的擴展性、可維護性。同時,修改或者替換服務的實現不需要修改調用方。

Java中有許多地方都使用到了SPI機制,比如數據庫加載驅動JDBC、Spring、以及

Dubbo的擴展實現等。

對比API有什么區別

API:接口實現方同時負責接口定義和接口實現,接口控制權在服務提供方。

SPI:服務調用方負責接口定義,不同的接口實現方根據接口定義可以有不同的實現,能夠在運行時動態的加載不用實現類,接口控制權在服務調用方。

SPI有什么用

解耦

在框架開發中,通常需要依賴一些可插拔的功能,但不希望實現具體的適配(能夠保持靈活性)。SPI機制通過定義接口和動態加載實現 ,可以讓框架與服務實現解耦。

※ 場景
  • 一個數據庫連接池庫需要支持多個數據庫實現(如 MySQL、PostgreSQL),可以通過 SPI 機制動態加載這些數據庫驅動。
  • 日志框架(比如 SLF4J)的具體實現,可以通過 SPI 機制加載不同的日志庫(如 Log4j、Logback)。

可擴展

SPI機制提供了動態發現和加載服務的能力,可以讓應用程序非常方便地實現擴展,而不需要修改現有代碼。

※ 場景
  • 一個文件處理系統需要支持不同的文件格式(如 JSON 或 XML)。通過 SPI 機制可以動態發現不同的文件解析器插件,無需提前硬編碼支持的格式。

動態加載

SPI 可以用來實現插件化架構,通過動態加載具體的服務實現增減模塊,而無需重新發布整個系統。

※ 場景
  • Web服務器(如 Tomcat)可以通過 SPI 機制動態加載不同的 HTTP 處理器或過濾器。
  • 數據分析系統可以通過 SPI 機制動態加載新的分析算法。

SPI工作機制

二、ServiceLoader

ServiceLoader是JDK中提供的服務加載類,位于 java.util 包下,final修飾不可被繼承,是實現SPI機制的核心。

源碼解析

public final class ServiceLoader<S>
    implements Iterable<S>
{
    // 默認加載路徑前綴
    private static final String PREFIX = "META-INF/services/";
   
    // The class or interface representing the service being loaded
    // 被加載的實例或接口
    private final Class<S> service;
    
    // The class loader used to locate, load, and instantiate providers
    // 類加載器
    private final ClassLoader loader;
   
    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;
    
    // Cached providers, in instantiation order
    // 本地緩存,key: 類名 value;類
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
  
    // The current lazy-lookup iterator
    // 迭代器
    private LazyIterator lookupIterator;
}
※ load方法
// 暴露給外部使用的加載方法
public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader)
{
    return new ServiceLoader<>(service, loader);
}
public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}


// 構造方法私有化
private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    // 如果指定了claseLoader,則使用該classLoader,如果沒有指定,則使用默認classLoader
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}

 ClassLoader cl = Thread.currentThread().getContextClassLoader();通過獲取線程上下文類加載器(Thread Context ClassLoader)。

※ reload方法
public void reload() {
    // 清空緩存,將linkedHashMap的頭和尾置為null
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}

 ServiceLoader 實現了 Iterable 接口的方法后,擁有了迭代的能力,在這個 iterator 方法被調用時,首先會在 ServiceLoader 的 Provider 緩存中進行查找,如果緩存中沒有命中,則在 LazyIterator 中進行查找。

public Iterator<S> iterator() {
    return new Iterator<S>() {
        // 本地緩存providers
        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();
       
        public boolean hasNext() {
            // 優先查本地緩存
            if (knownProviders.hasNext())
                return true;
            // 沒有則在LazyInterator中進行查找
            return lookupIterator.hasNext();
        }
      
        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }
     
        public void remove() {
            throw new UnsupportedOperationException();
        }
    
    };
}

 LazyIterator 使ServiceLoader擁有了懶加載的能力,只有調用iterator方法或遍歷的時候才會去加載對應的實現類,核心代碼如下:

// Private inner class implementing fully-lazy provider lookup
//
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() {
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            try {
            // 配置文件路徑,默認 /META-INF/services/ 開頭
                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();          // 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本質上就是讀取約定目錄(/META-INF/services/ )下對應接口全限定命名的文件,然后通過反射全量加載文件中定義的所有接口實現類,從而將接口與實現進行解耦。

※ 優點
  • 解耦 :接口與實現分離,無需在代碼中硬編碼實現類。
  • 擴展性 :新增實現只需添加配置,無需修改已有代碼。
※ 缺點
  • 線程不安全 : ServiceLoader 非線程安全,不能保證單例。
  • 性能開銷 :每次迭代都重新加載文件(可通過緩存解決),并且會全量配置文件中指定的所有實現類。
  • 無健壯性 :配置錯誤(如類未找到)會拋出異常而非優雅降級。

三、SPI實際應用場景

JDBC

JDK中定義了Driver接口,用于連接數據庫。

package java.sql;


import java.util.logging.Logger;


public interface Driver {
   
    Connection connect(String url, java.util.Properties info)
        throws SQLException;
   
    boolean acceptsURL(String url) throws SQLException;
    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
                         throws SQLException;
    int getMajorVersion();
    int getMinorVersion();
    boolean jdbcCompliant();
    public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}

不同的數據庫廠商實現這個接口,從而實現與數據庫的連接,以我們最熟悉的MySQL為例,獲取數據庫連接的示例代碼如下:

// 獲取數據庫連接
DriverManager connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123");
// 創建statement
Statement statement = connection.createStatement();
// 執行sql
ResultSet resultSet = statement.executeQuery("select * from student");

其中DriverManager加載時會執行靜態代碼塊去加載driver,部分核心代碼如下:

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}


private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    
    // 如果驅動程序被打包為服務提供者(Service Provider),則加載它。
    // 通過類加載器獲取所有驅動程序
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            // 這里就使用ServiceLoader去加載接口實現類
            // 以mysql-connector-java為例,加載的是 com.mysql.jdbc.Driver 和 com.mysql.fabric.jdbc.FabricMySQLDriver
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });
    
    println("DriverManager.initialize: jdbc.drivers = " + drivers);
    
    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            // 加載數據庫驅動Driver, 以mysql-connector-java為例,這里加載的是 com.mysql.jdbc.Driver 和 com.mysql.fabric.jdbc.FabricMySQLDrive
            // 并注冊到registeredDrivers中
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}

隨后在getConnection方法中遍歷已經加載的driver,執行connect方法進行連接。

private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();


// 遍歷已經注冊的driver,調用connect方法進行連接
for(DriverInfo aDriver : registeredDrivers) {
 
    if(isDriverAllowed(aDriver.driver, callerCL)) {
        try {
            println("    trying " + aDriver.driver.getClass().getName());
            Connection con = aDriver.driver.connect(url, info);
            if (con != null) {
                // Success!
                println("getConnection returning " + aDriver.driver.getClass().getName());
                return (con);
            }
        } catch (SQLException ex) {
            if (reason == null) {
                reason = ex;
            }
        }
 
    } else {
        println("    skipping: " + aDriver.getClass().getName());
    }


}
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }
   
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

Spring

Spring 中沒有直接使用Java SPI機制,不過 Spring的spring.factories 機制類似于SPI機制并擁有更強大的擴展機制,通過讀取 META-INF/spring.factories 文件實現自動裝配、上下文初始化等功能。

// SpringFactoriesLoader 源碼核心邏輯
public final class SpringFactoriesLoader {
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";


    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        // 從所有jar包的spring.factories文件加載配置
        Enumeration<URL> urls = (classLoader != null ?
                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));


        List<String> result = new ArrayList<>();
        while (urls.hasMoreElements()) {
            Properties properties = PropertiesLoaderUtils.loadProperties(
                new UrlResource(urls.nextElement()));
            String factoryClassNames = properties.getProperty(factoryType.getName());
            // 支持逗號分隔的多個實現
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }
        return result;
    }


    public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
        // 1. 加載所有實現類名
        List<String> names = loadFactoryNames(factoryType, classLoader);
        // 2. 實例化所有實現
        List<T> instances = new ArrayList<>(names.size());
        for (String name : names) {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            instances.add((T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance());
        }
        // 3. 按@Order排序
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }


    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            MultiValueMap<String, String> result = new LinkedMultiValueMap();
   
            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();
      
                while(var6.hasNext()) {
                    Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                    String factoryClassName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    int var10 = var9.length;
 
                    for(int var11 = 0; var11 < var10; ++var11) {
                        String factoryName = var9[var11];
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            // 讀取META-INF/spring.factories文件夾下文件,解析文件內容并緩存到Map中
            cache.put(classLoader, result);
            return result;
        } catch (IOException var13) {
            IOException ex = var13;
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", ex);
        }
    }
}
}

spring.factories文件的格式為 xxx=xxxx,=號前面的key為接口全限定名,=號后面的value為接口實現類全限定名 ,多個實現類之間用逗號分割,下圖中指定的key為EnableAutoConfiguration,這樣spring就會為=號后面的類注冊為bean。spring自動裝配原理這里暫不展開。

org.springframework.boot.autoconfigure.EnableAutoCnotallow=\
org.apache.hadoop.hbase.client.MonitorAlicloudHBaseAutoConfiguration

Dubbo

Dubbo的可擴展機制也使用到了SPI,不過不是原生的SPI,而是經過優化的。

由上文ServiceLoader源碼解析可知,SPI會讀取配置文件,遍歷所有類并實例化,如果某些類并不會使用,此時還是會加載進來,造成了資源的浪費;Java SPI配置文件中只是簡單列出了所有擴展實現,沒有給他們命名,無法在程序中準確引用;無法做到自動注入和裝配等等…… Dubbo擴展點機制優化了上述問題,提供了動態擴展的能力。

Demo

定義一個接口,并使用 @SPI 注解標注,這表明這個接口是一個擴展點,可被Dubbo的ExtensionLoader加載。

@SPI
public interface DemoSpi {
    void say();
}

接口實現:

public class DemoSpiImpl implements DemoSpi {
    public void say() {
    }
}

將實現類放在特定目錄下,Dubbo在加載擴展類的時候,會從 META-INF/services/  META-INF/dubbo/   META-INF/dubbo/internal/  這幾個目錄下讀取。這里在 META-INF/dubbo 目錄下新建一個以 DemoSpi 接口名為文件名的文件,內容如下:

demoSpiImpl = com.xxx.xxx.DemoSpiImpl(為 DemoSpi 接口實現類的全類名)

使用如下:

public class DubboSPITest {
 
    @Test    
    public void sayHello() throws Exception {
        ExtensionLoader<DemoSpi> extensionLoader = 
            ExtensionLoader.getExtensionLoader(DemoSpi.class);
        DemoSpi dmeoSpi = extensionLoader.getExtension("demoSpiImpl");
        dmeoSpi.sayHello();
    }
}

源碼解析

由上面的例子可以看出,Dubbo主要通過ExtensionLoader加載配置文件中指定的實現類,整體流程上和ServiceLoader加載流程類似,同時做了相關的優化擴展。

※ duboo加載擴展主要步驟
  • 讀取并解析配置文件
  • 緩存所有擴展實現
  • 基于用戶執行的擴展名,實例化對應的擴展實現
  • 進行擴展實例化屬性的IOC注入及實例化擴展的包裝類,實現AOP特性

SPI 加載固定擴展類的入口是 ExtensionLoader 的 getExtension 方法,該方法主要調用createExtession方法獲取擴展實例:

private T createExtension(String name, boolean wrap) {
    // 從配置文件中加載所有的拓展類,可得到“配置項名稱”到“配置類”的映射關系表    
    Class<?> clazz = getExtensionClasses().get(name);
    // 如果沒有該接口的擴展,或者該接口的實現類不允許重復但實際上重復了,直接拋出異常    
    if (clazz == null || unacceptableExceptions.contains(name)) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        // 這段代碼保證了擴展類只會被構造一次,也就是單例的.        
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 向實例中注入依賴        
        injectExtension(instance);
        // 如果啟用包裝的話,則自動為進行包裝.        
        // 比如我基于 Protocol 定義了 DubboProtocol 的擴展,但實際上在 Dubbo 中不是直接使用的 DubboProtocol, 而是其包裝類  ProtocolListenerWrapper        
        if (wrap) {
            List<Class<?>> wrapperClassesList = new ArrayList<>();
       
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
            
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }


            // 循環創建 Wrapper 實例            
            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                for (Class<?> wrapperClass : wrapperClassesList) {
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    if (wrapper == null                            || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                        // 將當前 instance 作為參數傳給 Wrapper 的構造方法,并通過反射創建 Wrapper 實例。                        // 然后向 Wrapper 實例中注入依賴,最后將 Wrapper 實例再次賦值給 instance 變量                        
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                    }
                }
            }
        }
        // 初始化        
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +                type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}
※ 上述代碼全流程
  • 通過getExtensionClasses獲取所有的拓展類
  • 通過反射創建拓展對象
  • 向拓展對象中注入依賴
  • 將拓展對象包裹在相應的Wrapper對象中
  • 初始化擴展對象

獲取所有拓展類getExtensionClasses和JDK SPI類似,也是先從緩存中拿,拿不到加鎖再次查緩存,還拿不到則通過loadExtensionClasses加載拓展類。

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = (Map)this.cachedClasses.get();
    if (classes == null) {
        synchronized(this.cachedClasses) {
            classes = (Map)this.cachedClasses.get();
            if (classes == null) {
                classes = this.loadExtensionClasses();
                this.cachedClasses.set(classes);
            }
        }
    }
 
    return classes;
}

loadExtensionClasses總共做了兩件事情,一是解析@SPI注解,二是調用loadDirectory方法加載指定文件夾配置文件。

private Map<String, Class<?>> loadExtensionClasses() {
    // 緩存默認的 SPI 擴展名    
    cacheDefaultExtensionName();
 
    Map<String, Class<?>> extensionClasses = new HashMap<>();


    // 基于策略來加載指定文件夾下的文件    
    // 分別讀取 META-INF/services/ META-INF/dubbo/ META-INF/dubbo/internal/ 這幾個目錄下的配置文件    
    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
    }
 
    return extensionClasses;
}

其中loadDirectory方法則是拿到指定文件夾下文件的全路徑名,調用loadResource去加載資源。

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
    try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8));
        Throwable var7 = null;
       
        try {
            String line;
            try {
            // 讀取配置文件中每一行
                while((line = reader.readLine()) != null) {
                    // 只讀取#前面的內容,#后面的為注釋
                    int ci = line.indexOf(35);
                    if (ci >= 0) {
                        line = line.substring(0, ci);
                    }
            
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            int i = line.indexOf(61);
                            if (i > 0) {
                            // 分別讀取=前的作為name,=后面的為具體的實現類全路徑名
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                  
                            if (line.length() > 0 && !this.isExcluded(line, excludedPackages)) {
                                this.loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
                            }
                        } catch (Throwable var21) {
                            Throwable t = var21;
                            IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + this.type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            this.exceptions.put(line, e);
                        }
                    }
                }
            } catch (Throwable var22) {
                var7 = var22;
                throw var22;
            }
        } finally {
            //close ...
        
        }
    } catch (Throwable var24) {
        Throwable t = var24;
        logger.error("Exception occurred when loading extension class (interface: " + this.type + ", class file: " + resourceURL + ") in " + resourceURL, t);
    }


}

loadResource則主要讀取配置文件中每一行,分為key和value兩部分,key為=前面的實現類別名,value為=后面的實現類實例,加載實例的過程為loadClass方法,如下:

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                       boolean overridden) throws NoSuchMethodException {
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException("Error occurred when loading extension class (interface: " +                type + ", class line: " + clazz.getName() + "), class "                + clazz.getName() + " is not subtype of interface.");
    }
    // 檢測目標類上是否有 Adaptive 注解    
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        cacheAdaptiveClass(clazz, overridden);
    } else if (isWrapperClass(clazz)) {
        // 緩存包裝類        
        cacheWrapperClass(clazz);
    } else {
        // 進入到這里,表明只是該類只是一個普通的拓展類        
        // 檢測 clazz 是否有默認的構造方法,如果沒有,則拋出異常        
        clazz.getConstructor();
        if (StringUtils.isEmpty(name)) {
            // 如果 name 為空,則嘗試從 Extension 注解中獲取 name,或使用小寫的類名作為 name
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
            }
        }
      
        String[] names = NAME_SEPARATOR.split(name);
        if (ArrayUtils.isNotEmpty(names)) {
            // 如果類上有 Activate 注解,則使用 names 數組的第一個元素作為鍵,            
            // 存儲 name 到 Activate 注解對象的映射關系            
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // 存儲 Class 到名稱的映射關系                
                cacheName(clazz, n);
                // 存儲 name 到 Class 的映射關系.                
                // 如果存在同一個擴展名對應多個實現類,基于 override 參數是否允許覆蓋,如果不允許,則拋出異常.                
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

Dubbo SPI應用場景

※ 協議擴展
# META-INF/dubbo/org.apache.dubbo.rpc.Protocol
dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
http=org.apache.dubbo.rpc.protocol.http.HttpProtocol
grpc=org.apache.dubbo.rpc.protocol.grpc.GrpcProtocol
※ 集群容錯
@SPI(FailoverCluster.NAME)
public interface Cluster {
    @Adaptive
    <T> Invoker<T> join(Directory<T> directory) throws RpcException;
}


// 使用示例
<dubbo:reference cluster="failfast"/>
※ 服務治理 filter過濾器
@SPI
public interface Filter {
    Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
    
    public interface Listener {
        void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation);
      
        void onError(Throwable t, Invoker<?> invoker, Invocation invocation);
    }
}

四、總結

值得一提的是,SPI在實際使用過程中存在一些問題:

※ 資源浪費預性能問題

由上文源碼解析可知,ServiceLoader在加載時會掃描 META-INF/services 目錄并解析文件,會通過反射實例化所有實現類,如果實現類很多,或者初始化很耗時,會造成一定程度上的性能開銷和資源浪費,所以Dubbo的ExtensionLoader在此之上進行了優化,通過緩存以及在配置文件中指定key實現只加載部分實現類。

※ 多個實現類加載順序問題

ServiceLoader加載服務提供者實現的順序由classpath中jar包的順序決定,如果你的邏輯依賴于獲取到的第一個實現,在不同環境下可能會出現加載順序不一致導致的異常問題,所以盡可能避免依賴加載順序。

※ 類重復加載問題

如果classpath中存在多個jar包都申明了同一個接口的相同實現類,或者同一個jar包被不同類加載器加載多次,會導致同一個實現類被多次加載和實例化,可能導致單例失效或資源沖突。

總的來說,SPI機制的應用場景還是很廣的,其核心在于通過讀取配置文件動態加載接口實現,解耦了接口定義與實現 ,實現了框架可擴展性,在各大框架中都能看到其身影。

責任編輯:武曉燕 來源: 得物技術
相關推薦

2024-10-29 08:34:55

SPI機制接口

2012-04-05 13:50:38

Java

2011-11-30 14:35:19

JavaSPI

2022-05-06 08:26:32

JavaSPI機制

2025-03-27 02:00:00

SPIJava接口

2025-03-04 09:02:25

JavaSPI機制

2025-05-08 03:25:00

DubboSPI機制

2020-06-30 15:35:36

JavaSPI代碼

2022-08-17 08:17:01

SPI機制接口

2023-12-11 07:21:12

SPI機制插件

2020-12-14 11:35:22

SPI Java機制

2012-05-22 15:37:10

2021-09-10 08:31:19

DubboSPI框架

2020-11-20 07:51:02

JavaSPI機制

2021-05-30 07:54:24

SPI機制場景

2024-01-15 08:25:53

SPI機制JavaDubbo

2025-05-20 05:53:07

DubboSPI機制

2011-08-24 09:30:29

JavaJVM

2022-05-12 12:47:07

SPI主設備通信

2022-05-15 22:34:32

SPI 控制器SPI 子系統
點贊
收藏

51CTO技術棧公眾號

国产suv精品一区二区6| 精品国产精品| 午夜精品久久久久久久久久久| 成人情视频高清免费观看电影| 国产成人啪精品午夜在线观看| 日韩超碰人人爽人人做人人添| 色8久久精品久久久久久蜜| 亚洲国产精品视频一区| 精品国自产拍在线观看| 天堂成人国产精品一区| 免费成人高清视频| 亚洲综合网在线观看| 国产精品美女久久久久人| 亚洲va天堂va国产va久| 一本久道久久综合狠狠爱亚洲精品| xxxwww在线观看| 日韩成人一区二区| 欧美劲爆第一页| 性猛交娇小69hd| 粉嫩一区二区三区四区公司1| 色综合久久久久网| a级免费在线观看| av在线收看| 99久久久精品| 3d蒂法精品啪啪一区二区免费| www.国产一区二区| 在线观看视频日韩| 欧美成人黑人xx视频免费观看| 中文字幕在线1| 精品国产影院| 日韩欧美电影在线| 欧美一级小视频| 69堂精品视频在线播放| 五月天一区二区三区| 在线不卡视频一区二区| 九色在线观看视频| 不卡电影一区二区三区| 成人午夜激情免费视频| 涩涩视频在线观看| 视频在线观看一区| 2019中文字幕在线免费观看| 麻豆91精品91久久久| 亚洲91中文字幕无线码三区| 亚洲小视频在线| 深爱五月激情网| 欧美人妖视频| 精品国产成人系列| 97精品人人妻人人| 国产精品qvod| 欧美v日韩v国产v| 永久av免费在线观看| 伊人久久一区| 欧美一二三在线| 激情在线观看视频| 国产精品白丝久久av网站| 欧美日韩国产一级| 欧美日韩中文不卡| 久久久久久久性潮| 欧美日韩高清不卡| 成人黄色一级大片| 国产在线不卡一区二区三区| 91精品国产乱码久久蜜臀| а 天堂 在线| 欧美黄色一级| 欧美变态口味重另类| 亚洲高清无码久久| 亚洲精品推荐| 一区二区三区四区精品| 超碰人人干人人| 999久久久精品国产| 久久国产精品亚洲| 久草免费新视频| 日韩亚洲在线| 国产999精品视频| 中文字幕在线播放日韩| 久久成人麻豆午夜电影| 97碰碰视频| 人妻91麻豆一区二区三区| 久久亚洲精精品中文字幕早川悠里| 欧美欧美一区二区| 毛片免费不卡| 亚洲国产精品精华液网站| 六月丁香婷婷激情| 久久人体av| 精品国产一区二区三区久久久蜜月 | 岛国大片在线观看| 1区2区3区欧美| 日本免费成人网| 欧美片第一页| 欧美一区二区三区在| 屁屁影院国产第一页| 禁果av一区二区三区| 欧美精品在线播放| 久久亚洲精品国产| 久久机这里只有精品| 国产精品视频福利| 1区2区3区在线观看| 一区二区不卡在线视频 午夜欧美不卡在| 黄色www网站| 欧美激情三区| 亚洲国产天堂网精品网站| 国产真人做爰视频免费| 欧美亚洲不卡| 国产精品流白浆视频| 成人精品在线播放| 国产精品天干天干在观线| 日韩av在线播放不卡| 全球最大av网站久久| 亚洲国产精品资源| 中文字幕无码日韩专区免费| 男人的天堂成人在线| 亚洲最大的免费| 韩国三级在线观看久| 一区二区三区精密机械公司| 成年人在线观看视频免费| 高清一区二区三区| 日韩少妇与小伙激情| 人人爽人人爽人人片av| 国产成人精品aa毛片| 一区二区三区偷拍| 日韩欧美精品电影| 亚洲美女在线看| 激情四射综合网| 久久99国产精品尤物| 奇米视频888战线精品播放| 123区在线| 日韩欧美一区电影| 国产精品白丝喷水在线观看| 日韩精品久久久久久| 另类小说综合网| a级片免费在线观看| 3d动漫精品啪啪一区二区竹菊| 精品无码国产污污污免费网站| 亚洲激情av| 国产精品一区免费观看| 日韩免费影院| 日韩免费在线观看| 岛国毛片在线观看| 激情小说亚洲一区| 一区二区免费在线观看| 国精品产品一区| 最近更新的2019中文字幕| 天天爱天天做天天爽| 久久久久国产精品麻豆| 干日本少妇首页| 亚洲综合福利| 国产成人免费av| jizzjizz在线观看| 欧美亚洲自拍偷拍| 波多野结衣喷潮| 国产一区欧美二区| av一区二区三区免费观看| 99国产精品免费网站| 国内伊人久久久久久网站视频| 亚洲精品字幕在线| 亚洲成人动漫精品| 性欧美丰满熟妇xxxx性久久久| 国产欧美日韩综合一区在线播放| 好看的日韩精品视频在线| 理论不卡电影大全神| 精品亚洲一区二区| 亚洲国产无线乱码在线观看| 国产精品免费视频网站| 不卡中文字幕在线观看| 中出一区二区| 国产在线精品一区| 免费在线小视频| 国产亚洲精品一区二区| 一级黄色片在线看| 一二三四区精品视频| 在线免费看黄色片| 久久亚洲视频| 伊人久久大香线蕉午夜av| 一区在线不卡| 97色在线观看免费视频| 二区三区在线| 日韩一区二区在线观看视频 | 99www免费人成精品| аⅴ资源天堂资源库在线| 亚洲男人天堂古典| 国产又粗又长又黄| 精品久久久久久久久中文字幕 | 91不卡在线观看| 国产日本一区二区三区| 一区二区视频免费完整版观看| 日韩小视频在线| 日本激情视频网站| 欧美三区免费完整视频在线观看| 欧美人妻精品一区二区三区| 91碰在线视频| 午夜免费视频网站| 亚洲中字在线| 992tv成人免费观看| 日韩精品福利一区二区三区| 国产日韩欧美成人| 爱福利在线视频| 日韩在线激情视频| 天堂中文资源在线观看| 91精品在线一区二区| 五月婷婷色丁香| 亚洲精品欧美专区| 一本加勒比北条麻妃| 国产九九视频一区二区三区| 99999精品视频| 欧美国产三级| 五月天色一区| 日本亚洲不卡| 3d精品h动漫啪啪一区二区 | 日韩视频精品在线观看| 黄瓜视频免费观看在线观看www | 国产成人高清视频| 成人午夜激情av| 一区二区三区国产在线| 成人在线观看毛片| 天天操夜夜操国产精品| 欧美高清性xxxxhdvideosex| 秋霞一区二区| 成人免费视频97| а√天堂资源国产精品| 欧美一级淫片aaaaaaa视频| 怡红院在线观看| 色婷婷av一区二区三区在线观看| 日本电影一区二区在线观看| 亚洲成人999| www日本视频| 日韩欧美中文一区| 一区二区三区黄色片| 日本精品视频一区二区| 精品国产免费观看| 亚洲成人在线免费| 黄页网站免费观看| 一区二区三区四区不卡视频| 久久久99999| 国产精品青草综合久久久久99| 国产真实乱人偷精品人妻| 99国产欧美久久久精品| 精品影片一区二区入口| 成人精品一区二区三区中文字幕| 少妇欧美激情一区二区三区| 国内成+人亚洲+欧美+综合在线| 激情综合网俺也去| 久久天堂精品| 欧美午夜性生活| 免费一级片91| 高潮一区二区三区| 久久爱另类一区二区小说| 性生生活大片免费看视频| 久久精品99国产国产精| 精品亚洲视频在线| 国产精品一品二品| 国偷自产av一区二区三区麻豆| 国产成人免费视频| 最新国产精品自拍| 成人免费视频一区| 自拍视频一区二区| 26uuu亚洲综合色| 国产伦理片在线观看| 国产喷白浆一区二区三区| 女人裸体性做爰全过| 国产精品高清亚洲| 欧美日韩午夜视频| 伊人夜夜躁av伊人久久| 欧美一级高潮片| 岛国av一区二区三区| 日日夜夜狠狠操| 欧美日韩在线一区二区| 99精品在线视频观看| 亚洲成年网站在线观看| 九色在线播放| 久久久黄色av| 第一中文字幕在线| 国产成人在线一区| 国产在线视频欧美一区| 精品产品国产在线不卡| av永久不卡| 无码人妻精品一区二区三区99v| 国产精品大片| av观看免费在线| 国产综合色在线视频区| 最新日本中文字幕| 日本一区二区三区在线不卡| 波多野结衣家庭教师| 五月天一区二区| 一本色道久久综合无码人妻| 欧美大片在线观看一区| 免费理论片在线观看播放老| 久久精品中文字幕| 神马午夜在线视频| 亚洲一区二区三区毛片| 日本妇女一区| 亚洲AV无码成人精品一区| 亚洲精选在线| 午夜精品免费看| 91女厕偷拍女厕偷拍高清| 亚洲最大的黄色网址| 色综合中文字幕| www日本在线| 中文字幕欧美日韩| 蜜桃麻豆av在线| 91久久精品一区| 在线亚洲a色| 国产精品日韩三级| 日韩精品乱码免费| 7788色淫网站小说| 中文字幕一区二区5566日韩| 免费在线不卡视频| 日韩一二三区不卡| 日本三级视频在线观看| 97热在线精品视频在线观看| 婷婷成人av| 日韩av一区二区三区在线| 激情久久久久久| 亚洲一级片av| 国产精品你懂的| 国产成人无码一区二区在线播放| 精品久久久久99| av网站在线免费看推荐| 国产精品精品一区二区三区午夜版 | 国产精品99久久久久久久| 欧美3p视频| 欧美日韩大尺度| 91美女片黄在线观看| 国产一国产二国产三| 欧美一区二区三区四区在线观看| 成年人在线观看| 国产91露脸中文字幕在线| 六月丁香久久丫| www精品久久| 国产69精品一区二区亚洲孕妇| 亚洲女人久久久| 欧美精品丝袜中出| 免费在线你懂的| 国产在线观看精品一区二区三区| 国产欧美一区二区三区精品观看| 激情深爱综合网| 99riav一区二区三区| 精品无码久久久久久久久| 精品国产一区二区三区不卡| 色综合999| 懂色中文一区二区三区在线视频| 你懂的网址国产 欧美| 国偷自产av一区二区三区麻豆| 怡红院av一区二区三区| 亚洲国产精品一| 欧美精品videosex极品1| 黑人久久a级毛片免费观看| 精品视频在线观看一区二区| 国产69精品久久99不卡| 国产极品在线播放| 亚洲的天堂在线中文字幕| 激情国产在线| 奇米影视首页 狠狠色丁香婷婷久久综合 | 艹b视频在线观看| 中文字幕在线不卡| 国产精品日韩无码| 久久国产精品免费视频| 成人盗摄视频| 人人妻人人添人人爽欧美一区| 99精品偷自拍| 亚洲天堂视频在线播放| 日韩中文字幕在线| 久久综合给合| 少妇人妻在线视频| 久久蜜桃一区二区| 中文字字幕在线观看| 久久国产精品久久久久久| 岛国精品一区| 成人在线观看黄| 国产精品二区一区二区aⅴ污介绍| 国产精品久久久久久免费| 欧美大片大片在线播放| 欧美交a欧美精品喷水| 欧美视频第三页| 国产精品黄色在线观看| a网站在线观看| 91精品国产乱码久久久久久久久| 免费一区二区三区视频导航| 91香蕉视频导航| 一区二区三区四区在线播放 | 最近日韩免费视频| 精品中文字幕在线| 五月综合久久| 久久人人爽av| 亚洲成人激情综合网| 国产黄在线观看| 成人永久免费| 日韩电影免费在线| 日本熟伦人妇xxxx| 中文字幕亚洲欧美日韩高清| 69精品国产久热在线观看| 久久久久狠狠高潮亚洲精品| 1000部国产精品成人观看| 色窝窝无码一区二区三区成人网站| 日本sm极度另类视频| 综合久久十次| 九九九视频在线观看| 精品国产乱码久久久久久图片| 成人精品动漫| 毛片在线播放视频| 亚洲视频在线观看一区|