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

一篇文章帶你理解Java的SPI機制(圖文并茂)

 更新時間:2024年10月29日 09:44:01   作者:家鄉(xiāng)的落日  
本文詳細介紹了Java的SPI機制,包括其定義、用途和實現(xiàn)方式,SPI(ServiceProviderInterface)是一種服務發(fā)現(xiàn)機制,用于實現(xiàn)框架或庫的擴展點,文中通過代碼介紹的非常詳細,需要的朋友可以參考下

一、Java的SPI機制

1、什么是Java的SPI ?

SPI全稱 Service Provider Interface ,字面意思:“服務提供者的接口”,是一種服務發(fā)現(xiàn)機制。

用于實現(xiàn)框架或庫的擴展點,允許在運行時動態(tài)地插入或更換組件實現(xiàn)。它提供了一個框架來發(fā)現(xiàn)和加載服務實現(xiàn),使得軟件模塊能夠靈活地選擇和使用不同的服務提供商。SPI鼓勵松耦合的設計,因為服務的消費者不需要直接依賴于具體的服務實現(xiàn)。有沒有想到面向對象的某個設計原則,SOLID中的 O 開閉原則,對擴展開放對修改關閉。Java允許服務提供者,按照SPI給定的規(guī)則實現(xiàn)自己的服務,而Java應用使用通過SPI規(guī)則提供的服務時無需進行額外的配置,并且可以隨時替換服務,也無需修改業(yè)務代碼。

SPI可以和我們常用的API對比著理解。

API全稱、Application Programming Interface,字面意思:“應用程序編程接口” 。API是一組規(guī)則和定義,允許一個軟件應用與另一個軟件應用、庫或操作系統(tǒng)進行交互。它定義了如何進行數(shù)據(jù)傳輸、請求服務或執(zhí)行特定功能的協(xié)議和工具。API為開發(fā)人員提供了一種標準化的方式來訪問和使用預先構建的功能,而無需了解這些功能內部的復雜實現(xiàn)細節(jié)。

總結:

名稱目的使用者舉例
SPI支持可插拔的架構,便于組件和服務的替換與擴展。主要由服務提供者(如庫、框架開發(fā)者)實現(xiàn),但也需要應用開發(fā)者配置以啟用特定的服務實現(xiàn)。(這里說的配置一般就是引入jar或者maven、gradle的坐標即可)Java中,JDBC驅動的加載就是一個典型的SPI應用。
API簡化開發(fā)過程,提高效率,促進不同系統(tǒng)間的互操作性。通常由應用開發(fā)者使用,以集成外部服務或內部模塊。語音識別 API、文件上傳 API等

個人感覺SPI就是種"設計模式",只是不在常見的23種設計模式之中,Java利用"SPI這種設計模式" 實現(xiàn)靈活的選擇服務供應商實現(xiàn),來達到解耦的目的,以此提高程序的可修改性和靈活性。
SPI和外觀設計模式也有相通之處、外觀模式(Facade) 定義了一個高層接口,為子系統(tǒng)中的一組接口提供一個一致的界面,從而簡化子系統(tǒng)的使用。

簡單的圖示對比:拿NASA的重返月球計劃舉例。

NASA準備把重返月球計劃的著陸器設計部分分包給商業(yè)航天公司來完成。

如果還按照API的方式來實現(xiàn),那么可能就是下圖中的結果。NASA想替換某個方案就必須修改系統(tǒng)去適配其他公司的API接口。
這樣代碼的耦合性就大大提高了。并且導致NASA失去了主動性。NASA這個甲方才不愿意這么干。 所以NASA決定采用SPI規(guī)則來約束供應商。

NASA給出了關鍵性的技術指標。

假設目前有SpaceX 和 Blue Origin兩家公司中標 去完成著陸器的設計。 這兩家公司需要遵循NASA給定的SPI規(guī)則進行設計。
兩家公司月球著陸器產品設計完成后,NASA只需要按照SPI規(guī)則去加載對應的產品即可。

下圖可以看出,雖然兩家公司的火箭設計不一樣,但是他們的接口都符合NASA指定的接口規(guī)則。這樣NASA就可以很輕松的集成兩家公司的方案,如果覺得SpaceX的方案不好,直接拿Blue Origin的方案無縫替代即可。

