DangerWind-RPC-framework---四、SPI

? ? ? ? SPI 即 Service Provider Interface ,可以理解為專門提供給服務提供者或者擴展框架功能的開發者去使用的一個接口。SPI 將服務接口和具體的服務實現分離開來,將服務調用方和服務實現者解耦,能夠提升程序的擴展性、可維護性。修改或者替換服務實現并不需要修改調用方。很多框架都使用了 Java 的 SPI 機制,比如:Spring 框架、數據庫加載驅動、日志接口、以及 Dubbo 的擴展實現等等。參考Dubbo的SPI機制,來實現本RPC框架的SPI部分。

? ? ? ? 舉個例子,client端在與server端進行通信時,需要對消息進行序列化。序列化時可以使用序列化算法有很多,包括Hessian、Kryo、ProtoStuff。系統的需求是根據消息中的序列化算法名稱來調用相關序列化算法對應的類中的方法來進行序列化與反序列化,加之為了便于擴展,需要使用SPI來進行解耦。

? ? ? ? SPI的使用方式如下:

Serializer serializer = ExtensionLoader.getExtensionLoader(Serializer.class).getExtension(codecName);

? ? ? ? codecName是序列化算法名稱,需要根據該名稱加載出對應的類。

    private final Class<?> type;private ExtensionLoader(Class<?> type) {this.type = type;}// 每個SPI接口都有自身的ExtensionLoaderpublic static <S> ExtensionLoader<S> getExtensionLoader(Class<S> type) {if (type == null) {throw new IllegalArgumentException("Extension type should not be null.");}if (!type.isInterface()) {throw new IllegalArgumentException("Extension type must be an interface.");}if (type.getAnnotation(SPI.class) == null) {throw new IllegalArgumentException("Extension type must be annotated by @SPI");}// firstly get from cache, if not hit, create oneExtensionLoader<S> extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);if (extensionLoader == null) {EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<S>(type));extensionLoader = (ExtensionLoader<S>) EXTENSION_LOADERS.get(type);}return extensionLoader;}

? ? ? ? 每個SPI接口都有自身的ExtensionLoader,調用getExtensionLoader時,首先會進行一系列的合法檢查操作,之后會嘗試獲取該接口的ExtensionLoader,先嘗試本地緩存CHM中獲取,獲取不到的話再創建Loader對象。

