<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          呦呦,這些代碼有點臭,重構大法帶你秀(SPI接口化),skr~

          共 5749字,需瀏覽 12分鐘

           ·

          2021-08-10 18:25

          如果說 正常的重構是為了消除代碼的壞味道, 那么高層次的重構就是消除架構的壞味道

          最近由于需要將公司基礎架構的組件進行各種兼容,適配以及二開,所以很多時候就需要對組件進行重構,大家是不是在拿到公司老項目老代碼,又需要二開或者重構的時候,會頭很大,無從下手,我之前也一直是這樣的狀態(tài),不過在慢慢熟悉了一些重構的思想和方法之后,就能稍微的得心應手一些,下面我就開始講下重構,然后會著重講下重構中的SPI接口化

          先給大家看看最近通過使用SPI接口化重構的一個組件-分布式存儲

          重構前的代碼結構

          好家伙,所有的第三方存儲都是寫在一個模塊中的,各種阿里云,騰訊云,華為云等等,這樣的代碼架構在前期可能在不需要經(jīng)常擴展,二開的時候,還是能用的。

          但是當某個新需求來的時候,比如我遇到的:需要支持多個云的多個賬號上傳下載功能,這個是因為在不同的云上,不同賬號的權限,安全認證等都是不太一樣的,所以在某一刻,這個需求就被提出來了,也就是你想上傳到哪個云的哪個賬號都可以。

          然后拿到這個代碼,看了下這樣的架構,可能在這樣的基礎上完成需求也是沒有問題的,但是擴展很麻煩,而且代碼會越來越繁重,架構會越來越復雜,不清晰

          所以我索性趁著這個機會,就重構一把,和其他同事也商量了下,決定分模塊,SPI化,好處就是根據(jù)你想使用的引入對應的依賴,讓代碼架構更加清晰,后續(xù)更加容易擴展了!下面就是重構后的大體架構:

          是不是清楚多了,之后哪怕某個云存儲需要增加新功能,或者需要兼容更多的云也是比較容易的了。

          好了,下面就讓我們開始講講重構大法~


          重構

          重構是什么?

          重構(Refactoring)就是通過調整程序代碼改善軟件的質量、性能,使其程序的設計模式和架構更趨合理,提高軟件的擴展性和維護性。

          重構最重要的思想就是讓普通程序員也能寫出優(yōu)秀的程序。

          把優(yōu)化代碼質量的過程拆解成一個個小的步驟,這樣重構一個項目的巨大工作量就變成比如修改變量名、提取函數(shù)、抽取接口等等簡單的工作目標。

          作為一個普通的程序員就可以通過實現(xiàn)這些易完成的工作目標來提升自己的編碼能力,加深自己的項目認識,從而為最高層次的重構打下基礎。

          而且高層次的重構依然是由無數(shù)個小目標構成,而不是長時間、大規(guī)模地去實現(xiàn)。

          重構本質是極限編程的一部分,完整地實現(xiàn)極限編程才能最大化地發(fā)揮重構的價值。而極限編程本身就提倡擁抱變化,增強適應性,因此分解極限編程中的功能去適應項目的需求、適應團隊的現(xiàn)狀才是最好的操作模式

          重構的重點

          重復代碼,過長函數(shù),過大的類,過長參數(shù)列,發(fā)散式變化,霰彈式修改,依戀情結,數(shù)據(jù)泥團,基本類型偏執(zhí),平行繼承體系,冗余類等

          下面舉一些常用的或者比較基礎的例子:

          一些基本的原則我覺得還是需要了解的

          1. 盡量避免過多過長的創(chuàng)建Java對象
          2. 盡量使用局部變量
          3. 盡量使用StringBuilder和StringBuffer進行字符串連接
          4. 盡量減少對變量的重復計算
          5. 盡量在finally塊中釋放資源
          6. 盡量緩存經(jīng)常使用的對象
          7. 不使用的對象及時設置為null
          8. 盡量考慮使用靜態(tài)方法
          9. 盡量在合適的場合使用單例
          10. 盡量使用final修飾符

          下面是關于類和方法優(yōu)化:

          1. 重復代碼的提取
          2. 冗長方法的分割
          3. 嵌套條件分支或者循環(huán)遞歸的優(yōu)化
          4. 提取類或繼承體系中的常量
          5. 提取繼承體系中重復的屬性與方法到父類

          這里先簡單介紹這些比較常規(guī)的重構思想和原則,方法,畢竟今天的主角是SPI,下面有請SPI登場!

          SPI

          什么是SPI?

          SPI全稱Service Provider Interface,是Java提供的一套用來被第三方實現(xiàn)或者擴展的API,它可以用來啟用框架擴展和替換組件。

          它是一種服務發(fā)現(xiàn)機制,它通過在ClassPath路徑下的META-INF/services文件夾查找文件,自動加載文件里所定義的類。

          這一機制為很多框架擴展提供了可能,比如在Dubbo、JDBC中都使用到了SPI機制

          下面就是SPI的機制過程

          SPI實際上是基于接口的編程+策略模式+配置文件組合實現(xiàn)的動態(tài)加載機制

          系統(tǒng)設計的各個抽象,往往有很多不同的實現(xiàn)方案,在面向的對象的設計里,一般推薦模塊之間基于接口編程,模塊之間不對實現(xiàn)類進行硬編碼。

          一旦代碼里涉及具體的實現(xiàn)類,就違反了可拔插的原則,如果需要替換一種實現(xiàn),就需要修改代碼。為了實現(xiàn)在模塊裝配的時候能不在程序里動態(tài)指明,這就需要一種服務發(fā)現(xiàn)機制。

          SPI就是提供這樣的一個機制:為某個接口尋找服務實現(xiàn)的機制。有點類似IOC的思想,就是將裝配的控制權移到程序之外,在模塊化設計中這個機制尤其重要。所以SPI的核心思想就是解耦。

          SPI使用介紹

          要使用Java SPI,一般需要遵循如下約定:

          1. 當服務提供者提供了接口的一種具體實現(xiàn)后,在jar包的META-INF/services目錄下創(chuàng)建一個以接口全限定名`為命名的文件,內容為實現(xiàn)類的全限定名;
          2. 接口實現(xiàn)類所在的jar包放在主程序的classpath中;
          3. 主程序通過java.util.ServiceLoder動態(tài)裝載實現(xiàn)模塊,它通過掃描META-INF/services目錄下的配置文件找到實現(xiàn)類的全限定名,把類加載到JVM;
          4. SPI的實現(xiàn)類必須攜帶一個不帶參數(shù)的構造方法;

          SPI使用場景

          概括地說,適用于:調用者根據(jù)實際使用需要,啟用、擴展、或者替換框架的實現(xiàn)策略

          以下是比較常見的例子:

          1. 數(shù)據(jù)庫驅動加載接口實現(xiàn)類的加載 JDBC加載不同類型數(shù)據(jù)庫的驅動
          2. 日志門面接口實現(xiàn)類加載 SLF4J加載不同提供商的日志實現(xiàn)類
          3. Spring Spring中大量使用了SPI,比如:對servlet3.0規(guī)范對ServletContainerInitializer的實現(xiàn)、自動類型轉換Type Conversion SPI(Converter SPI、Formatter SPI)等
          4. Dubbo Dubbo中也大量使用SPI的方式實現(xiàn)框架的擴展, 不過它對Java提供的原生SPI做了封裝,允許用戶擴展實現(xiàn)Filter接口

          SPI簡單例子

          先定義接口類

          package com.test.spi.learn;
          import java.util.List;

          public interface Search {
              public List<String> searchDoc(String keyword);   
          }

          文件搜索實現(xiàn)

          package com.test.spi.learn;
          import java.util.List;

          public class FileSearch implements Search{
              @Override
              public List<String> searchDoc(String keyword) {
                  System.out.println("文件搜索 "+keyword);
                  return null;
              }
          }

          數(shù)據(jù)庫搜索實現(xiàn)

          package com.test.spi.learn;
          import java.util.List;

          public class DBSearch implements Search{
              @Override
              public List<String> searchDoc(String keyword) {
                  System.out.println("數(shù)據(jù)庫搜索 "+keyword);
                  return null;
              }
          }

          接下來可以在resources下新建META-INF/services/目錄,然后新建接口全限定名的文件:com.test.spi.learn.Search

          里面加上我們需要用到的實現(xiàn)類

          com.test.spi.learn.FileSearch
          com.test.spi.learn.DBSearch

          然后寫一個測試方法

          package com.test.spi.learn;
          import java.util.Iterator;
          import java.util.ServiceLoader;

          public class TestCase {
              public static void main(String[] args) {
                  ServiceLoader<Search> s = ServiceLoader.load(Search.class);
                  Iterator<Search> iterator = s.iterator();
                  while (iterator.hasNext()) {
                     Search search =  iterator.next();
                     search.searchDoc("hello world");
                  }
              }
          }

          可以看到輸出結果:

          文件搜索 hello world
          數(shù)據(jù)庫搜索 hello world

          SPI原理解析

          通過查看ServiceLoader的源碼,梳理了一下,實現(xiàn)的流程如下:

          1. 應用程序調用ServiceLoader.load方法 ServiceLoader.load方法內先創(chuàng)建一個新的ServiceLoader,并實例化該類中的成員變量,包括以下:

          loader(ClassLoader類型,類加載器) acc(AccessControlContext類型,訪問控制器) providers(LinkedHashMap<String,S>類型,用于緩存加載成功的類) lookupIterator(實現(xiàn)迭代器功能)

          1. 應用程序通過迭代器接口獲取對象實例 ServiceLoader先判斷成員變量providers對象中(LinkedHashMap<String,S>類型)是否有緩存實例對象,

          如果有緩存,直接返回。如果沒有緩存,執(zhí)行類的裝載,實現(xiàn)如下:

          (1) 讀取META-INF/services/下的配置文件,獲得所有能被實例化的類的名稱,值得注意的是,ServiceLoader可以跨越jar包獲取META-INF下的配置文件
          (2) 通過反射方法Class.forName()加載類對象,并用instance()方法將類實例化。
          (3) 把實例化后的類緩存到providers對象中,(LinkedHashMap<String,S>類型) 然后返回實例對象。

          總結

          優(yōu)點

          使用SPI機制的優(yōu)勢是實現(xiàn)解耦,使得接口的定義與具體業(yè)務實現(xiàn)分離,而不是耦合在一起。應用進程可以根據(jù)實際業(yè)務情況啟用或替換具體組件。

          缺點

          1. 不能按需加載。雖然ServiceLoader做了延遲載入,但是基本只能通過遍歷全部獲取,也就是接口的實現(xiàn)類得全部載入并實例化一遍。如果你并不想用某些實現(xiàn)類,或者某些類實例化很耗時,它也被載入并實例化了,這就造成了浪費。
          2. 獲取某個實現(xiàn)類的方式不夠靈活,只能通過 Iterator 形式獲取,不能根據(jù)某個參數(shù)來獲取對應的實現(xiàn)類。
          3. 多個并發(fā)多線程使用 ServiceLoader 類的實例是不安全的。
          4. 加載不到實現(xiàn)類時拋出并不是真正原因的異常,錯誤很難定位。

          看到上面這么多的缺點,你肯定會想,有這些弊端為什么還要使用呢,沒錯,在重構的過程中,SPI接口化是一個非常有用的方式,當你需要擴展的時候,適配的時候,越早的使用你就會受利越早,在一個合適的時間,恰當?shù)臋C會的時候,就鼓起勇氣,重構吧!


          好了。今天就說到這了,我還會不斷分享自己的所學所想,希望我們一起走在成功的道路上!

          瀏覽 49
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  香蕉伊人综合 | 伊大香蕉| 国产三级三级在线观看 | 成人网站男人的天堂 | 婷婷高清无码 |