2、JavaSPI 代碼示例 (使用Maven項目演示)

①、NASA先定義SPI接口 并發(fā)布到本地倉庫供SpaceX和BlueOrigin實現(xiàn)項目結構:

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nasa</groupId>
    <artifactId>SPI-interface</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
</project>

maven打包安裝到本地倉庫

打包好的jar名稱

SPI接口:

package com.nasa;
/**
 * NASA提供的SPI接口
 * */
public interface LandingOnTheMoon {
    /**
     * 著陸方法
     */
    void land();
}

SPI加載實現(xiàn)(稍后會詳細介紹):

package com.nasa;

import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;

/**
 * 加載具體的服務實現(xiàn)
 * */
public class LandingOnTheMoonLoader {

    private static volatile LandingOnTheMoonLoader LOADER;

    private final LandingOnTheMoon landingOnTheMoon;

    private final List<LandingOnTheMoon> landingOnTheMoons;

    /**
     * 加載服務
     * */
    private LandingOnTheMoonLoader() {
        ServiceLoader<LandingOnTheMoon> loader = ServiceLoader.load(LandingOnTheMoon.class);
        List<LandingOnTheMoon> list = new ArrayList<>();
        for (LandingOnTheMoon landingOnTheMoon : loader) {
            list.add(landingOnTheMoon);
        }
        landingOnTheMoons = list;
        if (!list.isEmpty()) {
            // 取第一個
            landingOnTheMoon = list.get(0);
        } else {
            landingOnTheMoon = null;
        }
    }


    /**
     * LandingOnTheMoonLoader 單例加載
     * */
    public static LandingOnTheMoonLoader getLOADER() {
        if (LOADER == null) {
            synchronized (LandingOnTheMoonLoader.class) {
                if (LOADER == null) {
                    LOADER = new LandingOnTheMoonLoader();
                }
            }
        }
        return LOADER;
    }


    public void land(){
        if(landingOnTheMoons.isEmpty()){
            System.out.println("LandingOnTheMoon服務未加載!");
        }else {
            LandingOnTheMoon landingOnTheMoon = landingOnTheMoons.get(0);
            landingOnTheMoon.land();
        }
    }
}

②、SpaceX實現(xiàn)自己的登陸月球方案

先引入NASA 指定的SPI接口

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.spacex</groupId>
    <artifactId>SpaceX</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>com.nasa</groupId>
            <artifactId>SPI-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

SpaceX的登陸月球實現(xiàn):

package com.spacex;

import com.nasa.LandingOnTheMoon;

/**
 * SpaceX實現(xiàn)的月球著陸方案
 * */
public class SpaceXLand implements LandingOnTheMoon {
    @Override
    public void land() {
        System.out.println("SpaceX landing on the moon with StarShip~");
    }
}

SpaceX的登陸月球實現(xiàn)配置信息:

注意: Maven項目中 META-INF/services/SPI接口全限定名 需要放在resources目錄下

內容為SpaceX實現(xiàn)類的全限定名 :

③、BlueOrigin實現(xiàn)自己的登陸月球方案

先引入NASA 指定的SPI接口

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.blue</groupId>
    <artifactId>BlueOrigin</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>com.nasa</groupId>
            <artifactId>SPI-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

BlueOrigin的登陸月球實現(xiàn):

package com.blue;

import com.nasa.LandingOnTheMoon;

/**
 * 藍色起源實現(xiàn)的月球著陸方案
 * */
public class BlueOriginLand implements LandingOnTheMoon {
    @Override
    public void land() {
        System.out.println("BlueOrigin landing on the moon with  NewGlenn~");
    }
}

BlueOrigin的登陸月球實現(xiàn)配置信息:Maven項目中 META-INF/services/SPI接口全限定名 需要放在resources目錄下

內容為BlueOrigin實現(xiàn)類的全限定名 :

編寫測試代碼:

新建一個Maven項目

