欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JDK SPI機(jī)制以及自定義SPI類加載問題

 更新時(shí)間:2022年11月19日 11:15:14   作者:兩米以下皆凡人  
這篇文章主要介紹了JDK SPI機(jī)制以及自定義SPI類加載問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

概述

介紹SPI之前,我們先了解一下為什么要用SPI

JDBC相信已經(jīng)不陌生了,JDBC 是一個(gè)標(biāo)準(zhǔn)。

不同的數(shù)據(jù)庫廠商(如,mysql、oracle等)會(huì)根據(jù)這個(gè)標(biāo)準(zhǔn),有它們自己的實(shí)現(xiàn)。

既然,JDBC 是一個(gè)標(biāo)準(zhǔn),那么 JDBC 的接口,應(yīng)該就已經(jīng)存在于JDK 中了,以前我們?cè)谑褂肑DBC的時(shí)候,都是需要加載Driver驅(qū)動(dòng)的,如:

Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql:///test";
Connection connection = = DriverManager.getConnection(url,"root","123456");

但是我們?nèi)绻麤]有寫的這行代碼,也是可以讓com.mysql.jdbc.Driver正確加載的,即:

String url = "jdbc:///test";
Connection connection = = DriverManager.getConnection(url,"root","123456");

那么這是為什么呢?要知道DriverManager類是由啟動(dòng)類加載器加載,而且根據(jù)全盤負(fù)責(zé)委托機(jī)制,每個(gè)類都有自己的類加載器,那么負(fù)責(zé)加載當(dāng)前類的類加載器也會(huì)去加載當(dāng)前類中引用的其他類,前提是引用的類沒有被加載過。

例如ClassA中有個(gè)變量 ClassB,那么加載ClassA的類加載器會(huì)去加載ClassB,如果找不到ClassB,則異常。

根據(jù)以上特性,那么JDK中的DriverManager啟動(dòng)類加載器會(huì)嘗試去加載MySqljar包,但明顯是找不到的,因?yàn)樗静辉?strong>JDK中

那我們不妨看一下DriverManager的源碼:

繼續(xù)查看一下其中的 loadInitialDrivers() 方法:

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;
    }
	// 1
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {            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);
    // 2
    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);
            // 3
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}

分析其中兩個(gè)地方:

1、這里使用了ServiceLoader機(jī)制來加載驅(qū)動(dòng),它是Java提供的一套 SPI(Service Provider Interface) 框架,用于實(shí)現(xiàn)服務(wù)提供方與服務(wù)使用方解耦

2、使用 jdbc.drivers 定義的驅(qū)動(dòng)名加載驅(qū)動(dòng)

3、ClassLoader.getSystemClassLoader() 就是應(yīng)用程序類加載器

規(guī)則

SPI機(jī)制是JDK提供接口,第三方Jar包實(shí)現(xiàn),接口由啟動(dòng)類加載器加載,實(shí)現(xiàn)類不在JDK中,需要反向委派,由線程上下文加載器加載。它約定:在 jar 包的 META-INF/services 包下,以接口全限定名為文件名,文件內(nèi)容是實(shí)現(xiàn)類名稱

這樣便可以使用剛才loadInitialDrivers這個(gè)方法

ServiceLoader<接口類型> allImpls = ServiceLoader.load(接口類型.class);
Iterator<接口類型> iter = allImpls.iterator();
while(iter.hasNext()) {
    iter.next();
}

來得到具體的Driver實(shí)現(xiàn)類,那我們?cè)僮芬幌?strong>ServiceLoader是如何通過Driver.class接口來加載它具體的實(shí)現(xiàn)類的,現(xiàn)在進(jìn)入 load() 方法:

