精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

中文字幕 日韩有码| av在线播放中文字幕| 国产在线天堂www网在线观看| 99精品国产视频| 国产精品久久久久免费a∨大胸| 天天看天天摸天天操| 欧美自拍视频| 欧美日本不卡视频| 五十路熟女丰满大屁股| 第一页在线观看| 国产精品五月天| 精品国产精品网麻豆系列| 少妇高潮喷水在线观看| 川上优的av在线一区二区| 国产伦精品一区二区三区免费 | 97超碰人人看| 一区二区三区四区日本视频| 亚洲免费在线视频一区 二区| 久久国产日韩欧美| 国产xxxxxx| 日韩福利电影在线观看| 欧美激情第三页| 天堂av网手机版| 亚洲欧洲色图| 亚洲国产91精品在线观看| 超碰成人在线播放| 美女福利一区二区| 亚洲第一搞黄网站| 欧美交换配乱吟粗大25p| shkd中文字幕久久在线观看| 99久久精品国产一区| 亚洲一区久久久| 99re热视频| 久久高清一区| 91国产美女在线观看| 黄色一级视频免费观看| 国产精品久久久久久久免费观看| 国产丝袜精品第一页| xxxx视频在线观看| 久久影院一区二区三区| 欧美日韩电影一区| 五月婷婷激情久久| 日韩一区二区三区在线免费观看| 欧美午夜激情在线| 欧美色图另类小说| 亚洲性受xxx喷奶水| 精品国产91久久久久久| 黄色国产一级视频| 色在线中文字幕| 精品露脸国产偷人在视频| 亚洲国产精品无码观看久久| 丁香花在线影院| 亚洲国产精品久久艾草纯爱| 又大又硬又爽免费视频| 懂色av一区| 精品高清一区二区三区| 91黄色小网站| 人人鲁人人莫人人爱精品| 91福利在线导航| 性chinese极品按摩| 日本a人精品| 91精品国产乱码| 一区二区在线免费观看视频| 一级毛片精品毛片| 亚洲变态欧美另类捆绑| jizz日本免费| 成人一级毛片| 欧美成在线视频| 日本一区二区不卡在线| 久久久777| 国产精品一区久久久| 国产精品视频a| 国产成人精品免费| 久久久99国产精品免费| 韩日视频在线| 中文字幕一区二区三区不卡在线| 欧美做受777cos| 精精国产xxx在线视频app| 一本久久a久久免费精品不卡| 成年人在线观看视频免费| 先锋影音网一区二区| 欧美成人三级在线| aaaaa一级片| 日韩在线二区| 午夜免费日韩视频| 最近中文字幕免费在线观看| 国产精品一区在线| 久久视频在线观看中文字幕| av电影在线观看一区二区三区| 亚洲欧美在线视频观看| 国产高清不卡无码视频| 免费观看一级欧美片| 制服丝袜亚洲网站| 亚洲国产综合视频| 91亚洲人成网污www| 欧美极品少妇与黑人| www毛片com| 国产丶欧美丶日本不卡视频| 免费成人深夜夜行视频| 黄色国产网站在线播放| 精品av在线播放| 亚洲精品永久视频| 波多野吉衣中文字幕| 日韩免费观看高清| 日韩一区二区视频| 欧美激情一区三区| 天堂av在线一区| xxxx日韩| 男人av在线播放| 国产麻豆免费观看| 国产视频第一区| av影院午夜一区| 亚洲午夜精品久久| 国产一二在线播放| 日韩三级中文字幕| 性欧美一区二区| 精品99视频| 国产欧美婷婷中文| 天堂资源最新在线| 亚洲午夜久久久久久久久久久| 看欧美ab黄色大片视频免费| 欧美一级三级| 久久久久久久一区二区三区| 国产有码在线观看| 国产日产精品一区| 波多野结衣家庭教师在线| 91亚洲无吗| 国产成人小视频| 成人性生交大片免费看视频直播| 毛片免费在线播放| 午夜精品久久久| 国产51自产区| 极品中文字幕一区| 99热最新在线| 91三级在线| 777精品伊人久久久久大香线蕉| 久久视频精品在线观看| 美女精品在线| 欧美一级二级三级九九九| 亚洲最大成人| 亚洲人成伊人成综合网久久久| 日韩av免费网址| 成人午夜视频免费看| 久久精品无码中文字幕| 一区二区精彩视频| 久久久久久久爱| 欧美视频一二区| 精品电影在线观看| 中文字幕人妻一区二区| 欧美专区18| 日本一区免费在线观看| 成人性生活视频| 国产亚洲综合久久| 中文字幕在线观看高清| 国产精品久久毛片av大全日韩| 午夜免费高清视频| 99久久婷婷国产综合精品电影√| 91精品视频在线看| 在线中文字幕电影| 欧美精品一区二区久久久| 日韩久久久久久久久| 91一区在线观看| 色七七在线观看| 99久久夜色精品国产亚洲96| 91手机在线观看| 国产高潮在线| 国产亚洲免费的视频看| 91午夜交换视频| 亚洲黄一区二区三区| www国产视频| 久久九九免费| 日本高清xxxx| 欧美韩一区二区| 国产精品国模在线| h网站久久久| 亚洲娇小xxxx欧美娇小| 激情网站在线观看| 亚洲美腿欧美偷拍| 女~淫辱の触手3d动漫| 青青草国产精品97视觉盛宴| 麻豆传媒网站在线观看| 卡通动漫国产精品| 成人免费黄色网| av免费不卡| 久久精品一本久久99精品| 国精产品乱码一区一区三区四区| 狠狠躁18三区二区一区| 九九热最新地址| 91蜜桃婷婷狠狠久久综合9色| 性欧美videossex精品| 欧美日韩理论| 视频一区二区三| 精品成人自拍视频| 国产欧美 在线欧美| 2021中文字幕在线| 久久精品在线视频| 日韩在线免费看| 欧美一区2区视频在线观看| 青青视频在线免费观看| 樱桃视频在线观看一区| 中文字幕黄色网址| 99麻豆久久久国产精品免费| 中文字幕久久av| 久久免费高清| 国产 日韩 欧美在线| 我不卡神马影院| 日本一区二区三区四区高清视频 | 成人高h视频在线| av资源亚洲| 久久久久国产精品免费| 五月天婷婷在线视频| 亚洲毛片在线观看.| 国内毛片毛片毛片毛片| 欧美男女性生活在线直播观看| 日韩色图在线观看| 亚洲国产日日夜夜| 国产精品国产精品88| 中文字幕精品在线不卡| 亚洲专区区免费| 92国产精品观看| 佐佐木明希电影| 国产精品自拍一区| 看看黄色一级片| 日韩国产欧美一区二区三区| 免费高清在线观看免费| 国产精品毛片| 成人黄色av片| 日韩网站在线| 天天夜碰日日摸日日澡性色av| 欧美日本三区| 日本wwwcom| 黄色欧美日韩| www.好吊操| 伊人精品在线| 少妇人妻无码专区视频| 亚洲欧洲一区| 无码人妻精品一区二区三区在线| 亚洲国产第一| 波多野结衣之无限发射| 亚洲另类视频| 97在线播放视频| 久久国产精品亚洲77777| 日韩精品视频久久| 久久精品官网| youjizzxxxx18| 蜜桃视频在线观看一区| 天堂av2020| 国产一区二区在线看| 91视频福利网| 成人免费看视频| 免费黄色三级网站| 国产日产精品一区| 亚洲人与黑人屁股眼交| 一区二区三区在线播| 久久久久无码国产精品不卡| 亚洲一区二区三区在线| 久久精品一二区| 日本丰满少妇一区二区三区| 做爰视频毛片视频| 制服丝袜一区二区三区| 亚洲国产精彩视频| 亚洲经典中文字幕| 成黄免费在线| 欧美精品免费看| 蜜桃视频www网站在线观看| 国产成人亚洲综合青青| 日韩毛片免费视频一级特黄| 成人欧美一区二区三区在线观看| 欧美巨大xxxx| 亚洲人成网站在线观看播放| 综合亚洲视频| 国产亚洲欧美在线视频| 蜜桃视频在线一区| 一级黄色电影片| 国产无遮挡一区二区三区毛片日本| 美女av免费看| 亚洲成人免费电影| 中文字幕日日夜夜| 精品噜噜噜噜久久久久久久久试看 | 欧美aaaxxxx做受视频| 456亚洲影院| 在线观看欧美| 欧美第一黄网| 中出一区二区| 日韩视频免费在线播放| 国产精品自拍三区| aaaaa一级片| 悠悠色在线精品| 亚洲精品国产无码| 亚洲成av人乱码色午夜| 98在线视频| 992tv成人免费视频| 性欧美video另类hd尤物| 久久精彩视频| 欧美淫片网站| 91制片厂毛片| 99久久99久久久精品齐齐| 黄色精品视频在线观看| 精品福利樱桃av导航| 国产浮力第一页| 在线精品91av| 在线天堂资源| 国产精品久久国产精品| 99热在线成人| 91热这里只有精品| 99久久精品情趣| 欧美成人黄色网| 6080亚洲精品一区二区| 久久久久久久影视| 97在线免费观看| 日韩精品视频中文字幕| 亚洲一区三区在线观看| 久久大逼视频| 中文字幕丰满孑伦无码专区| 亚洲一区二区三区四区在线免费观看 | 69视频在线观看| 日本高清视频一区| 精品国产乱子伦一区二区| 九九久久九九久久| 精品一区二区久久| 国产精品成人在线视频| 在线免费亚洲电影| 三级做a全过程在线观看| 久久久久久国产| 亚洲图色一区二区三区| 最新中文字幕久久| 另类人妖一区二区av| 成年人看的免费视频| 色久优优欧美色久优优| 久久精品a一级国产免视看成人| 国内精品久久久久影院 日本资源| 亚洲国产中文在线| 久久这里只有精品18| 国产成人免费视频网站 | 性色一区二区三区| 黄色污在线观看| 精品露脸国产偷人在视频| 无码国产伦一区二区三区视频 | 在线观看中文字幕视频| 亚洲国产日韩欧美在线图片| 国产精品探花在线| 国产中文一区二区| 国产日韩欧美三级| 一区二区精品免费| 欧美在线短视频| 老司机午夜在线| av免费精品一区二区三区| 精品电影一区| 三级黄色片网站| 91成人在线精品| 天天综合视频在线观看| 成人高h视频在线| 国内揄拍国内精品久久| 无码av免费精品一区二区三区| 亚洲v日本v欧美v久久精品| 视频二区在线| 国产精品美女网站| 影视一区二区| 国产二级一片内射视频播放 | 欧美激情国内偷拍| 欧美美女黄色| 中文字幕在线观看第三页| 国产精品电影一区二区三区| 国产视频在线观看视频| 97色伦亚洲国产| 欧洲杯什么时候开赛| 一级网站在线观看| 亚洲成人免费看| av在线免费一区| 成人在线观看av| 亚洲欧美清纯在线制服| 激情高潮到大叫狂喷水| 欧美成人精品二区三区99精品| 免费高潮视频95在线观看网站| 日韩福利影院| 国产综合色精品一区二区三区| 精品久久免费视频| 一本色道久久88亚洲综合88| 久久久久久久久成人| 香港三级韩国三级日本三级| 欧美国产禁国产网站cc| 免费看黄色一级视频| 国产成人精品在线| 欧美日韩免费观看一区=区三区| 色婷婷免费视频| 在线91免费看| 中文在线最新版地址| 亚洲第一综合网站| 久久影视一区二区| 精品国自产在线观看| 国产精品久久久久久av福利软件| 欧美涩涩视频| 波多野结衣家庭教师在线观看 | 青青草视频播放| 6080yy午夜一二三区久久| 中文字幕资源网在线观看免费| 日本三日本三级少妇三级66| 久久久久久免费| 三级视频在线看| 亚洲一区中文字幕在线观看|