pom文件中引入SpaceX的實現(xiàn)坐標

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nasa</groupId>
    <artifactId>NASA-LANDING</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
    
        <!--使用藍色起源的著陸實現(xiàn)-->
  <!--      <dependency>
            <groupId>com.blue</groupId>
            <artifactId>BlueOrigin</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>-->

        <!--使用SpaceX的著陸實現(xiàn)-->
        <dependency>
            <groupId>com.spacex</groupId>
            <artifactId>SpaceX</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    </dependencies>

</project>
package com.nasa;

public class LandingShow {

    public static void main(String[] args) {
        LandingOnTheMoonLoader service = LandingOnTheMoonLoader.getLOADER();
        service.land();
    }
}

執(zhí)行結果:

SpaceX landing on the moon with StarShip~

此時如果NASA覺得SpaceX的方案不行,需要更換BlueOrigin的方案,操作起來非常簡單,只需要注釋掉SpaceX的坐標,添加BlueOrigin的坐標,業(yè)務代碼完全不用改。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nasa</groupId>
    <artifactId>NASA-LANDING</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--使用藍色起源的著陸實現(xiàn)-->
        <dependency>
            <groupId>com.blue</groupId>
            <artifactId>BlueOrigin</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <!--使用SpaceX的著陸實現(xiàn)-->
<!--        <dependency>-->
<!--            <groupId>com.spacex</groupId>-->
<!--            <artifactId>SpaceX</artifactId>-->
<!--            <version>1.0-SNAPSHOT</version>-->
<!--        </dependency>-->

    </dependencies>

</project>

執(zhí)行結果:

BlueOrigin landing on the moon with  NewGlenn~

3、 JavaSPI 機制的核心-ServiceLoader

上面代碼中我們使用ServiceLoader 去加載具體的服務實現(xiàn)。

ServiceLoader 是從JDK1.6 開始提供的一個類,用于加載服務提供者。

我們看下源碼:

其中 String PREFIX = “META-INF/services/”;

這個就是JDK的SPI功能規(guī)定的具體服務實現(xiàn)的配置信息文件所在的目錄 META-INF/services/

JDK的SPI規(guī)定 服務實現(xiàn)者需要在 META-INF/services/ 目錄下 新建文件名為 SPI接口全限定類名的文件

文件內容為 服務實現(xiàn)者需要被加載的具體類的全限定類名

我們上面代碼在加載服務的時候調用了ServiceLoader.load(LandingOnTheMoon.class);方法

ServiceLoader<LandingOnTheMoon> loader = ServiceLoader.load(LandingOnTheMoon.class);

看下這個方法的源碼:

public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

可以看到這個方法 只是獲取了當前線程的上下文類加載器 然后又調用了一個load的重載方法

public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

在重載的load方法中又調用了ServiceLoader的私有構造器

 private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

然后調用 reload();方法

  public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

reload();方法里面 調用了 new LazyIterator(service, loader);

具體的類加載行為就是在LazyIterator內部類中完成的。

這里的 lookupIterator = new LazyIterator(service, loader); 創(chuàng)建LazyIterator 懶加載迭代器對象,并沒有馬上去加載SPI的具體服務。

當我們調用 for (LandingOnTheMoon landingOnTheMoon : loader) {…} 循環(huán)時,會觸發(fā)迭代器,在ServiceLoader中,迭代器是由Iterable接口的iterator()方法提供的。當?shù)谝淮握{用iterator()時,如果之前沒有創(chuàng)建過迭代器,它會創(chuàng)建一個新的LazyIterator實例。

對應下面的源碼:

//  lookupIterator 在 調用reload 方法時 創(chuàng)建過了
private LazyIterator lookupIterator;
// 存儲已經加載過的服務提供者實例  在 調用reload 方法時   調用了providers.clear(); 清空了這個集合
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    
public Iterator<S> iterator() {
     return new Iterator<S>() {
     	 // 第一次調用時 providers 是空集合 會調用  lookupIterator 也就是 LazyIterator 的實例
         Iterator<Map.Entry<String,S>> 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();
         }

     };
 }

下面看 LazyIterator 的實現(xiàn) 具體的類加載就在這里:

LazyIterator 的 hasNext 和 next方法 中判斷了 acc 是否是null

