一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Java的SPI機制詳解

京東云 ? 來源:京東物流 楊葦葦 ? 作者:京東物流 楊葦葦 ? 2025-03-05 11:35 ? 次閱讀

作者:京東物流 楊葦葦

1.SPI簡介

SPI(Service Provicer Interface)是Java語言提供的一種接口發(fā)現(xiàn)機制,用來實現(xiàn)接口和接口實現(xiàn)的解耦。簡單來說,就是系統(tǒng)只需要定義接口規(guī)范以及可以發(fā)現(xiàn)接口實現(xiàn)的機制,而不需要實現(xiàn)接口。

SPI機制在Java中應(yīng)用廣泛。例如:JDBC中的數(shù)據(jù)庫連接驅(qū)動使用SPI機制,只定義了數(shù)據(jù)庫連接接口的規(guī)范,而具體實現(xiàn)由各大數(shù)據(jù)庫廠商實現(xiàn),不同數(shù)據(jù)庫的實現(xiàn)不同,我們常用的mysql的驅(qū)動也實現(xiàn)了其接口規(guī)范,通過這種方式,JDBC數(shù)據(jù)庫連接可以適配不同的數(shù)據(jù)庫。

SPI機制在各種框架中也有應(yīng)用,例如:springboot的自動裝配中查找spring.factories文件的步驟就是應(yīng)用了SPI機制;dubbo也對Java的SPI機制進行擴展,實現(xiàn)了自己的SPI機制。

2.SPI入門案例

2.1.創(chuàng)建工程

我們剛才在介紹中說過了,SPI機制需要定義接口規(guī)范,這里我們以一個簡單的接口案例來說明。

首先我們需要創(chuàng)建四個工程:

?spi-interface,這里定義SPI的接口類:Person

?spi-impl1,這里定義接口的第一個實現(xiàn)類:Teacher

?spi-impl2,這里定義接口的第二個實現(xiàn)類:Student

?spi-test,這里通過SPI機制加載所有實現(xiàn)類進行測試

wKgZPGfHxpqAeAmgAAAjzFACycg642.png

??

2.2.創(chuàng)建SPI接口規(guī)范

接口如下所示:

package com.jd.spi;

public interface Person {

    String favorite();
}

2.3.創(chuàng)建實現(xiàn)類1項目

2.3.1.創(chuàng)建接口

接口如下所示:

package com.jd.spi;


public class Teacher implements Person {

    public String favorite() {
        return "老師喜歡給學(xué)生上課";
    }
}

2.3.2.創(chuàng)建spi配置文件

如下圖所示,在項目的resources文件夾下創(chuàng)建兩個文件夾META-INF/services,然后在文件夾下面創(chuàng)建名稱為com.jd.spi.Person的文件,其文件的內(nèi)容為當(dāng)前項目的接口實現(xiàn)類com.jd.spi.Teacher。

wKgZO2fHxpuAPo_iAADdS6KCX7c957.png

??

2.4.創(chuàng)建實現(xiàn)類2項目

2.4.1.創(chuàng)建實現(xiàn)類2

接口如下所示:

package com.jd.spi;

public class Student implements Person {
    public String favorite() {
        return "學(xué)生喜歡努力學(xué)習(xí)";
    }
}

2.4.2.創(chuàng)建spi配置文件

如下圖所示,在項目的resources文件夾下創(chuàng)建兩個文件夾META-INF/services,然后在文件夾下面創(chuàng)建名稱為com.jd.spi.Person的文件,其文件的內(nèi)容為當(dāng)前項目的接口實現(xiàn)類com.jd.spi.Student。

wKgZPGfHxpuAB1NEAADUuAGqK38574.png

??

2.5.創(chuàng)建測試項目

2.5.1.引入3個maven依賴

這里需要引入接口定義項目和兩個接口實現(xiàn)項目。

如下所示:

    
        
            org.example
            spi-interface
            1.0-SNAPSHOT
        
        
            org.example
            spi-impl1
            1.0-SNAPSHOT
        
        
            org.example
            spi-impl2
            1.0-SNAPSHOT
        
    

2.5.2.創(chuàng)建測試類

如下所示:

package com.jd.spi;

import java.util.Iterator;
import java.util.ServiceLoader;

public class SPITest {

    public static void main(String[] args) {
        ServiceLoader loader = ServiceLoader.load(Person.class);
        for(Iterator it = loader.iterator(); it.hasNext();){
            Person person = it.next();
            System.out.println(person.favorite());;
        }
    }
}

運行測試類,其結(jié)果如下所示:

wKgZO2fHxpyAKyMLAABcra25luY964.png

??

