Java SPI

什么是SPI


SPI是jdk中的一種服務(wù)發(fā)現(xiàn)機(jī)制,在java中可以用來擴(kuò)展API和第三方實(shí)現(xiàn)。相比于API于毙,可以動(dòng)態(tài)替換發(fā)現(xiàn)皮迟。

我的理解:SPI是一種動(dòng)態(tài)服務(wù)發(fā)現(xiàn)方式搬泥。如果我們想要調(diào)用別人實(shí)現(xiàn)的方法,那么肯定是調(diào)用別人的實(shí)現(xiàn)類來操作伏尼。但是java里面是面向接口編程忿檩,實(shí)現(xiàn)多個(gè)類之間的解耦。所以SPI操作就是你可以通過調(diào)用接口來調(diào)用實(shí)現(xiàn)類爆阶。那么如何才能知道接口和實(shí)現(xiàn)類之間的綁定呢燥透?就是通過配置文件,那么我們?cè)趺醇虞d配置文件辨图,使用Classloader類加載器去加載班套,這樣就可以了。

jdk中使用SPI方式


  1. 在jar包的META-INF/services目錄下創(chuàng)建一個(gè)以"接口全限定名"為命名的文件故河,內(nèi)容為實(shí)現(xiàn)類的全限定名吱韭。
  2. 接口實(shí)現(xiàn)類所在的jar包在classpath下。
  3. 主程序通過java.util.ServiceLoader動(dòng)態(tài)狀態(tài)實(shí)現(xiàn)模塊鱼的,它通過掃描META-INF/services目錄下的配置文件找到實(shí)現(xiàn)類的全限定名理盆,把類加載到JVM。
  4. SPI的實(shí)現(xiàn)類必須帶一個(gè)無參構(gòu)造方法凑阶。

自定義SPI實(shí)現(xiàn)同包調(diào)用


思路:

  • 創(chuàng)建META-INF/services目錄猿规,放在classpath下面(放在resources下面)。
  • 在META-INF/services目錄下創(chuàng)建一個(gè)以"接口全限定名"為命名的文件宙橱,內(nèi)容為實(shí)現(xiàn)類的全限定名姨俩。
  • 主程序通過java.util.ServiceLoader動(dòng)態(tài)狀態(tài)實(shí)現(xiàn)模塊,它通過掃描META-INF/services目錄下的配置文件找到實(shí)現(xiàn)類的全限定名养匈,把類加載到JVM。

目錄結(jié)構(gòu):

com.sunpy.permissionservice.spi.ISpiSunpyService文件:

com.sunpy.permissionservice.spi.SpiSunpyServiceOne
com.sunpy.permissionservice.spi.SpiSunpyServiceTwo

接口和實(shí)現(xiàn)類:

public interface ISpiSunpyService {

    public void outMsg();
}

public class SpiSunpyServiceOne implements ISpiSunpyService{

    @Override
    public void outMsg() {
        System.out.println("SpiSunpyServiceOne服務(wù)信息");
    }
}

public class SpiSunpyServiceTwo implements ISpiSunpyService{

    @Override
    public void outMsg() {
        System.out.println("SpiSunpyServiceTwo服務(wù)信息");
    }
}

測(cè)試:

@Test
public void spiTest() {
    ServiceLoader<ISpiSunpyService> serviceLoader = ServiceLoader.load(ISpiSunpyService.class);
    serviceLoader.forEach(ISpiSunpyService::outMsg);
}

自定義SPI實(shí)現(xiàn)引入jar包調(diào)用


public class SunpyTest {

    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/services/com.sunpy.spi.ISpiService";


    public static Map<String, List<String>> loadClassName() throws IOException {
        ClassLoader classLoader = SunpyTest.class.getClassLoader();
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        Map<String, List<String>> map = new HashMap<>();
        List<String> list = new ArrayList<>();

        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            URLConnection urlConnection = url.openConnection();
            BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
            String content = "";

            while ((content = br.readLine()) != null) {
                list.add(content);
            }
            map.put(FACTORIES_RESOURCE_LOCATION, list);
        }

        return map;
    }

    public static void doMethod() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        Map<String, List<String>> map = loadClassName();
        List<String> list = map.get(FACTORIES_RESOURCE_LOCATION);
        for (String clazzName : list) {
            Class<?> clazz = Class.forName(clazzName);

            Method method = clazz.getMethod("outMsg");
            method.invoke(clazz.newInstance());
        }

    }

    public static void main(String[] args) throws Exception {
        doMethod();
    }
}

SPI應(yīng)用場(chǎng)景


  • springboot中的SPI
    配置文件:

獲取spring中的實(shí)例都伪,委派給SpringFactoriesLoader.loadFactoryNames加載全限定類名呕乎。

// 獲取spring的實(shí)例
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    // 獲取類加載器
    ClassLoader classLoader = getClassLoader();
    /**
     * 使用給定的類加載器從“META-INF/spring.factories”加載給定類型的工廠實(shí)現(xiàn)的全限定類名。
     */
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 創(chuàng)建實(shí)例集合
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

SpringFactoriesLoader.loadFactoryNames的實(shí)現(xiàn)

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    result = new HashMap<>();
    try {
        // public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                String[] factoryImplementationNames =
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                for (String factoryImplementationName : factoryImplementationNames) {
                    result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                            .add(factoryImplementationName.trim());
                }
            }
        }

        // Replace all lists with unmodifiable lists containing unique elements
        result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
        cache.put(classLoader, result);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
}

查看:

實(shí)例化類:

@SuppressWarnings("unchecked")
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
        ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    // 拿著加載的所有的全限定名陨晶,進(jìn)行實(shí)例化
    for (String name : names) {
        try {
            // 反射加載全限定名的Class
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            // 反射獲取Constructor類
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            // 反射通過構(gòu)造器實(shí)例化類
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

參考


https://xie.infoq.cn/article/2021f8d92c93c94b96ebf4a3d
https://www.cnblogs.com/xrq730/p/11440174.html
https://blog.csdn.net/weixin_43476824/article/details/125739140

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末猬仁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子先誉,更是在濱河造成了極大的恐慌湿刽,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件褐耳,死亡現(xiàn)場(chǎng)離奇詭異诈闺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)铃芦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門雅镊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來襟雷,“玉大人,你說我怎么就攤上這事仁烹∷逝” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵卓缰,是天一觀的道長(zhǎng)计呈。 經(jīng)常有香客問我,道長(zhǎng)征唬,這世上最難降的妖魔是什么捌显? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮鳍鸵,結(jié)果婚禮上苇瓣,老公的妹妹穿的比我還像新娘。我一直安慰自己偿乖,他們只是感情好击罪,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贪薪,像睡著了一般媳禁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上画切,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天竣稽,我揣著相機(jī)與錄音,去河邊找鬼霍弹。 笑死毫别,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的典格。 我是一名探鬼主播岛宦,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼耍缴!你這毒婦竟也來了砾肺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤防嗡,失蹤者是張志新(化名)和其女友劉穎变汪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚁趁,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡裙盾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闷煤。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡童芹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鲤拿,到底是詐尸還是另有隱情假褪,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布近顷,位于F島的核電站生音,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏窒升。R本人自食惡果不足惜缀遍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望饱须。 院中可真熱鬧域醇,春花似錦、人聲如沸蓉媳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酪呻。三九已至减宣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間玩荠,已是汗流浹背漆腌。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留阶冈,地道東北人闷尿。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像女坑,于是被迫代替她去往敵國(guó)和親填具。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容