其中 acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; 這段代碼是在 ServiceLoader初始化的時候執(zhí)行的
主要用于判斷 當前Java運行環(huán)境中是否存在安全管理器(SecurityManager),默認情況下不會配置,如果配置了SecurityManager,某些敏感操作(比如文件訪問、網(wǎng)絡連接等)可能會受到安全策略的限制或需要相應的權限檢查。

這個不重要,我們還是看具體的方法 hasNextService 和 nextService方法。

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

LazyIterator 的 hasNextService 和 nextService方法終于到了加載具體服務的地方了。

// 迭代器
Iterator<String> pending = null;
  
private boolean hasNextService() {
      if (nextName != null) {
          return true;
      }
      if (configs == null) {
          try {
          	  // 這里獲取 SPI配置文件的文件名  包含 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();
    // nextName 在hasNextService中已經被賦值了
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        //  關鍵點終于來了  最終還是利用Java的反射機制 完成類的加載
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service,
             "Provider " + cn + " not found");
    }
    // 判斷下 加載的 Class 的類型 是否實現(xiàn)了 給定的 SPI接口   拿上面NASA登月的例子看,這里的 service 就是  LandingOnTheMoon.class
    if (!service.isAssignableFrom(c)) {
        fail(service,
             "Provider " + cn  + " not a subtype");
    }
    try {
    	// 通過反射實例化 SPI的實現(xiàn)類 并且轉換成 SPI接口類型
        S p = service.cast(c.newInstance());
        // 保存到  LinkedHashMap<String,S> providers 緩存中
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    throw new Error();          // This cannot happen
}

看 具體的parse 方法(解析服務實現(xiàn)者提供的配置文件)

private Iterator<String> parse(Class<?> service, URL u)
        throws ServiceConfigurationError
    {
        InputStream in = null;
        BufferedReader r = null;
        // 保存解析配置文件內的 全限定類名
        ArrayList<String> names = new ArrayList<>();
        try {
            in = u.openStream();
            r = new BufferedReader(new InputStreamReader(in, "utf-8"));
            int lc = 1;
            // 一行一行的讀取配置信息 并將每行解析出的全限定類名 保存到 names 
            // parseLine 方法不用看了 就是解析字符串的
            while ((lc = parseLine(service, u, r, lc, names)) >= 0);
        } catch (IOException x) {
            fail(service, "Error reading configuration file", x);
        } finally {
            try {
                if (r != null) r.close();
                if (in != null) in.close();
            } catch (IOException y) {
                fail(service, "Error closing configuration file", y);
            }
        }
        // 返回 擁有所有實現(xiàn)SPI接口的服務提供者全限定類名集合的迭代器
        return names.iterator();
    }

繞了半天 通過源碼發(fā)現(xiàn) 最終類的加載還是通過 Java反射機制實現(xiàn)的。

那了解了ServiceLoader的具體實現(xiàn)之后,我們也可以實現(xiàn)一個自己的 服務加載器。

ServiceLoader只不過在實現(xiàn)了核心的SPI服務加載功能的基礎上增加了一些額外的功能,比如通過LazyIterator 實現(xiàn)延遲加載,

可以調用reload() 在應用運行時實現(xiàn)服務的動態(tài)加載(不用重啟應用就能加載服務),訪問控制(比如判斷 當前Java運行環(huán)境中是否存在安全管理器),還有按照加載順序保存已加載的服務等等。不過ServiceLoader也是線程不安全的這點需要注意。

4、實現(xiàn)自己的ServiceLoader

我們實現(xiàn)一個最簡單的ServiceLoader只考慮加載服務的功能,其他的安全、性能之類的都不考慮。

我們也仿照ServiceLoader類的結構,和SPI配置類規(guī)定的路徑來實現(xiàn)。

package com.nasa;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.*;

public class MySimpleServiceLoader<T> {

    // 直接copy ServiceLoader的規(guī)則
    private static final String PREFIX = "META-INF/services/";


    // 定義的SPI接口
    private final Class<T> service;

    // 類加載器
    private final ClassLoader loader;

    // 保存已加載的服務實例
    private final LinkedHashMap<String, T> providers = new LinkedHashMap<>();

    // 構造方法T
    private MySimpleServiceLoader(Class<T> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        // 加載服務
        realLoad();
    }

    public static <T> MySimpleServiceLoader<T> load(Class<T> service) {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        return new MySimpleServiceLoader<>(service, contextClassLoader);
    }


    private void realLoad() {
        String fullName = PREFIX + service.getName();
        try {
            // 獲取配置文件
            Enumeration<URL> resources = loader.getResources(fullName);
            // 讀取配置文件
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                InputStream inputStream = url.openStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
                // 我們就不做解析錯誤之類的邏輯了  默認給的配置文件都是對的
                String name = reader.readLine();
                // 通過反射創(chuàng)建對象
                Class<?> aClass = Class.forName(name, false, loader);
                // 判斷 服務實現(xiàn)類是否實現(xiàn)了 給定的SPI接口
                if (service.isAssignableFrom(aClass)) {
                    T cast = service.cast(aClass.newInstance());
                    providers.put(name, cast);
                }
            }
        } catch (Exception e) {
            System.out.println("出現(xiàn)異常!");
            e.printStackTrace();
        }
    }

    // 返回加載好的 服務實現(xiàn)
    public LinkedHashMap<String, T> getProviders() {
        return providers;
    }
}