public static <S> ServiceLoader<S> load(Class<S> service) {
    //獲取到了線程上下文類加載器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

線程上下文類加載器是當(dāng)前線程使用的類加載器,默認(rèn)就是應(yīng)用程序類加載器,那么這個(gè)方法中的load方法就會(huì)使用剛才拿到的線程上下文類加載器去加載目標(biāo)實(shí)現(xiàn)類,不過這個(gè)方法比較深,真正加載的具體代碼在 ServiceLoader 的內(nèi)部類 LazyIteratornextService方法中:

自定義實(shí)現(xiàn)

注解

package com.phz.prpc.extension;import java.lang.annotation.*;/**
 * <p>
 * {@code SPI}注解,可運(yùn)行其他第三方實(shí)現(xiàn)的抽象接口需使用此注解
 * </p>
 * </br>
 * <p>
 * {@code JDK}的{@code SPI}機(jī)制是{@code JDK}提供接口,第三方{@code jar}包實(shí)現(xiàn),接口由啟動(dòng)類加載器加載,實(shí)現(xiàn)類不在{@code JDK}中,需要反向委派,由線程上下文加載器加載。
 * </p>
 * </br>
 * <p>
 * 它約定:在 {@code jar} 包的 {@code META-INF/services} 包下,以接口全限定名為文件名,文件內(nèi)容是實(shí)現(xiàn)類名稱
 * </p>
 * </br>
 * <p>
 * 那么我們完全可以參照它的思想取仿寫一個(gè)
 * </p>
 *
 * @author PengHuanZhi
 * @date 2022年01月16日 17:50
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Spi {
}

基于SPI的偽類加載器

package com.phz.prpc.extension;import lombok.Data;
import lombok.extern.slf4j.Slf4j;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;import static java.nio.charset.StandardCharsets.UTF_8;/**
 * <p>
 * 自己實(shí)現(xiàn)一個(gè)擴(kuò)展類加載器輔助類
 * ,區(qū)別于{@code JDK}的{@code SPI}機(jī)制,我們預(yù)定好在 {@code jar} 包的 {@code META-INF/extensions} 目錄下方存放擴(kuò)展類文件,文件內(nèi)容就為第三方實(shí)現(xiàn)的全路徑
 * </p>
 *
 * @author PengHuanZhi
 * @date 2022年01月16日 17:56
 */
@Slf4j
@Data
public final class ExtensionLoader<T> {
    /**
     * 約定第三方實(shí)現(xiàn)配置文件目錄
     **/
    private static final String SERVICE_DIRECTORY = "META-INF/extensions/";    /**
     * 接口的類型,用于獲取此接口下的第三方實(shí)現(xiàn)
     **/
    private final Class<?> type;    /**
     * 通過接口的{@link Class}對(duì)象獲取其第三方實(shí)現(xiàn)類的加載器
     *
     * @param type 接口的類型
     * @return ExtensionLoader<T> 返回一個(gè)指定接口類型的類加載器輔助類
     **/
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Spi需要知道你想要找到哪個(gè)功能的第三方實(shí)現(xiàn)!");
        }
        if (!type.isInterface()) {
            throw new IllegalArgumentException("只支持尋找接口類型的第三方實(shí)現(xiàn)!");
        }
        if (type.getAnnotation(Spi.class) == null) {
            throw new IllegalArgumentException("目標(biāo)接口必須被@Spi注解標(biāo)注!");
        }
        return new ExtensionLoader<>(type);
    }    /**
     * 獲取這個(gè)接口指定名稱的第三方實(shí)現(xiàn)對(duì)象
     *
     * @return T 返回目標(biāo)實(shí)現(xiàn)
     **/
    public T getExtension() {
        // 加載到一個(gè)第三方實(shí)現(xiàn)
        Class<T> clazz = loadExtensionFile();
        if (clazz == null) {
            return null;
        }
        try {
            return clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException("實(shí)例化失敗 : " + clazz);
        }
    }    /**
     * 加載約定好的目錄下方的名稱為接口全路徑的擴(kuò)展文件
     *
     * @return Class<T> 返回目標(biāo)第三方實(shí)現(xiàn)的{@link Class}對(duì)象
     **/
    private Class<T> loadExtensionFile() {
        //想要獲取誰的實(shí)現(xiàn)類
        String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();
        try {
            Enumeration<URL> urls;
            ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
            urls = classLoader.getResources(fileName);
            if (urls != null) {
                URL resourceUrl = urls.nextElement();
                return loadResource(classLoader, resourceUrl);
            }
            return null;
        } catch (IOException e) {
            log.error(e.getMessage());
            return null;
        }
    }    /**
     * 讀取擴(kuò)展文件的內(nèi)容,找到第三方實(shí)現(xiàn)的全路徑,并獲得其{@link Class}對(duì)象
     *
     * @param classLoader 擴(kuò)展類加載器輔助類的類加載器
     * @param resourceUrl 文件在資源{@code URL}
     * @return Class<T> 返回目標(biāo){@link Class}對(duì)象
     **/
    @SuppressWarnings("unchecked")
    private Class<T> loadResource(ClassLoader classLoader, URL resourceUrl) {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                // 可能是注釋
                final int ci = line.indexOf('#');
                //如果是第一個(gè)位置,則這一行都可以不用解析了
                if (ci == 0) {
                    continue;
                } else if (ci > 0) {
                    //如果非第一個(gè)位置,需要將注釋前面的內(nèi)容取出來,也就是將注釋后面的內(nèi)容截取
                    line = line.substring(0, ci);
                }
                return (Class<T>) classLoader.loadClass(line.trim());
            }
        } catch (IOException | ClassNotFoundException e) {
            log.error(e.getMessage());
            return null;
        }
        return null;
    }
}

