java SPI機制

什麼是SPI    

    軟件很大程度是真實世界的反映,思想、模式都能在真實事物找到影子。這不難理解,因爲不管是真實事物還是軟件都是由人類創造,都是朝着高效、低成本方向發展,難免相互借鑑。SPI也是如此,SPI全稱Service Provider Interface。本質就是上層軟件制定所需模塊的接口規範,然後基於這個規範搭建上層建築,至於模塊誰來開發實現並不關心,反正廠商按照接口規範做出來就好。好比汽車廠商開發一款新型汽車,輪胎不要他生產,他只要告訴輪胎廠商這款車要用195/55/R16 85V 規格的輪胎,輪胎廠商按照這個標準生產即可。

    SPI的應用比較常見,比如說 (1) jdbc驅動,如果針對具體的數據庫開發軟件,換數據庫將是災難級調整。所以業務面向jdbc接口規範開發,利用SPI自動發現接口實現類將極大提高軟件的擴展性;(2) 之前寫過的ServletContainerInitializer,servlet容器用SPI發現spring-web包中的SpringServletContainerInitializer從而啓動初始化應用;(3) 此外,還有日誌框架、validation(hibernator-validator)也是基於SPI機制發現具體的服務實現。

SPI機制原理

    SPI的核心是java.util.ServiceLoader。ServiceLoader大致的過程是在classpath*:META-INF/services/目錄下查找配置文件(名稱是接口的全路徑類名),這個文件裏面每一行就是實現類的全路徑類名,然後用當前線程的classloader加載實現類。具體實現可以看看jdk源碼:

public final class ServiceLoader<S> implements Iterable<S>{
    //查找的目錄
    private static final String PREFIX = "META-INF/services/";
    ......

    //內部類 具體的查找的實現
    private class LazyIterator implements Iterator<S>{
        ......
        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;
        }
        ......
    }
    ......
}

spi demo程序

    整一個demo程序跑一跑,否則太乾巴巴的了,demo程序儘量簡單,只是打個日誌,實現2個具體的實現類。

    首先,定一個接口LogPrint

public interface LogPrint {
    //打印日誌
    void  print(String message);
}

   其次,創建2個實現類LogPrintImplA、LogPrintImplB

package com.focuse.jdkdemo.spi;

public class LogPrintImplA implements LogPrint {
    @Override
    public void print(String message) {
        System.out.println("LogPrinterA: " + message);
    }
}
package com.focuse.jdkdemo.spi;

public class LogPrintImplB implements LogPrint {
    @Override
    public void print(String message) {
        System.out.println("LogPrinterB: " + message);
    }
}

第三步,在目錄META-INF/services下創建文件com.focuse.jdkdemo.spi.LogPrint,文件內容2行。

最後,用ServiceLoader加載實現類,並運行程序

package com.focuse.jdkdemo.spi;

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

/**
 * @author :focuse
 * @date :Created in 2020/2/16 上午11:10
 * @description:
 * @modified By:
 */
public class SPIDemo {

    public static void main(String[] args) {
        ServiceLoader<LogPrint> loader = ServiceLoader.load(LogPrint.class);
        Iterator<LogPrint> it = loader.iterator();
        while (it.hasNext()) {
            it.next().print("hello spi!");
        }
    }
}

ps: 爲了讓demo程序簡單,就沒有用jar引用的形式,而是直接在一個工程裏面完成所有的事情

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章