使用自定義的服務加載器 MySimpleServiceLoader

package com.nasa;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

/**
 * 加載具體的服務實現(xiàn)
 * */
public class LandingOnTheMoonLoader {

    private static volatile LandingOnTheMoonLoader LOADER;

    private final LandingOnTheMoon landingOnTheMoon;

    private final List<LandingOnTheMoon> landingOnTheMoons = new ArrayList<>();

    /**
     * 加載服務
     * */
    private LandingOnTheMoonLoader() {
        // 通過自定義的服務加載器 去加載服務實現(xiàn)
        MySimpleServiceLoader<LandingOnTheMoon> mySimpleServiceLoader = MySimpleServiceLoader.load(LandingOnTheMoon.class);
        LinkedHashMap<String, LandingOnTheMoon> providers = mySimpleServiceLoader.getProviders();

        providers.forEach((k,v)->{
            System.out.println(k);
            landingOnTheMoons.add(v);
        });
        if (!landingOnTheMoons.isEmpty()) {
            // 取第一個
            landingOnTheMoon = landingOnTheMoons.get(0);
        } else {
            landingOnTheMoon = null;
        }

    }


    /**
     * LandingOnTheMoonLoader 單例加載
     * */
    public static LandingOnTheMoonLoader getLOADER() {
        if (LOADER == null) {
            synchronized (LandingOnTheMoonLoader.class) {
                if (LOADER == null) {
                    LOADER = new LandingOnTheMoonLoader();
                }
            }
        }
        return LOADER;
    }


    public void land(){
        if(landingOnTheMoons.isEmpty()){
            System.out.println("LandingOnTheMoon服務未加載!");
        }else {
            LandingOnTheMoon landingOnTheMoon = landingOnTheMoons.get(0);
            landingOnTheMoon.land();
        }
    }
}

測試:

public static void main(String[] args) {
        LandingOnTheMoonLoader service = LandingOnTheMoonLoader.getLOADER();
        service.land();
    }

使用SpaceX的實現(xiàn),結果如下:

com.spacex.SpaceXLand
SpaceX landing on the moon with StarShip~