我們發(fā)現(xiàn),Java的SPI機制獲取了所有Person類的實現(xiàn)類,并執(zhí)行其對應(yīng)的favorite方法。

3.SPI機制的原理

3.1.ServiceLoader的核心屬性

其核心機制就是ServiceLoader類的load方法,下面我們將從源碼來分析其原理。

首先我們先看下ServiceLoader的核心屬性:

public final class ServiceLoader
    implements Iterable
{

    private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    private final Class 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
    private LinkedHashMap providers = new LinkedHashMap();

    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;

這個PREFIX屬性、providers屬性和lookupIterator屬性將在后續(xù)的代碼中使用到,我們發(fā)現(xiàn)PREFIX屬性就是示例中說的META-INF/services路徑。

3.2.ServiceLoader的遍歷器

示例中,我們會獲取serviceLoader的遍歷器iterator,其方法如下所示:

    public Iterator iterator() {
        return new Iterator() {

            Iterator> 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();
            }

        };
    }

然后需要執(zhí)行遍歷器的next方法獲取元素,其next方法執(zhí)行的是lookupIterator.next()。

接下來我們來看下lookupIterator的next方法:

        public S next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction action = new PrivilegedAction() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

其執(zhí)行的是nextService方法,如下所示:

        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
        }

nextService方法首先執(zhí)行hasNextService方法,如下所示:

        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;
        }

這個方法會執(zhí)行String fullName = PREFIX + service.getName(),而PREFIX就是我們前面剛才說的非常重要的屬性,其值為META-INF/services/,service就是接口類,其最終的fullName指的就是META-INF/services文件夾下的名稱為com.jd.spi.Person的文件。

接著會執(zhí)行configs = loader.getResources(fullName)方法,這個方法這里不做詳細描述,其主要功能就是獲取類路徑下所有相對路徑為fullName的所有文件的URL對象。

然后會執(zhí)行pending = parse(service, configs.nextElement())方法,這個方法這里也不詳細描述,其主要功能是讀取文件,將文件內(nèi)容變成字符串,然后nextName就被賦值為當(dāng)前文件的內(nèi)容,即實現(xiàn)類的接口全限定名

因此,執(zhí)行hasNextService()方法后,nextName被賦值為一個實現(xiàn)類的全限定名。

我們繼續(xù)看上面的nextService()方法,其最終會執(zhí)行c = Class.forName(cn, false, loader)方法,這個方法很明顯就是通過反射實例化一個對象。通過一系列操作,最終返回了對應(yīng)實現(xiàn)類的對象。

3.3.流程總結(jié)

我們將其總結(jié)為以下幾個步驟:

1.創(chuàng)建ServiceLoader對象

2.創(chuàng)建迭代器lookupIterator

3.通過迭代器的hasNextService方法讀取類路徑下META-INF/services目錄的所有名稱為接口全限定名的文件,將其內(nèi)容存入configs對象中

4.從configs對象中獲取實現(xiàn)類的全限定名,然后通過反射實例化對象

從上述流程,我們也可以總結(jié)實現(xiàn)SPI的幾點重要信息:

1.實現(xiàn)工程必須在類路徑下的META-INF/services目錄下創(chuàng)建接口全限定名的文件,其文件內(nèi)容必須是接口實現(xiàn)類的全限定名

2.實現(xiàn)類必須有一個無參構(gòu)造方法,因為SPI默認是使用無參構(gòu)造方法實例化對象的

4.總結(jié)

本文首先概述了Java的SPI機制,隨后闡述了其基本使用方法,最后深入探討了其實現(xiàn)原理。SPI在Java語言體系中具有廣泛應(yīng)用,能夠有效地實現(xiàn)系統(tǒng)解耦,眾多框架基于此機制進行了拓展和優(yōu)化,從而實現(xiàn)了更為強大的SPI機制。掌握SPI的使用技巧可以幫助我們設(shè)計出更為靈活的系統(tǒng),而深入理解其原理則有助于提升我們的技術(shù)水平。

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • JAVA
    +關(guān)注

    關(guān)注

    20

    文章

    2983

    瀏覽量

    106499
  • SPI
    SPI
    +關(guān)注

    關(guān)注

    17

    文章

    1749

    瀏覽量

    94075
