兩天前寫了文章《「造個輪子」——cicada(輕量級 WEB 框架)》 向你們介紹了 cicada
以後收到不少反饋,也有許多不錯的建議。java
同時在 GitHub 也收穫了 80 幾顆 小♥♥(絕對不是刷的。。)git
也有朋友但願能出一個源碼介紹,本文就目前的 v1.0.1
版原本一塊兒分析分析。github
沒有看錯,剛發佈就修復了一個 bug,想要試用的請升級到
1.0.1
吧。json
通常在作一個新玩意以前都會有技術選型的過程,但這點在作 cicada
的時候卻異常簡單。設計模式
由於個人需求是想提供一個高性能的 HTTP 服務,縱觀整個開源界其實選擇很少。緩存
加上最近我在作 Netty 相關的開發,因此天然而然就選擇了它。服務器
同時 Netty 自帶了對 HTTP 協議的編解碼器,能夠很是簡單快速的開發一個 HTTP 服務器。我只須要把精力放在參數處理、路由等業務處理上便可。app
同時 Netty 也是基於 NIO 實現,性能上也有保證。關於 Netty 相關內容能夠參考這裏。框架
下面來重點分析其中的各個過程。性能
最核心的天然就是 HTTP 的處理 handle
,對應的就是 HttpHandle
類。
查看源碼其實很容易看出具體的步驟,註釋也很明顯。
這裏只分析重點功能。
先來考慮下需求。
首先做爲一個 HTTP 框架,天然是得讓使用者能有地方來實現業務代碼;就像我們如今使用 SpringMVC 時寫的 controller
同樣。
其實當時考慮過三種方案:
properties
配置文件,在裏面編寫 JSON 格式的對應關係。這時就得分析各個方案的優缺點了。
方案二和三其實就是 XML 和 json 的對比了;XML 會讓維護者感到結構清晰,同時便於維護和新增。
JSON 就不太方便處理了,而且在這樣的場景並不用於傳輸天然也發揮不出優點。
最後考慮到如今流行的 SpringBoot 都在去 XML,要是再搞一個依賴於 XML 的東西也跟不上你們的使用習慣。
因而就採用相似於 SpringMVC 這樣的註解形式。
既然採用了註解,那框架怎麼知道用戶訪問某個接口時能對應到業務類呢?
因此首先第一步天然是須要將加有註解的類所有掃描一遍,放到一個本地緩存中。
這樣才能方便後續的路由定位。
其中核心的源碼在 routeAction
方法中。
首先會全局掃描使用了 @CicadaAction
的註解,而後再根據請求地址找到對應的業務類。
全局掃描代碼:
首先是獲取到項目中自定義的全部類,而後判斷是否加有 @CicadaAction
註解。
是目標類則把他緩存到一個本地 Map 中,方便下次訪問時能夠再也不掃描直接從緩存中獲取便可(反射很耗性能)。
執行完 routeAction
後會得到真正的業務類類型。
Class<?> actionClazz = routeAction(queryStringDecoder, appConfig);
拿到業務類的類類型以後就成功一大半了,只須要反射生成它的對象而後執行方法便可。
在執行方法以前又要涉及到一個問題,參數我該怎麼傳遞呢?
考慮到靈活性我採用了最簡答 Map 方式。
所以定義了一個通用的 Param 接口並繼承了 Map 接口。
public interface Param extends Map<String, Object> {
/** * get String * @param param * @return */
String getString(String param);
/** * get Integer * @param param * @return */
Integer getInteger(String param);
/** * get Long * @param param * @return */
Long getLong(String param);
/** * get Double * @param param * @return */
Double getDouble(String param);
/** * get Float * @param param * @return */
Float getFloat(String param);
/** * get Boolean * @param param * @return */
Boolean getBoolean(String param) ;
}
複製代碼
其中封裝了幾種基本類型的獲取方式。
同時在 buildParamMap()
方法中,將接口中的參數封裝到這個 Map 中。
Param paramMap = buildParamMap(queryStringDecoder);
複製代碼
最後只須要執行業務便可;因爲在上文已經獲取到業務類的類類型,因此這裏經過反射便可調用。
同時也定義了一個業務類須要實現的一個通用接口 WorkAction
,想要實現具體業務只要實現它就行。
而這裏的方法參數天然就是剛纔定義的參數接口 Param
。
因爲全部的業務類都是實現了 WorkAction
,因此在反射時均可以定義爲 WorkAction
對象。
WorkAction action = (WorkAction) actionClazz.newInstance();
WorkRes execute = action.execute(paramMap);
複製代碼
最後將構建好的參數 map 傳入便可。
有了請求那天然也得有響應,觀察剛纔定義的 WorkAction
接口能夠發現其實定義了一個 WorkRes
響應類。
全部的響應數據都須要封裝到這個對象中。
這個沒啥好說的,都是一些基本數據。
最後在 responseMsg()
方法中將響應數據編碼爲 JSON 輸出便可。
攔截器也是一個框架基本的功能,用處很是多。
cicada
的實現原理很是簡單,就是在 WorkAction
接口執行業務邏輯以前調用一個方法、執行完畢以後調用另外一個方法。
也是一樣的思路須要定義一個接口 CicadaInterceptor
,其中有兩個方法。
看方法名字天然也能看出具體做用。
同時在這兩個方法中執行具體的調用。
這裏重點要看看 interceptorBefore
方法。
其中也是加入了一個緩存,儘可能的減小反射操做。
就這樣的攔截器接口是夠用了,但並非全部的業務都須要實現兩個接口。
所以也提供了一個適配器 AbstractCicadaInterceptorAdapter
。
它做爲一個抽象類實現了 CicadaInterceptor
接口,這樣後續的攔截業務也可繼承該接口選擇性的實現方法便可。
相似於這樣:
v1.0.1
版本的 cicada
就介紹完畢了,其中的原理和源碼都比較簡單。
大量使用了反射和一些設計模式、多態等應用,這方面經驗較少的朋友能夠參考下。
同時也有不少不足;好比傳參後續會考慮更加優雅的方式、攔截器目前寫的比較死,後續會利用動態代理實現自定義攔截。
其實 cicada
只是利用週末兩天時間作的,bug 確定少不了;也歡迎你們在 GitHub 上提 issue 參與。
最後貼下項目地址:
你的點贊與轉發是最大的支持。