5、Java中還有哪些SPI實現(xiàn)?

  • 1、JDBC (Java Database Connectivity): JDBC驅動的加載就是SPI的一個經典應用。各個數(shù)據(jù)庫廠商提供自己的JDBC驅動實現(xiàn),應用程序無需直接引用具體驅動的實現(xiàn)類,只需將驅動JAR包放入類路徑,Java SPI機制會自動發(fā)現(xiàn)并加載合適的驅動。

  • 2、日志框架: 如SLF4J (Simple Logging Facade for Java) 和Logback、Log4j等,它們利用SPI機制來發(fā)現(xiàn)和加載具體的日志實現(xiàn)。用戶可以根據(jù)需要選擇或更換日志實現(xiàn),而無需修改應用程序代碼。

  • 3、Spring框架: 雖然Spring框架本身更傾向于使用其自身的bean工廠和依賴注入機制來管理組件和服務,但Spring也支持SPI機制,特別是在某些擴展點和與第三方庫集成時。

  • 4、Dubbo: Apache Dubbo是一個高性能的RPC框架,它大量使用SPI機制來實現(xiàn)其插件體系,允許用戶輕松替換或擴展序列化、負載均衡、集群容錯等核心組件。

  • 5、JNDI (Java Naming and Directory Interface): JNDI服務提供者也是通過SPI機制注冊和發(fā)現(xiàn)的,允許應用程序訪問不同的命名和目錄服務。

  • 6、JAX-WS (Java API for XML Web Services) 和 JAX-RS (Java API for RESTful Web Services): 這些Java Web服務技術框架使用SPI來發(fā)現(xiàn)和加載實現(xiàn)特定功能的服務提供者,比如SOAP綁定和HTTP連接器。

  • 7、JavaBeans Activation Framework (JAF): 用于處理MIME類型的郵件附件等,通過SPI來發(fā)現(xiàn)數(shù)據(jù)處理器。

  • 8、JavaMail: 用于發(fā)送和接收電子郵件的API,利用SPI來加載傳輸協(xié)議和其他服務提供者。

總結

到此這篇關于Java的SPI機制的文章就介紹到這了,更多相關Java的SPI機制內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java8使用Supplier啟動ScheduledThread代碼實例

    Java8使用Supplier啟動ScheduledThread代碼實例

    這篇文章主要介紹了Java8使用Supplier啟動ScheduledThread詳解,項目開啟立即啟動定時任務是很多項目都會遇到的一個需求,如何利用Java提供的函數(shù)優(yōu)雅的寫出來十分考驗一個人的功底,需要的朋友可以參考下
    2024-01-01
  • bug解決Failed_to_execute_goal_org.springframework

    bug解決Failed_to_execute_goal_org.springframework

    這篇文章主要為大家介紹了bug解決Failed_to_execute_goal_org.springframework,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • IDEA安裝部署Alibaba Cloud Toolkit的實現(xiàn)步驟

    IDEA安裝部署Alibaba Cloud Toolkit的實現(xiàn)步驟

    Alibaba Cloud Toolkit是阿里云針對IDE平臺為開發(fā)者提供的一款插件,本文主要介紹了IDEA安裝部署Alibaba Cloud Toolkit的實現(xiàn)步驟,具有一定的參考價值,感興趣的可以了解一下
    2023-08-08
  • SpringBoot的攔截器中依賴注入為null的解決方法

    SpringBoot的攔截器中依賴注入為null的解決方法

    這篇文章主要介紹了SpringBoot的攔截器中依賴注入為null的解決方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-06-06
  • Java構建乘積數(shù)組的方法

    Java構建乘積數(shù)組的方法

    這篇文章主要為大家詳細介紹了Java構建乘積數(shù)組的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-03-03
  • java9開始——接口中可以定義private私有方法

    java9開始——接口中可以定義private私有方法

    這篇文章主要介紹了從java9開始 接口中可以定義private私有方法,幫助大家更好的理解和學習Java,感興趣的朋友可以了解下
    2020-10-10
  • springboot配置logback日志管理過程詳解

    springboot配置logback日志管理過程詳解

    這篇文章主要介紹了springboot配置logback日志管理過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-09-09
  • jdk自帶定時器使用方法詳解

    jdk自帶定時器使用方法詳解

    這篇文章主要為大家詳細介紹了jdk自帶定時器的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • Spring5.2.x 源碼本地環(huán)境搭建的方法步驟

    Spring5.2.x 源碼本地環(huán)境搭建的方法步驟

    這篇文章主要介紹了Spring5.2.x 源碼本地環(huán)境搭建的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-09-09
  • Java中Agent的使用詳解

    Java中Agent的使用詳解

    Java Agent是一種特殊類型的軟件組件,它允許在Java虛擬機(JVM)運行時修改應用程序的字節(jié)碼,下面我們就來一起深入了解一下Agent的具體使用吧
    2023-12-12

最新評論