? ? ? ? 之后通過getExtension獲取實例,實例也進行了本地緩存,緩存中沒有的話再創建實例。

    private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();public T getExtension(String name) {if (StringUtil.isBlank(name)) {throw new IllegalArgumentException("Extension name should not be null or empty.");}// firstly get from cache, if not hit, create one// 緩存holderHolder<Object> holder = cachedInstances.get(name);if (holder == null) {cachedInstances.putIfAbsent(name, new Holder<>());holder = cachedInstances.get(name);}// create a singleton if no instance exists// holder為空,雙重檢查鎖創建示例Object instance = holder.get();if (instance == null) {synchronized (holder) {instance = holder.get();if (instance == null) {instance = createExtension(name);holder.set(instance);}}}return (T) instance;}

? ? ? ? ? 獲取到類的Class對象后可以通過反射的方式創建此對象。?

   // 緩存   private static final Map<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();private T createExtension(String name) {// load all extension classes of type T from file and get specific one by name// SPI接口對應的實現類,其標識名與class文件的映射,根據標識名獲取classClass<?> clazz = getExtensionClasses().get(name);if (clazz == null) {throw new RuntimeException("No such extension of name " + name);}T instance = (T) EXTENSION_INSTANCES.get(clazz);if (instance == null) {try {// 緩存中不存在,則創建實例EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());instance = (T) EXTENSION_INSTANCES.get(clazz);} catch (Exception e) {log.error(e.getMessage());}}return instance;}

? ? ? ? ?關鍵是獲取Class對象的過程?,即getExtensionCalsses方法:

    // 該SPI接口所有實現類的標識與其Class對象的緩存private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>(); private static final String SERVICE_DIRECTORY = "META-INF/extensions/";  private Map<String, Class<?>> getExtensionClasses() {// get the loaded extension class from the cache// 根據Interface實現類的類名獲取對應類的緩存Map<String, Class<?>> classes = cachedClasses.get();// double checkif (classes == null) {synchronized (cachedClasses) {classes = cachedClasses.get();if (classes == null) {classes = new HashMap<>();// load all extensions from our extensions directoryloadDirectory(classes);// 將Map集合存儲在Holder中進行緩存cachedClasses.set(classes);}}}return classes;}private void loadDirectory(Map<String, Class<?>> extensionClasses) {// 固定路徑下的文件,SPI接口的類名作為文件名,在此文件中規定需要加載的實現類String fileName = ExtensionLoader.SERVICE_DIRECTORY + type.getName();try {Enumeration<URL> urls;// 系統類加載器,它能夠加載用戶類路徑(ClassPath)上的類和資源。對于SPI機制尤為重要,因為SPI的實現類通常是由應用程序提供并放置在應用程序的類路徑下的ClassLoader classLoader = ExtensionLoader.class.getClassLoader();// 獲取當前類加載器加載的URL資源,文件名確定一般urls是唯一的urls = classLoader.getResources(fileName);if (urls != null) {while (urls.hasMoreElements()) {URL resourceUrl = urls.nextElement();// 使用classLoader加載資源,資源目標在resourceUrl下,加載后的class存儲在extensionClasses Map集合當中loadResource(extensionClasses, classLoader, resourceUrl);}}} catch (IOException e) {log.error(e.getMessage());}}private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, URL resourceUrl) {try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceUrl.openStream(), UTF_8))) {String line;// read every line// #是注釋,截取注釋之前的部分while ((line = reader.readLine()) != null) {// get index of commentfinal int ci = line.indexOf('#');if (ci >= 0) {// string after # is comment so we ignore itline = line.substring(0, ci);}line = line.trim();if (line.length() > 0) {try {final int ei = line.indexOf('=');// 標識與類名String name = line.substring(0, ei).trim();String clazzName = line.substring(ei + 1).trim();// our SPI use key-value pair so both of them must not be emptyif (name.length() > 0 && clazzName.length() > 0) {// 加載類Class<?> clazz = classLoader.loadClass(clazzName);// 在map中保存extensionClasses.put(name, clazz);}} catch (ClassNotFoundException e) {log.error(e.getMessage());}}}} catch (IOException e) {log.error(e.getMessage());}}
kyro=github.javaguide.serialize.kyro.KryoSerializer
protostuff=github.javaguide.serialize.protostuff.ProtostuffSerializer
hessian=github.javaguide.serialize.hessian.HessianSerializer

? ? ? ?逐層的方法調用,實現了加載META-INF/extensions/路徑下對應SPI配置文件從而加載Class對象并獲取實例的過程,重要部分可參考注釋。

? ? ? ?需要注意META-INF/extensions/下的文件名需要與代碼里一致,代碼里指定的文件名是SPI接口類的全類名。文件里的內容也需要按照(實現類標識=實現類全類名)來編寫,這樣才能與代碼一致,程序才可以正確解析文件,并使用類加載器加載對應的Class。最后按照<實現類標識,實現類Class對象>進行緩存。

? ? ? ?由于(類標識,Class對象)、(Class對象,對象實例)、(類標識,對象實例)這三個緩存的存在,后續可以直接傳入標識獲取到對應類的實例,也優化了RPC框架的性能。

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/44292.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/44292.shtml
英文地址,請注明出處:http://en.pswp.cn/web/44292.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

etcd 實現分布式鎖

10 基于 Etcd 的分布式鎖實現原理及方案

如何通過兔子和窩窩的故事理解“在機器人學習和研究中的獲得成本與維護成本”(節選)

獲得成本 掌握一門課程&#xff0c;以最為簡單的學校成績過60為例&#xff0c;需要按要求提交材料&#xff0c;包括作業、報告、實驗和考試等&#xff0c;依據學分和考核要求的不同&#xff0c;需要對于花費時間和經歷進行完成。 維護成本 考完了&#xff0c;如果被動學習那…

docker拉取鏡像-配置阿里云鏡像加速

1、配置阿里云鏡像&#xff08;用于拉取鏡像加速&#xff09; sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors": ["https://xxxxxxxx.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload sudo syst…

Docker 使用基礎(4)—存儲卷

&#x1f3ac;慕斯主頁&#xff1a;修仙—別有洞天 ??今日夜電波&#xff1a;秒針を噛む—ずっと真夜中でいいのに。 0:34━━━━━━?&#x1f49f;──────── 4:20 &#x1f504; ?? ? …

JVM堆內存的結構,YGC,FGC的原理

JVM堆內存結構&#xff1a; JVM堆內存可分為三個區域&#xff1a;新生代&#xff08;Young Generation&#xff09;、年老代&#xff08;Tenured Generation&#xff0c;也叫做Old Generation&#xff09;和永久代&#xff08;Permanent Generation&#xff0c;也叫做Method Ar…

linux 設置nginx開機自啟

1、關閉當前nginx運行 systemctl stop nginx 2、添加以下內容到nginx.service文件&#xff0c;注意nginx.pid文件的路徑&#xff0c;要替換哦&#xff01; vim /etc/systemd/system/nginx.service [Unit] DescriptionThe NGINX HTTP and reverse proxy server Afternetwork…

ArcGIS如何快速對齊兩個圖層

1、問題 如何讓兩個圖層快速對齊 2、使用捕捉工具 移動點或折點&#xff0c;使其與其他要素的折點、邊或端點精確重合。 可指定捕捉規則來控制是將輸入折點捕捉到指定距離范圍內的最近折點、邊還是端點。

MySQL數字相關數據處理函數

目錄 1. 隨機數生成 rand ( ) 2. 四舍五入 round&#xff08;&#xff09; 3. 舍去 truncate ( ) 4. 向上/下取整 5. 空處理 ifnull&#xff08; x , y &#xff09; 1. 隨機數生成 rand ( ) rand ( ) 生成 0 到 1 的隨機數&#xff1b; rand ( x ) 生成 0 到 1 的隨機數…

簡單理解Lua 協程(coroutine)

也許更好的閱讀體驗 協程簡單理解為可以暫停的線程&#xff0c;但是同一時刻只有一個協程可以處于運行狀態。 文章目錄 coroutine.create()coroutine.resume()coroutine.wrap()coroutine.yield()coroutine.resume()參數傳遞resume和yield之間互換數據 coroutine.create() lua…

403 禁止錯誤: 它是什么?如何修復?

您應該對403錯誤代碼很熟悉&#xff01;這種錯誤會導致流量損失&#xff0c;甚至錯失一些商業機會&#xff01; 什么&#xff1f;您在自己的網站上遇到了403錯誤&#xff1f;請立即修復它&#xff01;但是什么原因導致這種錯誤&#xff1f;該如何解決&#xff1f;這兩個問題都…

66種智能優化算法和改進優化算法優化BP神經網絡【開源代碼!】【文末福利IT學習資料】

前言 熟話說得好&#xff0c;創新點不夠&#xff0c;智能優化算法來湊&#xff0c;不要覺得羞恥&#xff0c;因為不僅我們這么干&#xff0c;很多外國人也這么干&#xff01;因為創新點實在太難想了&#xff0c;和優化算法結合下是最簡單的創新點了&#xff01; 之前給大家分享…

485通訊抗干擾,超時重發,不斷重連的程序架構

485通訊抗干擾,超時重發,不斷重連的編程思路 在工程中會遇到一種情況,當通信受到干擾之后,數據超時重發多次,無法被成功發出去,當恢復干擾后,之前發送的指令就被報錯清掉了,相當于串口掉線之后,即使短暫時間內通信連上,掉線之后發出的指令也不生效。 為了確保受到干…

OFDM符號周期

OFDM符號周期的確定 OFDM符號周期的確定是一個復雜的過程&#xff0c;需要考慮多個因素。以下是主要的考慮因素和確定步驟&#xff1a; 主要考慮因素 信道特性 多徑延遲擴展相干時間 系統要求 數據速率頻譜效率 硬件限制 采樣率計算復雜度 應用場景 移動性要求覆蓋范圍 …

spark shuffle寫操作——SortShuffleWriter

寫入的簡單流程&#xff1a; 1.生成ExternalSorter對象 2.將消息都是插入ExternalSorter對象中 3.獲取到mapOutputWriter&#xff0c;將中間產生的臨時文件合并到一個臨時文件 4.生成最后的data文件和index文件 可以看到寫入的重點類是ExternalSorter對象 ExternalSorter 基…

Vant Ui 最新訪問地址

Vant 4 - A lightweight, customizable Vue UI library for mobile web apps. 順帶一個頂部導航欄正常寫法 先使用吸頂為0&#xff0c;然后再寫nav-bar <van-sticky :offset-top"0"> <van-nav-bar class"top-title" title"村集體土地公示&q…

對為什么react需要時間分片,vue3不需要的淺學習

1、時間分片 時間分片指在讓應用在cpu進行大量計算時也能與用戶交互&#xff0c;但時間分片只能對大量cpu計算進行優化&#xff0c;無法優化復雜DOM操作&#xff0c;因為要確保用戶正在操作的界面是最新。 web卡頓的場景&#xff1a; 1、cpu計算量不大&#xff0c;但dom操作…

人工智能算法工程師(中級)課程1-Opencv視覺處理之基本操作與代碼詳解

大家好&#xff0c;我是微學AI&#xff0c;今天給大家介紹一下人工智能算法工程師(中級)課程1-Opencv視覺處理之基本操作與代碼詳解。OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一個開源的計算機視覺和機器學習軟件庫。它提供了各種視覺處理函數&am…

Redis為什么變慢了?一文講透如何排查Redis性能問題

Redis 作為優秀的內存數據庫&#xff0c;其擁有非常高的性能&#xff0c;單個實例的 OPS 能夠達到 10W 左右。但也正因此如此&#xff0c;當我們在使用 Redis 時&#xff0c;如果發現操作延遲變大的情況&#xff0c;就會與我們的預期不符。 你也許或多或少地&#xff0c;也遇到…

以太網中的各種幀結構

幀結構&#xff08;Ethernet Frame Structure&#xff09;介紹 以太網信號幀結構&#xff08;Ethernet Signal Frame Structure&#xff09;&#xff0c;有被稱為以太網幀結構&#xff0c;一般可以分為兩類 —— 數據幀和管理幀。 按照 IEEE 802.3&#xff0c;ISO/IEC8803-3 …

短視頻矩陣管理系統:如何提升內容質量,幫助企業獲客?

在數字化營銷蓬勃發展的今天&#xff0c;短視頻已成為企業推廣的重要陣地。然而&#xff0c;如何高效管理短視頻內容&#xff0c;提升內容質量&#xff0c;進而幫助企業精準獲客&#xff0c;成為企業亟待解決的問題。短視頻矩陣管理系統應運而生&#xff0c;以其強大的功能和靈…