日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java SPI 源码解析及 demo 讲解

發布時間:2025/3/20 java 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java SPI 源码解析及 demo 讲解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

??點擊上方?好好學java?,選擇?星標?公眾號

重磅資訊、干貨,第一時間送達 今日推薦:Java實現QQ登錄和微博登錄個人原創+1博客:點擊前往,查看更多 作者:JlDang 來源:https://segmentfault.com/a/1190000022101812

1. SPI是什么,有什么用處

SPI全稱S而vice Provider Interface,是java提供的一套用來被第三方實現或者擴展的API,它可以用來啟用框架擴展和替換組件。

系統設計的各個抽象,一般有很多不同的實現方案,比如通過不同類型的配置文件加載配置信息,通過不同的序列化方案實現序列化。一般推薦模塊之間基于接口編程,模塊之間不對實現類進行硬編碼。一旦代碼里涉及具體的實現類,就違反了可插拔的原則。為了實現在模塊裝配的時候能不在程序里動態指明,這就需要一種服務發現機制。

SPI的核心思想就是解耦

2.使用場景

調用者根據實際需要替換框架的實現策略。

比如常見的例子:

  • 數據庫驅動加載類接口實現類的加載,JDBC加載不同類型數據庫的驅動

  • 日志實現類加載

  • dubbo中也大量使用SPI的方式實現框架的擴展,不過它對java提供的SPI進行了封裝

3.如何使用

實例代碼

  • 定義一組接口,并寫出多個實現類

  • package com.djl.test.spi.api;/*** @ClassName Robot* @Description TODO* @Author djl* @Date 2020-03-18 19:01* @Version 1.0*/ public interface Robot {void sayHello(); } package com.djl.test.spi.java;import com.djl.test.spi.api.Robot;/*** @ClassName Bumblebee* @Description TODO* @Author djl* @Date 2020-03-18 19:03* @Version 1.0*/ public class Bumblebee implements Robot {@Overridepublic void sayHello() {System.out.println("Hello ,I am Bumble bee");} } package com.djl.test.spi.java;import com.djl.test.spi.api.Robot;/*** @ClassName OptimusPrime* @Description TODO* @Author djl* @Date 2020-03-18 19:02* @Version 1.0*/ public class OptimusPrime implements Robot {@Overridepublic void sayHello() {System.out.println("Hello,I am Optimus prime");} }
  • 在META-INF/services文件下,創建一個以接口全限定名命名的文件,內容為實現類的全限定名

  • image.pngcom.djl.test.spi.java.Bumblebee com.djl.test.spi.java.OptimusPrime
  • 通過ServiceLoader類進行加載

  • import com.djl.test.spi.api.Robot; import com.djl.test.spi.api.RobotDubbo; import org.apache.dubbo.common.extension.ExtensionLoader; import org.junit.Test;import java.util.ServiceLoader;/*** @ClassName JavaSPITest* @Description TODO* @Author djl* @Date 2020-03-18 19:06* @Version 1.0*/ public class JavaSPITest {@Testpublic void sayHello(){ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);System.out.println("java spi");serviceLoader.forEach(Robot::sayHello);} }
  • 執行結果

  • image.png

    4 源碼解析

    我會將分析都寫在代碼注釋中,大家可以打開自己的源碼耐心的看一會。

    接下來是重頭戲了,知道了spi怎么用,那么內部是如何實現的呢?

    我們直接從ServiceLoader類的load方法看起。

    /**為給定的服務類型創建一個新的服務加載程序,使用 *當前線程的{@linkplain java.lang.Thread#getContextClassLoader *上下文類裝入器}。 */ public static <S> ServiceLoader<S> load(Class<S> service) {//1.獲取當前線程的類加載ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl); }</code><code class="java">public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) {//new一個serviceloader對象return new ServiceLoader<>(service, loader); }</code><code class="java">//構造函數 private ServiceLoader(Class<S> svc, ClassLoader cl) {//判斷入參是否為nullservice = Objects.requireNonNull(svc, "Service interface cannot be null");//2.加載器如果不存在,獲取系統類加載器,通常是applicationLoader,應用程序加載器loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;//3.獲取訪問控制器acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;reload(); }</code><code class="java">public void reload() {// 清空緩存providers.clear();// 初始化內部類,用于遍歷提供者lookupIterator = new LazyIterator(service, loader); }

    看到這里,相信大家對于初始化的內容有了一定了解,這里面涉及到了一些屬性,我們來總結下

    private static final String PREFIX = "META-INF/services/";// 要加載的類 private final Class<S> service;// 用于加載實現類的類加載器 private final ClassLoader loader;// 訪問控制器 private final AccessControlContext acc;// 提供者的緩存 private LinkedHashMap<String,S> providers = new LinkedHashMap<>();// 一個內部類,用于遍歷實現類 private LazyIterator lookupIterator;

    現在我們發現重點就在于LazyIterator這個內部類上,我們獲取實現類都看這個內部類了,我們繼續來分析

    private class LazyIteratorimplements Iterator<S> {Class<S> service;ClassLoader loader;Enumeration<URL> configs = null;Iterator<String> pending = null;String nextName = null;//構造函數private LazyIterator(Class<S> service, ClassLoader loader) {this.service = service;this.loader = loader;}private boolean hasNextService() {if (nextName != null) {return true;}if (configs == null) {try {//獲取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;}</code><code class="java">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;//讀取文件內容,這里不多說了,正常的流操作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);}}return names.iterator(); }</code><code class="java">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 } image.png//這里是iterable循環遍歷 default void forEach(Consumer<? super T> action) {Objects.requireNonNull(action);for (T t : this) {action.accept(t);} }

    到這里整個鏈路就分析完成了。

    感興趣的小伙伴可以按照demo,自己跑一遍,有問題歡迎提問。

    總結

    以上是生活随笔為你收集整理的Java SPI 源码解析及 demo 讲解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。