測(cè)試

參考如下方式:

代碼中體現(xiàn)(因?yàn)樽远x的SPI機(jī)制用于筆者自己的項(xiàng)目下方,所以讀者可以僅關(guān)注代碼中的11行即可):

/**
 * 使用負(fù)載均衡算法從服務(wù)集合中選取一個(gè)服務(wù)
 *
 * @param serviceInstances 服務(wù)集合
 * @return InetSocketAddress 選取的服務(wù)
 **/
public InetSocketAddress doChoice(List<InetSocketAddress> serviceInstances) {
    String loadBalanceAlgorithm = prpcProperties.getLoadBalanceAlgorithm();
    LoadBalance loadBalance;
    try {
        loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension();
        if (loadBalance == null) {
            loadBalance = LoadBalanceAlgorithm.valueOf(loadBalanceAlgorithm);
        }
    } catch (IllegalArgumentException e) {
        log.error("未知的負(fù)載均衡算法:{},異常信息為:{}", loadBalanceAlgorithm, e.getMessage());
        throw new PrpcException(ErrorMsg.UNKNOWN_LOAD_BALANCE_ALGORITHM);
    }
    return loadBalance.doChoice(serviceInstances);
}

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Springboot基于websocket實(shí)現(xiàn)簡(jiǎn)單在線聊天功能

    Springboot基于websocket實(shí)現(xiàn)簡(jiǎn)單在線聊天功能

    這篇文章主要介紹了Springboot基于websocket實(shí)現(xiàn)簡(jiǎn)單在線聊天功能,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • 解決druid監(jiān)控頁面SQL不顯示的問題

    解決druid監(jiān)控頁面SQL不顯示的問題

    這篇文章主要介紹了解決druid監(jiān)控頁面SQL不顯示的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • java 獲取內(nèi)存使用率的流程實(shí)例詳解

    java 獲取內(nèi)存使用率的流程實(shí)例詳解

    這篇文章主要為大家介紹了java 獲取內(nèi)存使用率的流程實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • servlet創(chuàng)建web后端程序的示例代碼

    servlet創(chuàng)建web后端程序的示例代碼

    本文主要介紹了servlet創(chuàng)建web后端程序的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • Java多線程-線程的同步與鎖的問題

    Java多線程-線程的同步與鎖的問題

    線程的同步是為了防止多個(gè)線程訪問一個(gè)數(shù)據(jù)對(duì)象時(shí),對(duì)數(shù)據(jù)造成的破壞。本篇文章主要介紹了Java多線程-線程的同步與鎖的問題,有興趣的可以了解一下。
    2016-11-11
  • 15道非常經(jīng)典的Java面試題 附詳細(xì)答案

    15道非常經(jīng)典的Java面試題 附詳細(xì)答案

    這篇文章主要為大家推薦了15道非常經(jīng)典的Java面試題,附詳細(xì)答案,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • 解決MyBatis-Plus使用動(dòng)態(tài)表名selectPage不生效的問題

    解決MyBatis-Plus使用動(dòng)態(tài)表名selectPage不生效的問題

    這篇文章主要介紹了如惡化解決MyBatis-Plus使用動(dòng)態(tài)表名selectPage不生效的問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-11-11
  • 詳解SpringMVC中的異常處理

    詳解SpringMVC中的異常處理

    這篇文章主要介紹了SpringMVC中的異常處理的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用SpringMVC,感興趣的朋友可以了解下
    2021-03-03
  • java程序中指定某個(gè)瀏覽器打開的實(shí)現(xiàn)方法

    java程序中指定某個(gè)瀏覽器打開的實(shí)現(xiàn)方法

    最近工作中遇到一個(gè)需求,是要利用java打開指定瀏覽器,整理后發(fā)現(xiàn)有四種解決的方法,所以想著分享出來,下面這篇文章主要給大家介紹了java程序中指定某個(gè)瀏覽器打開的實(shí)現(xiàn)方法,,需要的朋友可以參考下。
    2017-03-03
  • SpringBoot編譯target目錄下沒有resource下的文件踩坑記錄

    SpringBoot編譯target目錄下沒有resource下的文件踩坑記錄

    這篇文章主要介紹了SpringBoot編譯target目錄下沒有resource下的文件踩坑記錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-08-08

最新評(píng)論