收藏 人收藏

    評論

    相關(guān)推薦

    高保真膽機制詳解

    http://115.com/file/be3wripk#高保真膽機制詳解.rar
    發(fā)表于 02-14 09:54

    Java開發(fā)利器Myeclipse全面詳解

    Java開發(fā)利器Myeclipse全面詳解
    發(fā)表于 11-06 11:17 ?0次下載

    java入門到詳解[推薦]

    java入門到詳解[推薦]
    發(fā)表于 03-19 11:23 ?4次下載

    基于Java反射機制的Excel文件導(dǎo)出實現(xiàn)_楊敏煜

    基于Java反射機制的Excel文件導(dǎo)出實現(xiàn)_楊敏煜
    發(fā)表于 03-18 09:46 ?1次下載

    java類加載機制圖文詳解

    解析生成對應(yīng)的Class對象,這就是類加載器的功能。我們可以利用類加載器,實現(xiàn)類的動態(tài)加載。 二、類的加載機制Java中,采用雙親委派機制來實現(xiàn)類的加載。那什么是雙親委派機制?在
    發(fā)表于 09-27 14:27 ?0次下載
    <b class='flag-5'>java</b>類加載<b class='flag-5'>機制</b>圖文<b class='flag-5'>詳解</b>

    詳解java并發(fā)機制

    在一般性開發(fā)中,筆者經(jīng)??吹胶芏嗤瑢W(xué)在對待java并發(fā)開發(fā)模型中只會使用一些基礎(chǔ)的方法。比如Volatile,synchronized。像Lock和atomic這類高級并發(fā)包很多人并不經(jīng)常使用。我想
    發(fā)表于 09-27 14:31 ?0次下載

    java的動態(tài)代理機制和作用

    的我們的功能,我們更需要學(xué)習(xí)的是其底層是怎么樣的一個原理,而AOP的原理就是java的動態(tài)代理機制,所以本篇隨筆就是對java的動態(tài)機制進行一個回顧。 在
    發(fā)表于 09-27 14:37 ?0次下載

    java動態(tài)代理機制詳解的類和接口描述

    的我們的功能,我們更需要學(xué)習(xí)的是其底層是怎么樣的一個原理,而AOP的原理就是java的動態(tài)代理機制,所以本篇隨筆就是對java的動態(tài)機制進行一個回顧。 在
    發(fā)表于 09-28 13:33 ?0次下載

    Java反射機制到底是什么?有什么作用

    Java反射機制Java 語言的一個重要特性,它在服務(wù)器程序和中間件程序中得到了廣泛運用。在服務(wù)器端,往往需要根據(jù)客戶的請求,動態(tài)調(diào)用某一個對象的特定方法。此外,在 ORM 中間件的實現(xiàn)中,運用
    的頭像 發(fā)表于 02-15 14:07 ?4919次閱讀

    礦石收音機制詳解

    礦石收音機制詳解
    發(fā)表于 12-27 17:52 ?98次下載

    源碼級深度理解Java SPI

    SPI 配置:Java SPI 機制約定的配置文件,提供查找服務(wù)實現(xiàn)類的邏輯。配置文件必須置于 META-INF/services 目錄中,并且,文件名應(yīng)與服務(wù)提供者接口的完全限定名保
    的頭像 發(fā)表于 11-15 11:38 ?793次閱讀

    基于spring的SPI擴展機制是如何實現(xiàn)的?

    基本上,你一說是基于 spring 的 SPI 擴展機制,再把spring.factories文件和EnableAutoConfiguration提一下,那么這個問題就答的八九不離十了。
    的頭像 發(fā)表于 03-07 09:17 ?1246次閱讀

    Java、Spring、Dubbo三者SPI機制的原理和區(qū)別

    其實我之前寫過一篇類似的文章,但是這篇文章主要是剖析dubbo的SPI機制的源碼,中間只是簡單地介紹了一下Java、Spring的SPI機制
    的頭像 發(fā)表于 06-05 15:21 ?1257次閱讀
    <b class='flag-5'>Java</b>、Spring、Dubbo三者<b class='flag-5'>SPI</b><b class='flag-5'>機制</b>的原理和區(qū)別

    SPI是什么?Java SPI的使用介紹

    SPI 全稱 Service Provider Interface,是 Java 提供的一套用來被第三方實現(xiàn)或者擴展的 API,它可以用來啟用框架擴展和替換組件。
    的頭像 發(fā)表于 09-02 09:58 ?1604次閱讀
    <b class='flag-5'>SPI</b>是什么?<b class='flag-5'>Java</b> <b class='flag-5'>SPI</b>的使用介紹

    什么是SPI機制

    1、前言 在之前的 JVM 分析系列之類加載 提到過 Java SPI 機制,主要是類加載器反雙親委派的實現(xiàn)(第三方包不在指定jdk路徑,一般類加載器無法加載,需要特殊
    的頭像 發(fā)表于 10-08 15:03 ?1425次閱讀
    什么是<b class='flag-5'>SPI</b><b class='flag-5'>機制</b>