日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
呦呦,這些代碼有點(diǎn)臭,重構(gòu)大法帶你秀(SPI接口化)

本文轉(zhuǎn)載自微信公眾號(hào)「狼王編程」,作者狼王。轉(zhuǎn)載本文請(qǐng)聯(lián)系狼王編程公眾號(hào)。  

10余年的蒼溪網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。營(yíng)銷型網(wǎng)站的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整蒼溪建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無(wú)論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)建站從事“蒼溪網(wǎng)站設(shè)計(jì)”,“蒼溪網(wǎng)站推廣”以來(lái),每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。

如果說(shuō) 正常的重構(gòu)是為了消除代碼的壞味道, 那么高層次的重構(gòu)就是消除架構(gòu)的壞味道

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

先給大家看看最近通過(guò)使用SPI接口化,重構(gòu)的一個(gè)組件-分布式存儲(chǔ)。

重構(gòu)前的代碼結(jié)構(gòu)

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

但是當(dāng)某個(gè)新需求來(lái)的時(shí)候,比如我遇到的:需要支持多個(gè)云的多個(gè)賬號(hào)上傳下載功能,這個(gè)是因?yàn)樵诓煌脑粕?,不同賬號(hào)的權(quán)限,安全認(rèn)證等都是不太一樣的,所以在某一刻,這個(gè)需求就被提出來(lái)了,也就是你想上傳到哪個(gè)云的哪個(gè)賬號(hào)都可以。

然后拿到這個(gè)代碼,看了下這樣的架構(gòu),可能在這樣的基礎(chǔ)上完成需求也是沒(méi)有問(wèn)題的,但是擴(kuò)展很麻煩,而且代碼會(huì)越來(lái)越繁重,架構(gòu)會(huì)越來(lái)越復(fù)雜,不清晰。

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

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

好了,下面就讓我們開始講講重構(gòu)大法~

重構(gòu)

重構(gòu)是什么?

重構(gòu)(Refactoring)就是通過(guò)調(diào)整程序代碼改善軟件的質(zhì)量、性能,使其程序的設(shè)計(jì)模式和架構(gòu)更趨合理,提高軟件的擴(kuò)展性和維護(hù)性。

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

把優(yōu)化代碼質(zhì)量的過(guò)程拆解成一個(gè)個(gè)小的步驟,這樣重構(gòu)一個(gè)項(xiàng)目的巨大工作量就變成比如修改變量名、提取函數(shù)、抽取接口等等簡(jiǎn)單的工作目標(biāo)。

作為一個(gè)普通的程序員就可以通過(guò)實(shí)現(xiàn)這些易完成的工作目標(biāo)來(lái)提升自己的編碼能力,加深自己的項(xiàng)目認(rèn)識(shí),從而為最高層次的重構(gòu)打下基礎(chǔ)。

而且高層次的重構(gòu)依然是由無(wú)數(shù)個(gè)小目標(biāo)構(gòu)成,而不是長(zhǎng)時(shí)間、大規(guī)模地去實(shí)現(xiàn)。

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

重構(gòu)的重點(diǎn)

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

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

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

  • 盡量避免過(guò)多過(guò)長(zhǎng)的創(chuàng)建Java對(duì)象
  • 盡量使用局部變量
  • 盡量使用StringBuilder和StringBuffer進(jìn)行字符串連接
  • 盡量減少對(duì)變量的重復(fù)計(jì)算
  • 盡量在finally塊中釋放資源
  • 盡量緩存經(jīng)常使用的對(duì)象
  • 不使用的對(duì)象及時(shí)設(shè)置為null
  • 盡量考慮使用靜態(tài)方法
  • 盡量在合適的場(chǎng)合使用單例
  • 盡量使用final修飾符

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

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

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

SPI

什么是SPI?

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

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

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

下面就是SPI的機(jī)制過(guò)程

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

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

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

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

SPI使用介紹

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

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

SPI使用場(chǎng)景

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

以下是比較常見的例子:

數(shù)據(jù)庫(kù)驅(qū)動(dòng)加載接口實(shí)現(xiàn)類的加載 JDBC加載不同類型數(shù)據(jù)庫(kù)的驅(qū)動(dòng)

日志門面接口實(shí)現(xiàn)類加載 SLF4J加載不同提供商的日志實(shí)現(xiàn)類

Spring Spring中大量使用了SPI,比如:對(duì)servlet3.0規(guī)范對(duì)ServletContainerInitializer的實(shí)現(xiàn)、自動(dòng)類型轉(zhuǎn)換Type Conversion SPI(Converter SPI、Formatter SPI)等

