一、dubbo的架構思路
1.1 dubbo框架設計
dubbo官網的架構設計提供了一張整體的框架圖,10個層級看起來挺嚇人的。但是其核心總結起來就是:Microkernel + Plugin(微內核+插件)。
微內核+插件機制
官網介紹的架構設計思想是兩點:
- 採用 URL 作爲配置信息的統一格式,所有擴展點都通過傳遞 URL 攜帶配置信息;
- 採用 Microkernel + Plugin 模式,Microkernel 只負責組裝 Plugin,Dubbo 自身的功能也是通過擴展點實現的,也就是 Dubbo 的所有功能點都可被用戶自定義擴展所替換。
對於第一點比較容易理解,因爲是分佈式環境,各系統之間的參數傳遞基於URL來攜帶配置信息,所有的參數都封裝成 Dubbo 自定義的 URL 對象進行傳遞。URL 對象主要包括以下屬性:
String protocol
String host
int port
String path
Map<String, String> parameters
第二點:系統裏抽象的各個模塊,往往有很多不同的實現方案,好的設計來說:模塊之間基於接口編程,模塊之間不對實現類進行硬編碼。一旦代碼裏涉及具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改代碼,例如:
if(參數=="dubbo"){
return new DubboProtocol(); }
else if(參數 == "rmi"){
return new RMIProtocol();
}
SPI的解決方案就呼之欲出了,一個接口對應有多個實現類的時候該怎樣指定麼?如果採用上述設計是很糟糕的,用if else
來寫死自己的服務發現,如果新增一種協議則還需要去修改代碼,針對此類問題Java本身提供了spi機制,可以做到服務發現和動態擴展,但是弊端就是一初始化就把所有實現類給加載進去,dubbo改進了spi並重新命名爲ExtensionLoader(擴展點機制),按照用戶配置來指定加載模塊,只需要約定一下路徑即可:
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";
這部分源碼可以考察知識點非常多,對使用者來說是透明的,而精華卻良多,尤其結合java-spi,jvm以及spring等多方面對比、借鑑,因此理論上可以好好掌握,當然最好的學習方式就是按照極簡的思路來實現一個簡版RPC工具。
1.2dubbo原理、與Spring融合
dubbo是一個分佈式服務框架,致力於提供高性能和透明化的RPC遠程服務調用方案,以及SOA服務治理方案。既然是分佈式那就意味着:一個業務分拆多個子業務,部署在不同的服務器上,既然各服務是部署在不同的服務器上,那服務間的調用就是要通過網絡通信。既然涉及到了網絡通信,那麼服務消費者調用服務之前,都要寫各種網絡請求,編解碼之類的相關代碼,明顯是很不友好的.dubbo所說的透明,就是指,讓調用者對網絡請求,編解碼之類的細節透明,讓我們像調用本地服務一樣調用遠程服務,甚至感覺不到自己在調用遠程服務。
public class ProxyFactory implements InvocationHandler {
private Class interfaceClass;
public ProxyFactory(Class interfaceClass) {
this.interfaceClass = interfaceClass;
}
//返回代理對象,此處用泛型爲了調用時不用強轉,用Object需要強轉
public <T> T getProxyObject(){
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),//類加載器
new Class[]{interfaceClass},//爲哪些接口做代理(攔截哪些方法)
this);//(把這些方法攔截到哪處理)
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
System.out.println("進行編碼");
System.out.println("發送網絡請求");
System.out.println("將網絡請求結果進行解碼並返回");
return null;
}
}
項目引入dubbo的方法推薦用XML配置的方式引入,即便是老項目拆分改造,只要是Spring工程,這個都是比較好做的,可以想象自己如果開發一箇中間件服務,如果把服務嵌入spring容器當中呢?作爲高級開發人員這個也是一個進階的既能項。XML 配置方式是基於 Spring 的 Schema 和 XML 擴展機制實現的。通過該機制,我們可以編寫自己的 Schema,並根據自定義的 Schema 自定義標籤來配置 Bean。
使用 Spring 的 XML 擴展機制有以下幾個步驟:
- 定義 Schema(編寫 .xsd 文件)
- 定義 JavaBean
- 編寫 NamespaceHandler 和 BeanDefinitionParser 完成 Schema 解析
- 編寫 spring.handlers 和 spring.schemas 文件串聯解析部件
- 在 XML 文件中應用配置
最好的學習效果是可以自己按照模板來一樣畫瓢來創作一個類似的xml配置。可參考《dubbo源碼解析-簡單原理、與spring融合》
1.3 服務發佈
服務的發佈總共做了以下幾件事,這個也可以從日誌log上看出來:
- 暴露本地服務
- 暴露遠程服務
- 啓動netty
- 連接zookeeper
- 到zookeeper註冊
- 監聽zookeeper
貼出一張官方文檔的服務發佈圖
服務發佈
首先
ServiceConfig
類拿到對外提供服務的實際類 ref(如:HelloWorldImpl
),然後通過ProxyFactory
類的getInvoker
方法使用 ref 生成一個AbstractProxyInvoker
實例,到這一步就完成具體服務到Invoker
的轉化。接下來就是Invoker
轉換到Exporter
的過程。Dubbo 處理服務暴露的關鍵就在Invoker
轉換到Exporter
的過程,上圖中的紅色部分。
Dubbo 的實現
Dubbo 協議的Invoker
轉爲Exporter
發生在DubboProtocol
類的export
方法,它主要是打開socket
偵聽服務,並接收客戶端發來的各種請求,通訊細節由 Dubbo 自己實現。
上面摘抄了官方文檔(具體鏈接請戳),可能還是有點抽象,實際上從代碼層面進行分析:
此處就是將本地的需要暴漏的方法以url形式作爲參數傳入 exportLocal()
方法,url之前已經提到過包含了ip地址、端口、接口以及配置信息等。
關鍵步驟1-本地暴露
這時會執行到一個接口方法getInvoker()
,這是一個註解了@Adaptive的方法,該方法的具體實現類是運行中生成動態編譯的Adaptive類,把java編譯出來的動態類貼出來debug如下,恍然大悟,原來他就是幾個if判斷,來告訴程序我這個url參數配置的是哪種協議,我現在就動態的去調用這個擴展點服務(dubbo-spi),動態編譯的好處就是不用將代碼寫死,在協議會擴展的情況下,我根據你配置的協議來動態的生成我的extensionLoader,再來加載我所需要的Invoker。
關鍵步驟2-getInvoker()方法
上圖引用的是本地服務的暴露執行,若是遠程服務的暴露,arg2參數的開頭則會是registry://192.168.0.1:2181/com.alibaba.dubbo.** / **。從exporter對象裏包含的invoker屬性可以看出,invoker包含的攜帶ip、端口、接口以及配置信息的url。
關鍵步驟3-invoker信息
現在開始進入到遠程服務暴露的過程,一般來說這部分是應用和考察最多的點,通過配置的協議將服務暴露給外部調用。dubbo所支持的協議有多重,默認推薦dubbo協議,於是在動態代理的時候會生成Protocol$Adpative代理類,該代理類實現了RPC 協議接口,再通過擴展機制將服務加載進來。
關鍵步驟4-Protocol$Adpative代理類
加載了實現類後方法會順着調用鏈路進入到dubbo協議中的export()方法中來,可以再DubboProtocol類中設置斷點觀察方法執行,此處完成了一個綁定,將暴露的接口+DubboExporter進行關聯放入map中緩存。
關鍵步驟5-DubboProtocol
後面的步驟不再一一展開來講,越來越貼近底層和網絡通信,我們在調用dubbo接口的時候dubbo都爲了我們做了這樣的工作,但是對開發人員來說都是透明無感知的:
- exchange 信息交換層。封裝請求響應模式,同步轉異步,以 Request, Response 爲中心。
- transport 網絡傳輸層:抽象 mina 和 netty 爲統一接口,以 Message 爲中心。
- serialize 數據序列化層:可複用的一些工具,擴展接口爲 Serialization, ObjectInput, ObjectOutput, ThreadPool
這裏引用一張肥朝博客的總結圖,來總結服務暴露所幹的事情:
首先是通過動態代理店的方式將暴露的接口組裝成url形式的invoker,然後再根據url的配置信息來指定傳輸協議、交換方式、序列化方式等等,由於dubbo採用了自定義的SPI擴展,各層之間都是相互獨立的,只有在調用的時候才知道所調用的具體擴展實現,這裏還是以jdk或者javasisit的方式來動態代理實現。
服務暴露流程
1.4 服務引用
首先
ReferenceConfig
類的init
方法調用 Protocol 的refer
方法生成 Invoker 實例(如上圖中的紅色部分),這是服務消費的關鍵。接下來把 Invoker 轉換爲客戶端需要的接口(如:HelloWorld)。關於每種協議如RMI/Dubbo/Web service
等它們在調用refer
方法生成Invoker
實例的細節和上一章節所描述的類似。
服務應用流程
上述圖和文字是摘自官方文檔的原話(地址在這裏),總結來說就是幹了兩件事情:1、將spring的schemas標籤信息轉換bean,然後通過這個bean的信息,連接、訂閱zookeeper節點信息創建一個invoker。2、將invoker的信息創建一個動態代理對象。貼一張服務應用的時序圖:
服務引用時序
這裏又一次出現了Invoker
,這個抽象的概念真是無處不在呀,dubbo中最重要的兩種 Invoker
:服務提供 Invoker
和服務消費 Invoker
。Invoker從類的設計信息上是封裝了 Provider和Consumer地址及 Service 接口信息,我們在自己的子系統調用遠程接口的時候,會像調用自己的方法一樣,比如在消費端這裏用註解@Autowirted
自動注入一個遠程接口進來,這個遠程接口就是上圖中服務消費端的 proxy
,但是遠程接口是需要網絡通信、編碼解碼等等一系列工作的,要封裝這個通信細節,讓用戶像以本地調用方式調用遠程服務,就必須使用代理,然後說到動態代理,用戶代碼通過這個 proxy
調用其對應的 Invoker
,而該 Invoker
實現了真正的遠程服務調用。
image.png
二、Dubbo實戰應用
實戰應用主要是從應用層面講引入dubbo框架後如何做一些關鍵配置
2.1 Dubbo 支持四種配置方式:
XML 配置:基於 Spring 的 Schema 和 XML 擴展機制實現(推薦)
屬性配置:加載 classpath 根目錄下的 dubbo.properties
API 配置:通過硬編碼方式配置(不推薦使用,可學習加深源碼理解)
註解配置:通過註解方式配置(Dubbo-2.5.7及以上版本支持,不推薦使用)
2.2 集羣容錯
在集羣調用失敗時,Dubbo 提供了多種容錯方案,缺省爲 failover 重試。
集羣容錯
- Invoker 是 Provider 的一個可調用 Service 的抽象,Invoker 封裝了 Provider 地址及 Service 接口信息
- Directory 代表多個 Invoker,可以把它看成 List<Invoker> ,但與 List 不同的是,它的值可能是動態變化的,比如註冊中心推送變更
- Cluster 將 Directory 中的多個 Invoker 僞裝成一個 Invoker,對上層透明,僞裝過程包含了容錯邏輯,調用失敗後,重試另一個
- Router 負責從多個 Invoker 中按路由規則選出子集,比如讀寫分離,應用隔離等
- LoadBalance 負責從多個 Invoker 中選出具體的一個用於本次調用,選的過程包含了負載均衡算法,調用失敗後,需要重選。
集羣調用的配置可從如下列表中選擇:
<dubbo:service cluster="failsafe" />
<!-- 或者 -->
<dubbo:reference cluster="failsafe" />
集羣模式 | 說明 |
---|---|
Failfast Cluster | 快速失敗,只發起一次調用,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。 |
Failsafe Cluster | 失敗安全,出現異常時,直接忽略。通常用於寫入審計日誌等操作。 |
Failback Cluster | 失敗自動恢復,後臺記錄失敗請求,定時重發。通常用於消息通知操作。 |
Forking Cluster | 並行調用多個服務器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可通過 forks="2" 來設置最大並行數。 |
Broadcast Cluster | 廣播調用所有提供者,逐個調用,任意一臺報錯則報錯 [2]。通常用於通知所有提供者更新緩存或日誌等本地資源信息。 |
2.3 負載均衡
Random LoadBalance
- 隨機,按權重設置隨機概率。
- 在一個截面上碰撞的概率高,但調用量越大分佈越均勻,而且按概率使用權重後也比較均勻,有利於動態調整提供者權重。
RoundRobin LoadBalance
- 輪詢,按公約後的權重設置輪詢比率。
- 存在慢的提供者累積請求的問題,比如:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,久而久之,所有請求都卡在調到第二臺上。
LeastActive LoadBalance
- 最少活躍調用數,相同活躍數的隨機,活躍數指調用前後計數差。
- 使慢的提供者收到更少請求,因爲越慢的提供者的調用前後計數差會越大。
ConsistentHash LoadBalance
- 一致性 Hash,相同參數的請求總是發到同一提供者。
- 當某一臺提供者掛時,原本發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引起劇烈變動。
- 算法參見:http://en.wikipedia.org/wiki/Consistent_hashing
- 缺省只對第一個參數 Hash,如果要修改,請配置
<dubbo:parameter key="hash.arguments" value="0,1" />
- 缺省用 160 份虛擬節點,如果要修改,請配置
<dubbo:parameter key="hash.nodes" value="320" />
<!--服務端服務級別-->
<dubbo:service interface="..." loadbalance="roundrobin" />
<!--客戶端服務級別-->
<dubbo:reference interface="..." loadbalance="roundrobin" />
<!--服務端方法級別-->
<dubbo:service interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>
<!--客戶端方法級別-->
<dubbo:reference interface="...">
<dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>
三、dubbo面經
SPI
1、你是否瞭解SPI,講一講什麼是SPI,爲什麼要使用SPI?
SPI具體約定:當服務的提供者,提供了服務接口的一種實現之後,在jar包的META-INF/services/
目錄裏同時創建一個以服務接口命名的文件。該文件裏就是實現該服務接口的具體實現類。而當外部程序裝配這個模塊的時候,就能通過該jar包META-INF/services/
裏的配置文件找到具體的實現類名,並裝載實例化,完成模塊的注入(從使用層面來說,就是運行時,動態給接口添加實現類)。 基於這樣一個約定就能很好的找到服務接口的實現類,而不需要再代碼裏制定(不需要在代碼裏寫死)。
這樣做的好處:java設計出SPI目的是爲了實現在模塊裝配的時候能不在程序裏動態指明,這就需要一種服務發現機制。這樣程序運行的時候,該機制就會爲某個接口尋找服務的實現,有點類似IOC的思想,就是將裝配的控制權移到程序之外,在模塊化設計中這個機制尤其重要。例如,JDBC驅動,可以加載MySQL、Oracle、或者SQL Server等,目前有不少框架用它來做服務的擴張發現。回答這個問題可以延伸一下和API的對比,API是將方法封裝起來給調用者使用的,SPI是給擴展者使用的。
2、對類加載機制瞭解嗎,說一下什麼是雙親委託模式,他有什麼弊端,這個弊端有沒有什麼我們熟悉的案例,解決這個弊端的原理又是怎麼樣的?
擴展延生的一道題。
3、Dubbo的SPI和JDK的SPI有區別嗎?有的話,究竟有什麼區別?
Dubbo 的擴展點加載是基於JDK 標準的 SPI 擴展點發現機制增強而來的,Dubbo 改進了 JDK 標準的 SPI 的以下問題:
- JDK 標準的 SPI 會一次性實例化擴展點所有實現,如果有擴展實現初始化很耗時,但如果沒用上也加載,會很浪費資源。
- 增加了對擴展點 IoC 和 AOP 的支持,一個擴展點可以直接 setter 注入其它擴展點。
上文已提供。另外在博客中也單獨對此寫了一篇《Dubbo內核之SPI機制》、《跟我學Dubbo系列之Java SPI機制簡介》
4、Dubbo中SPI也增加了IoC,先講講Spring的IoC,然後再講講Dubbo裏面又是怎麼做的
5、Dubbo中SPI也增加了AOP,那你講講這用到了什麼設計模式,Dubbo又是如何做的.
Dubbo原理
1、Dubbo角色和設計是怎麼樣的,原理是怎麼樣的?請簡單談談?
Dubbo角色和設計
2、有沒有考慮過自己實現一個類似dubbo的RPC框架,如果有,請問你會如果着手實現?(面試高頻題,區分度高)
可從兩個方面去入手,考慮接口擴展性,改造JDK的SPI機制來實現自己的擴展SPI機制。另外就是從動態代理入手,從網絡通信、編碼解碼這些步驟以動態代理的方式植入遠程調用方法中,實現透明化的調用。
3、用過mybatis是否知道Mapper接口的原理嗎?(如果回答得不錯,並且提到動態代理這個關鍵詞會繼續往下問,那這個動態代理又是如何通過依賴注入到Mapper接口的呢?)
4、服務發佈過程中做了哪些事?
暴露本地服務、暴露遠程服務、啓動netty、連接zookeeper、到zookeeper註冊、監聽zookeeper
5、dubbo都有哪些協議,他們之間有什麼特點,缺省值是什麼?
dubbo支持多種協議,默認使用的是dubbo
協議,具體介紹官方文檔寫得很清楚,傳送地址:相關協議介紹,重點是掌握好推薦dubbo協議。Dubbo 缺省協議採用單一長連接和 NIO 異步通訊,適合於小數據量大併發的服務調用,以及服務消費者機器數遠大於服務提供者機器數的情況。
6、什麼是本地暴露和遠程暴露,他們的區別?
在dubbo中我們一個服務可能既是Provider,又是Consumer,因此就存在他自己調用自己服務的情況,如果再通過網絡去訪問,那自然是捨近求遠,因此他是有本地暴露服務的這個設計.從這裏我們就知道這個兩者的區別
- 本地暴露是暴露在JVM中,不需要網絡通信.
- 遠程暴露是將ip,端口等信息暴露給遠程客戶端,調用時需要網絡通信.
7、服務暴露中遠程暴露的總體過程,畫圖和文字方式說明
詳見上述說明
zookeeper
1、一般選擇什麼註冊中心,還有別的選擇嗎?
zk爲默認推薦,其餘還有Multicast、redis、Simple等註冊中心。
2、dubbo中zookeeper做註冊中心,如果註冊中心集羣都掛掉,那發佈者和訂閱者還能通信嗎?(面試高頻題)
zookeeper的信息會緩存到服務器本地作爲一個cache緩存文件,並且轉換成properties對象方便使用,每次調用時,按照本地存儲的地址進行調用,但是無法從註冊中心去同步最新的服務列表,短期的註冊中心掛掉是不要緊的,但一定要儘快修復。所以掛掉是不要緊的,但前提是你沒有增加新的服務,如果你要調用新的服務,則是不能辦到的。
3、項目中有使用過多線程嗎?有的話講講你在哪裏用到了多線程?(面試高頻題)
以dubbo爲例,這裏的做法是:建立線程池,定時的檢測並連接註冊中心,如果失敗了就重連,其實也就是一個定時任務執行器。可能做了兩三年java還沒真正在項目中開啓過線程,問到這個問題時菊花一緊,但是定時任務執行器這種需求在項目中還是很常見的,比如失敗重連、輪詢執行任務等等,可以參考這個例子,把你們的定時任務場景和這裏的多線程用法套在一起。
dubbo檢測zk鏈接
4、zookeeper的java客戶端你使用過哪些?
zookeeper是支持ZkClient和Curator兩種,關於zk的使用場景,除了以dubbo作爲註冊中心以外,zk在分佈式環境作爲協調服務器有許多應用場景,可以嘗試用java來調用zk服務做一些協調服務,如負載均衡、數據訂閱與發佈等等。SnailClimb寫了一篇優秀的博客《可能是全網把ZK概念講的最清楚的一篇文章》
zookeeper知識點一覽圖
5、服務提供者能實現失效踢出是什麼原理(高頻題)
在分佈式系統中,我們常常需要知道某個機器是否可用,傳統的開發中,可以通過Ping某個主機來實現,Ping得通說明對方是可用的,相反是不可用的,ZK 中我們讓所有的機器都註冊一個臨時節點,我們判斷一個機器是否可用,我們只需要判斷這個節點在ZK中是否存在就可以了,不需要直接去連接需要檢查的機器,降低系統的複雜度。
6、zookeeper的有哪些節點,他們有什麼區別?講一下應用場景
zookeeper中節點是有生命週期的.具體的生命週期取決於節點的類型.節點主要分爲持久節點(Persistent)和臨時節點(Ephemeral),但是更詳細的話還可以加上時序節點(Sequential),創建節點中往往組合使用,因此也就是4種:持久節點、持久順序節點、臨時節點、臨時順序節點。
- 所謂持久節點,是指在節點創建後,就一直存在,直到有刪除操作來主動清除這個節點,也就是說不會因爲創建該節點的客戶端會話失效而消失。
- 臨時節點的生命週期和客戶端會話綁定,也就是說,如果客戶端會話失效,那麼這個節點就會自動被清除掉。
7、在dubbo中,什麼時候更新本地的zookeeper信息緩存文件?訂閱zookeeper信息的整體過程是怎麼樣的?
dubbo向zk發送了訂閱請求以後,會去監聽zk的回調,(如果zk有回調就回去調用notify方法),接着會去創建接口配置信息的持久化節點,同時dubbo也設置了對該節點的監聽,zk節點如果發生了變化那麼會觸發回調方法,去更新zk信息的緩存文件,同時註冊服務在調用的時候會去對比最新的配置信息節點,有差別的話會以最新信息爲準重新暴露。《dubbo源碼解析-zookeeper訂閱》
zk訂閱流程
服務引用
1、描述一下dubbo服務引用的過程,原理
上文已提供。
2、既然你提到了dubbo的服務引用中封裝通信細節是用到了動態代理,那請問創建動態代理常用的方式有哪些,他們又有什麼區別?dubbo中用的是哪一種?(高頻題)
jdk、cglib還有javasisit,JDK的動態代理代理的對象必須要實現一個接口,而針對於沒有接口的類,則可用CGLIB。要明白兩者區別必須要瞭解原理,明白了原理自然一通百通,CGLIB其原理也很簡單,對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但由於採用的是繼承,所以不能對final修飾的類進行代理。除了以上兩種大家都很熟悉的方式外,其實還有一種方式,就是javassist生成字節碼來實現代理(dubbo多處用到了javassist)。
集羣容錯
1、dubbo提供了集中集羣容錯模式?
2、談談dubbo中的負載均衡算法及特點?最小活躍數算法中是如何統計活躍數的?簡單談談一致性哈希算法
這部分可以多結合官方文檔進行學習,而且涉及到了負載均衡的多個重要算法,也是高頻的考察熱點。
3、怎麼通過dubbo實現服務降級的,降級的方式有哪些,又有什麼區別?
當網站處於高峯期時,併發量大,服務能力有限,那麼我們只能暫時屏蔽邊緣業務,這裏面就要採用服務降級策略了。首先dubbo中的服務降級分成兩個:屏蔽(mock=force)、容錯(mock=fail)。
mock=force:return+null
表示消費方對該服務的方法調用都直接返回 null 值,不發起遠程調用。用來屏蔽不重要服務不可用時對調用方的影響。mock=fail:return+null
表示消費方對該服務的方法調用在失敗後,再返回 null 值,不拋異常。用來容忍不重要服務不穩定時對調用方的影響。
要生效需要在dubbo後臺進行配置的修改:
服務降級策略
4、dubbo監控平臺能夠動態改變接口的一些設置,其原理是怎樣的?
改變註冊在zookeeper上的節點信息,從而zookeeper通知重新生成invoker(這些具體細節在zookeeper創建節點,zookeeper連接,zookeeper訂閱中都詳細講了,這裏不再重複)。
學習框架三部曲:
- 掌握基本使用
- 看過源碼,知道其中原理
- 臨摹源碼,自己仿寫一個簡易的框架
臨摹源碼的這個過程,也需要分爲三個過程,分別是入門版(用最簡單的代碼表達出框架原理),進階版(加入設計模式等思想,在入門版的基礎上優化代碼),高級版(和框架代碼基本一致)。
作者:千淘萬漉
鏈接:https://www.jianshu.com/p/292fcdcfe41e
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。