Dubbo Dubbo中也大量使用SPI的方式實(shí)現(xiàn)框架的擴(kuò)展, 不過(guò)它對(duì)Java提供的原生SPI做了封裝,允許用戶擴(kuò)展實(shí)現(xiàn)Filter接口

SPI簡(jiǎn)單例子

先定義接口類

 
 
 
 
  1. package com.test.spi.learn; 
  2. import java.util.List; 
  3.  
  4. public interface Search { 
  5.     public List searchDoc(String keyword);    

文件搜索實(shí)現(xiàn)

 
 
 
 
  1. package com.test.spi.learn; 
  2. import java.util.List; 
  3.  
  4. public class FileSearch implements Search{ 
  5.     @Override 
  6.     public List searchDoc(String keyword) { 
  7.         System.out.println("文件搜索 "+keyword); 
  8.         return null; 
  9.     } 

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

 
 
 
 
  1. package com.test.spi.learn; 
  2. import java.util.List; 
  3.  
  4. public class DBSearch implements Search{ 
  5.     @Override 
  6.     public List searchDoc(String keyword) { 
  7.         System.out.println("數(shù)據(jù)庫(kù)搜索 "+keyword); 
  8.         return null; 
  9.     } 

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

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

 
 
 
 
  1. com.test.spi.learn.FileSearch 
  2. com.test.spi.learn.DBSearch 

然后寫一個(gè)測(cè)試方法

 
 
 
 
  1. package com.test.spi.learn; 
  2. import java.util.Iterator; 
  3. import java.util.ServiceLoader; 
  4.  
  5. public class TestCase { 
  6.     public static void main(String[] args) { 
  7.         ServiceLoader s = ServiceLoader.load(Search.class); 
  8.         Iterator iterator = s.iterator(); 
  9.         while (iterator.hasNext()) { 
  10.            Search search =  iterator.next(); 
  11.            search.searchDoc("hello world"); 
  12.         } 
  13.     } 

可以看到輸出結(jié)果:

 
 
 
 
  1. 文件搜索 hello world 
  2. 數(shù)據(jù)庫(kù)搜索 hello world 

SPI原理解析

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

應(yīng)用程序調(diào)用ServiceLoader.load方法 ServiceLoader.load方法內(nèi)先創(chuàng)建一個(gè)新的ServiceLoader,并實(shí)例化該類中的成員變量,包括以下:

loader(ClassLoader類型,類加載器) acc(AccessControlContext類型,訪問(wèn)控制器) providers(LinkedHashMap

應(yīng)用程序通過(guò)迭代器接口獲取對(duì)象實(shí)例 ServiceLoader先判斷成員變量providers對(duì)象中(LinkedHashMap

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

(1) 讀取META-INF/services/下的配置文件,獲得所有能被實(shí)例化的類的名稱,值得注意的是,ServiceLoader可以跨越j(luò)ar包獲取META-INF下的配置文件

(2) 通過(guò)反射方法Class.forName()加載類對(duì)象,并用instance()方法將類實(shí)例化。

(3) 把實(shí)例化后的類緩存到providers對(duì)象中,(LinkedHashMap

總結(jié)

優(yōu)點(diǎn)

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

缺點(diǎn)

不能按需加載。雖然ServiceLoader做了延遲載入,但是基本只能通過(guò)遍歷全部獲取,也就是接口的實(shí)現(xiàn)類得全部載入并實(shí)例化一遍。如果你并不想用某些實(shí)現(xiàn)類,或者某些類實(shí)例化很耗時(shí),它也被載入并實(shí)例化了,這就造成了浪費(fèi)。

獲取某個(gè)實(shí)現(xiàn)類的方式不夠靈活,只能通過(guò) Iterator 形式獲取,不能根據(jù)某個(gè)參數(shù)來(lái)獲取對(duì)應(yīng)的實(shí)現(xiàn)類。

多個(gè)并發(fā)多線程使用 ServiceLoader 類的實(shí)例是不安全的。

加載不到實(shí)現(xiàn)類時(shí)拋出并不是真正原因的異常,錯(cuò)誤很難定位。

看到上面這么多的缺點(diǎn),你肯定會(huì)想,有這些弊端為什么還要使用呢,沒(méi)錯(cuò),在重構(gòu)的過(guò)程中,SPI接口化是一個(gè)非常有用的方式,當(dāng)你需要擴(kuò)展的時(shí)候,適配的時(shí)候,越早的使用你就會(huì)受利越早,在一個(gè)合適的時(shí)間,恰當(dāng)?shù)臋C(jī)會(huì)的時(shí)候,就鼓起勇氣,重構(gòu)吧!

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


本文題目:呦呦,這些代碼有點(diǎn)臭,重構(gòu)大法帶你秀(SPI接口化)
本文地址:http://www.5511xx.com/article/cdigseh.html