




已閱讀5頁,還剩12頁未讀, 繼續(xù)免費閱讀
版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
你知道如何寫一個框架嗎?詳細步驟放送定位 所謂定位就是回答幾個問題,我出于什么目的要寫一個框架,我的這個框架是干什么的,有什么特性適用于什么場景,我的這個框架的用戶對象是誰,他們會怎么使用,框架由誰維護將來怎么發(fā)展等等。如果你打算寫框架,那么肯定心里已經(jīng)有一個初步的定位,比如它是一個緩存框架、Web MVC框架、IOC框架、ORM/數(shù)據(jù)訪問框架、RPC框架或是一個用于Web開發(fā)的全棧式框架。 是 否要重復造輪子?除非是練手項目,一般我們是有了解決不了問題的時候才會考慮不使用既有的成熟的框架而重復造輪子的,這個時候需要列出新框架主要希望解決 什么問題。有關是否應該重復造輪子的話題討論了很多,我的建議是在把問題列清后進行簡單的研究看看是否可以通過擴展現(xiàn)有的框架來解決這個問題。一般而言大 部分成熟的框架都有一定的擴展和內部組件的替換能力,可以解決大部分技術問題,但在如下情況下我們可能不得不自己去寫一個框架,比如即使通過擴展也無法滿 足技術需求、安全原因、需要更高的生產(chǎn)力、需要讓框架和公司內部的流程更好地進行適配、開源的普適框架無法滿足性能需求、二次開發(fā)的成本高于重新開發(fā)的成 本等等。 主打輕量級?輕量級是很多人打算自己寫一個新框架的原因,但我們要明白,大部分項目在一開始的時候其實都是輕量級的,隨著框架 的用戶越來越多,它必定需要滿足各種奇怪的需求,在經(jīng)過了無數(shù)次迭代之后,框架的主線流程就會多很多擴展點、檢測點,這樣框架勢必變得越來越重(從框架的 入口到框架的工作結束的方法調用層次越來越多,勢必框架也就越來越慢),如果你打算把框架定位于一個輕量級的框架的話,那么在今后的迭代過程中需要進行一 些權衡,在心中有堅定的輕量級的理念的同時不斷做性能測試來確??蚣艿妮p量,否則隨著時間的發(fā)展框架可能會越來越重進而偏離了開始的定位。 特性?如果你打算寫一個框架,并且只有輕量級這一個理由的話,你或許應該再為自己的框架想一些新特性,就像做一個產(chǎn)品一樣,如果找不出兩個以上的亮點,那么這個產(chǎn)品不太可能成功,比如你的新框架可以是一個零配置的框架,可以是一個前端開發(fā)也能用的后端框架。 其它?一般來說框架是給程序員使用的,我們要考慮框架使用的頻度是怎么樣的,這可能決定的框架的性能需求和穩(wěn)定性需求。還有,需要考慮框架將來怎么發(fā)展,是希望走開源路線還是商業(yè)路線。當然,這些問題也可以留到框架有一個大致的結構后再去考慮。 我們來為本文模擬一個場景,假設我們覺得現(xiàn)有的Spring MVC等框架開發(fā)起來效率有點低,打算重復造輪子,對于新框架的定位是一個給Java程序員使用的輕量級的、零配置的、易用的、易擴展的Web MVC框架。調研雖然到這里你已經(jīng)決定去寫一個框架了,但是在著手寫之前還是至少建議評估一下市面上的類似(成熟)框架。需要做的是通讀這些框架的文檔以及閱讀一些源碼,這么做有幾個目的:通過分析現(xiàn)有框架的功能,可以制定出一個新框架要實現(xiàn)的功能列表。 通過分析現(xiàn)有框架的問題,總結出新框架需要避免的東西和改善的地方。 通過閱讀現(xiàn)有框架的源碼,幫助自己理清框架的主線流程為總體設計做鋪墊(后面總體設計部分會更多談到)。 如果能充分理解現(xiàn)有的框架,那么你就是站在巨人的肩膀上寫框架,否則很可能就是在井底造輪子。 新 開發(fā)一個框架的好處是沒有兼容歷史版本的包袱,但是責任也同樣重大,因為如果對于一開始的定位或設計工作沒有做好的話,將來如果要對格局進行改變就會有巨 大的向前兼容的包袱(除非你的框架沒有在任何正式項目中使用),兼容意味著框架可能會越來越重,可能會越來越難看,閱讀至少一到兩個開源實現(xiàn),做好充分的 調研工作可以使你避免犯大錯。假設我們評估了一些主流框架后已經(jīng)很明確,我們的MVC框架是一個Java平臺的、基于Servlet的輕量級的Web MVC框架,主要的理念是約定優(yōu)于配置,高內聚大于低耦合,提供主流Web MVC框架的大部分功能,并且易用方面有所創(chuàng)新,新特性體包括:起手零配置,總體上約定由于配置,即使需要擴展配置也支持通過代碼和配置文件兩種方式進行配置。 除了Servlet之外不依賴其它類庫,支持通過插件方式和諸如Spring等框架進行整合。 更優(yōu)化的項目結構,不需要按照傳統(tǒng)的Java Web項目結構那樣來分離代碼和WEB-INF,視圖可以和代碼在一起,閱讀代碼更便利。 攔截器和框架本身更緊密,提供Action、Controller和Global三個級別的攔截器(或者說過濾器)。 豐富的Action的返回值,返回的可以是視圖、可以是重定向、可以是文件、可以是字符串、可以是Json數(shù)據(jù),可以是Javascript代碼等等。 支持針對測試環(huán)境自動生成測試的視圖模型數(shù)據(jù),以便前端和后端可以同時開發(fā)項目。 支持在開發(fā)的時候自動生成路由信息、模型綁定、異常處理等配置的信息頁面和調試頁面,方便開發(fā)和調試。 提供一套通用的控件模版,使得,并且支持多種模版引擎,比如Jsp、Velocity、Freemarker、Mustache等等。嗯,看上去挺誘人的,這是一個不錯的開端,如果你要寫的框架自己都不覺得想用的話,那么別人就更不會有興趣來嘗試使用你的框架了。解決難點之 所以把解決難點放在開搞之前是因為,如果實現(xiàn)這個框架的某些特性,甚至說實現(xiàn)這個框架的主流程有一些核心問題難以解決,那么就要考慮對框架的特性進行調 整,甚至取消框架的開發(fā)計劃了。有的時候我們在用A平臺的時候發(fā)現(xiàn)一個很好用的框架,希望把這個框架移植到B平臺,這個想法是好的,但之所以在這以前這么 多年沒有人這么干過是因為這個平臺的限制壓根不可能實現(xiàn)這樣的東西。比如我們要實現(xiàn)一個MVC框架,勢必需要依賴平臺提供的反射特性,如果你的語言平臺壓 根就沒有運行時反射這個功能,那么這就是一個非常難以解決的難點。又比如我們在某個平臺實現(xiàn)一個類似于.NET平臺Linq2Sql的數(shù)據(jù)訪問框架,但如 果這個目標平臺的開發(fā)語言并不像C#那樣提供了類型推斷、匿名類型、Lambda表達式、擴展方法的話那么由于語法的限制你寫出來的框架在使用的時候是無 法像.NET平臺Linq2Sql那樣優(yōu)雅的,這就違背了實現(xiàn)框架的主要目的,實現(xiàn)新的框架也就變得意義不大了。對于我們要實現(xiàn)的MVC框 架貌似不存在什么根本性的無法解決的問題,畢竟在Java平臺已經(jīng)有很多可以參考的例子了。如果框架的實現(xiàn)總體上沒什么問題的話,就需要逐一評估框架的這 些新特性是否可以解決。建議對于每一個難點特性做一個原型項目來證明可行,以免在框架實現(xiàn)到一半的時候發(fā)現(xiàn)有無法解決的問題就比較尷尬了。分析一下,貌似我們要實現(xiàn)的這8大特性只有第1點要研究一下,看看如何免配置通過讓代碼方式讓我們的Web MVC框架可以和Servlet進行整合,如果無法實現(xiàn)的話,我們可能就需要把第1點特性從零配置改為一分鐘快速配置了。開搞首先需要給自己框架取一個名字,取名要考慮到易讀、易寫、易記,也需要盡量避免和市面上其它產(chǎn)品的名字重復,還有就是最好不要起一個侮辱其它同類框架的名字以免引起公憤。如果將來打算把項目搞大的話,可以提前注冊一下項目的相關域名,畢竟現(xiàn)在域名也便宜,避免到時候項目名和域名差距很大,或項目的.com或.org域名對應了一個什么不太和諧的網(wǎng)站這就尷尬了。然后就是找一個地方來托管自己的代碼,如果一開始不希望公開代碼的話,最好除了本地源代碼倉庫還有一個異地的倉庫以免磁盤損壞導致抱憾終身,當然如果不怕出丑的話也可以在起步的時候就使用Github等網(wǎng)站來托管自己的代碼。總體設計對 于總體設計我的建議是一開始不一定需要寫什么設計文檔畫什么類圖,因為可能一開始的時候無法形成這么具體的概念,我們可以直接從代碼開始做第一步??蚣艿?使用者一般而言還是開發(fā)人員,拋開框架的內在的實現(xiàn)不說,框架的API設計的好壞取決于兩個方面。對于普通開發(fā)人員而言就是使用層面的API是否易于使 用,拿我們的MVC框架舉例來說:最基本的,搭建一個HelloWorld項目,聲明一個Controller和Action,配置一個路由規(guī)則讓Get方法的請求可以解析到這個Action,可以輸出HelloWorld文字,怎么實現(xiàn)?如果要實現(xiàn)從Cookie以及表單中獲取相關數(shù)據(jù)綁定到Action的參數(shù)里面,怎么實現(xiàn)?如果要配置一個Action在調用前需要判斷權限,在調用后需要記錄日志,怎么實現(xiàn)?我們這里說的API,它不一定全都是方法調用的API,廣義上來說我們認為框架提供的接入層的使用都可以認為是API,所以上面的一些功能都可以認為是MVC框架的API??蚣艹颂峁┗镜墓δ?,還要提供一定程度的擴展功能,使得一些復雜的項目能夠在某些方面對框架進行增強以適應各種需求,比如:我的Action是否可以返回圖片驗證碼? 我的Action的參數(shù)綁定是否可以從Memcached中獲取數(shù)據(jù)? 如果出現(xiàn)異常,能否在開發(fā)的時候顯示具體的錯誤信息,在正式環(huán)境顯示友好的錯誤頁面并且記錄錯誤信息到數(shù)據(jù)庫? 一 般而言如果要實現(xiàn)這樣的功能就需要自己實現(xiàn)框架公開的一些類或接口,然后把自己的實現(xiàn)注冊到框架中,讓框架可以在某個時候去使用這些新的實現(xiàn)。這就需 要框架的設計者來考慮應該以怎么樣的友好形式公開出去哪些內容,使得以后的擴展實現(xiàn)在自由度以及最少實現(xiàn)上的平衡,同時要兼顧外來的實現(xiàn)不破壞框架已有的 結構。要想清楚這些不是一件容易的事情,所以在框架的設計階段完全可以使用從上到下的方式進行設計。也就是不去考慮框架怎么實現(xiàn),而是以一 個使用者的身份來寫一個框架的示例網(wǎng)站,API怎么簡單怎么舒服就怎么設計,只從使用者的角度來考慮問題。對于相關用到的類,直接寫一個空的類(能用接口 的盡量用接口,你的目的只是通過編譯而不是能運行起來),讓程序可以通過編譯就可以了。你可以從框架的普通使用開始寫這樣一個示例網(wǎng)站,然后再寫各種擴展 應用,在此期間你可能會用到框架內部的20個類,這些類就是框架的接入類,在你的示例網(wǎng)站通過編譯的那剎那,其實你已經(jīng)實現(xiàn)了框架的接入層的設計。這里值得一說的是API的設計蘊含了非常多的學問以及經(jīng)驗,要在目標平臺設計一套合理易用的API首先需要對目標平臺足夠了解,每一個平臺都有一些約定俗成的規(guī)范,如果設計的API能符合這些規(guī)范那么開發(fā)人員會更容易接受這個框架,此外還有一些建議:之 所以我們把API的設計先行,而不是讓框架的設計先行是因為這樣我們更容易設計出好用的API,作為框架的實現(xiàn)者,我們往往會進行一些妥協(xié),我們可能會為 了在框架內部DRY而設計出一套丑陋的API讓框架的使用者去做一些重復的工作;我們也可能會因為想讓框架變得更松耦合強迫框架的使用者去使用到框架的一 些內部API去初始化框架的組件。如果框架不是易用的,那么框架的內部設計的再合理又有什么意義? 盡量少暴露一些框架內部的類名吧,對 于框架的使用者來說,你的框架對他一點都不熟悉,如果要上手你的框架需要學習一到兩個類尚可接受,如果要使用到十幾個類會頭暈腦脹的,即使你的框架有非常 多的功能以及配置,可以考慮提供一個入口類,比如創(chuàng)建一個ConfigCenter類作為入口,讓使用者可以僅僅探索這個類便可對框架進行所有的配置。 一 個好的框架是可以讓使用者少犯錯誤的,框架的設計者務必要考慮到,框架的使用者沒有這個業(yè)務來按照框架的最佳實踐來做,所以在設計API的時候,如果你希 望API的使用者一定要按照某個方式來做的話,可以考慮設置一個簡便的重載來加載默認的最合理的使用方式而不是要求使用者來為你的方法初始一些什么依賴, 同時也可以在API內部做一些檢測,如果發(fā)現(xiàn)開發(fā)人員可能會犯錯進行一些提示或拋出異常。好的框架無需過多的文檔,它可以在開發(fā)人員用的時候告知它哪里錯 了,最佳實踐是什么,即便他們真的錯了也能以默認的更合理的方式來彌補這個錯誤。 建議所有的API都有一套統(tǒng)一的規(guī)范,比如入口都叫XXXCenter或XXXManager,而不是叫XXXCenter、YYYManager和 ZZZService。API往往需要進行迭代和改良的,在首個版本中把好名字用掉也不一定是一個好辦法,最好還是給自己的框架各種API的名字留一點余 地,這樣以后萬一需要升級換代不至于太牽強。 下一步工作就是把項目中那些空的類按照功能進行劃分。目的很簡單,就是讓你的框架 的100個類或接口能夠按照功能進行拆分和歸類,這樣別人一打開你的框架就可以馬上知道你的框架分為哪幾個主要部分,而不是在100個類中暈眩;還有因為 一旦在你的框架有使用者后你再要為API相關的那些類調整包就比困難了,即使你在創(chuàng)建框架的時候覺得我的框架就那么十幾個類無需進行過多的分類,但是在將 來框架變大又發(fā)現(xiàn)當初設計的不合理,無法進行結構調整就會變得很痛苦。因此這個工作還是相當重要的,對于大多數(shù)框架來說,可以有幾種切蛋糕的方式:分 層。我覺得框架和應用程序一樣,也需要進行分層。傳統(tǒng)的應用程序我們分為表現(xiàn)層、邏輯層和數(shù)據(jù)訪問層,類似的對于很多框架也可以進行橫向的層次劃分。要分 層的原因是我們的框架要處理的問題是基于多層抽象的,就像如果沒有OSI七層模型,要讓一個HTTP應用去直接處理網(wǎng)絡信號是不合理的也是不利于重用的。 舉一個例子,如果我們要寫一個基于Socket的RPC的框架,我們需要處理方法的代理以及序列化,以及序列化數(shù)據(jù)的傳輸,這完全是兩個層面的問題,前者 偏向于應用層,后者偏向于網(wǎng)絡層,我們完全有理由把我們的框架分為兩個層面的項目(至少是兩個包),rpc.core和rpc.socket,前者不關心 網(wǎng)絡實現(xiàn)來處理所有RPC的功能,后者不關心RPC來處理所有的Socket功能,在將來即使我們要淘汰我們的RPC的協(xié)議了,我們也可以重用 rpc.socket項目,因為它和RPC的實現(xiàn)沒有任何關系,它關注的只是socket層面的東西。 橫切。剛才說的分層是橫向的分 割,橫切是縱向的分割(橫切是跨多個模塊的意思,不是橫向來切的意思)。其實橫切關注點就是諸如日志、配置、緩存、AOP、IOC等通用的功能,對于這部 分功能,我們不應該把他們和真正的業(yè)務邏輯混淆在一起。對于應用類項目是這樣,對于框架類項目也是這樣,如果某一部分的代碼量非常大,完全有理由為它分出 一個單獨的包。對于RPC項目,我們可能就會把客戶端和服務端通訊的消息放在common包內,把配置的處理單獨放在config包內。 功能。也就是要實現(xiàn)一個框架主要解決的問題點,比如對于上面提到的RPC框架的core部分,可以想到的是我們主要解決是客戶端如何找到服務端,如何把進 行方法調用以及把方法的調用信息傳給目標服務端,服務端如何接受到這樣的信息根據(jù)配置在本地實例化對象調用方法后把結果返回客戶端三大問題,那么我們可能 會把項目分為routing、client、server等幾個包。 如果是一個RPC框架,大概是這樣的結構:對于我們的Web MVC框架,舉例如下:我們可以有一個mvc.core項目,細分如下的包: common:公共的一組件,下面的各模塊都會用到 config:配置模塊,解決框架的配置問題 startup:啟動模塊,解決框架和Servlet如何進行整合的問題 plugin:插件模塊,插件機制的實現(xiàn),提供IPlugin的抽象實現(xiàn) routing:路由模塊,解決請求路徑的解析問題,提供了IRoute的抽象實現(xiàn)和基本實現(xiàn) controller:控制器模塊,解決的是如何產(chǎn)生控制器 model:視圖模型模塊,解決的是如何綁定方法的參數(shù) action:action模塊,解決的是如何調用方法以及方法返回的結果,提供了IActionResult的抽象實現(xiàn)和基本實現(xiàn) view:視圖模塊,解決的是各種視圖引擎和框架的適配 filter:過濾器模塊,解決是執(zhí)行Action,返回IActionResult前后的AOP功能,提供了IFilter的抽象實現(xiàn)以及基本實現(xiàn) 我們可以再創(chuàng)建一個mvc.extension項目,細分如下的包: filters:一些IFilter的實現(xiàn) results:一些IActionResult的實現(xiàn) routes:一些IRoute的實現(xiàn) plugins:一些IPlugin的實現(xiàn) 這里我們以IXXX來描述一個抽象,可以是接口也可以是抽象類,在具體實現(xiàn)的時候根據(jù)需求再來確定。這 種結構的劃分方式完全吻合上面說的切蛋糕方式,可以看到除了橫切部分和分層部分,作為一個Web MVC框架,它核心的組件就是routing、model、view、controller、action(當然,對于有些MVC框架它沒有route部 分,route部分是交由Web框架實現(xiàn)的)。如果我們在這個時候還無法確定框架的模塊劃分的話,問題也不大,我們可以在后續(xù)的搭建龍骨的步驟中隨著更多的類的建立,繼續(xù)理清和確定模塊的劃分。經(jīng)過了設計的步驟,我們應該心里對下面的問題有一個初步的規(guī)劃了:我們的框架以什么形式來提供如何優(yōu)雅的API? 我們的框架包含哪些模塊,模塊大概的作用是什么? 搭建龍骨在 經(jīng)過了初步的設計之后,我們可以考慮為框架搭建一套龍骨,一套抽象的層次關系。也就是用抽象類、接口或空的類實現(xiàn)框架,可以通過編譯,讓框架撐起來,就像 造房子搭建房子的鋼筋混凝土結構(添磚加瓦是后面的事情,我們先要有一個結構)。對于開發(fā)應用程序來說,其實沒有什么撐起來一說,因為應用程序中很多模塊 都是并行的,它可能并沒有一個主結構,主流程,而對于框架來說,它往往是一個高度面向對象的,高度抽象的一套程序,搭建龍骨也就是搭建一套抽象層。這么說 可能有點抽象,我們還是來想一下如果要做一個Web MVC框架,需要怎么為上面說的幾個核心模塊進行抽象(我們也來體會一下框架中一些類的命名,這里我們?yōu)榱烁逦?,為所有接口都命名為IXXX,這點不太 符合Java的命名規(guī)范):routing MVC的入口是路由 每一個路由都是IRoute代表了不同的路由實現(xiàn),它也提供一個getRouteResult()方法來返回RouteResult對象 我們實現(xiàn)一個框架自帶的DefaultRoute,使得路由支持配置,支持默認值,支持正則表達式,支持約束等等 我們需要有一個Routes類來管理所有的路由IRoute,提供一個findRoute()方法來返回RouteResult對象,自然我們這邊調用的就是IRoute的getRouteResult()方法,返回能匹配到的結果 RouteResult對象就是匹配的路由信息,包含了路由解析后的所有數(shù)據(jù) controller 路由下來是控制器 我們有IControllerFactory來創(chuàng)建Controller,提供createController()方法來返回IController IController代表控制器,提供一個execute()方法來執(zhí)行控制器 我們實現(xiàn)一個框架自帶的DefaultControllerFactory來以約定由于配置的方式根據(jù)約定規(guī)則以及路由數(shù)據(jù)RouteResult來找到IController并創(chuàng)建它 我 們?yōu)镮Controller提供一個抽象實現(xiàn),AbstractController,要求所有MVC框架的使用者創(chuàng)建的控制器需要繼承 AbstractController,在這個抽象實現(xiàn)中我們可以編寫一些便捷的API以便開發(fā)人員使用,比如view()方法、file()方法、 redirect()方法、json()方法、js()方法等等 action 找到了控制器后就是來找要執(zhí)行的方法了 我們有IActionResult來代表Action返回的結果,提供一個execute()方法來執(zhí)行這個結果 我們的框架需要實現(xiàn)一些自帶的IActionResult,比如ContentResult、ViewResult、FileResult、JsonResult、RedirectResult來對應AbstractController的一些便捷方法 再來定義一個IActionInvoker來執(zhí)行Action,提供一個invokeAction()方法 我們需要實現(xiàn)一個DefaultActionInvoker以默認的方式進行方法的調用,也就是找到方法的一些IFilter按照一定的順序執(zhí)行他們,最后使用反射進行方法的調用得到上面說的IActionResult并執(zhí)行它的execute()方法 filter 我們的框架很重要的一點就是便捷的過濾器 剛才提到了IFilter,代表的是一個過濾器,我們提供IActionFilter對方法的執(zhí)行前后進行過濾,提供IResultFilter對IActionResult執(zhí)行前后進行過濾 我們的IActionInvoker怎么找到需要執(zhí)行的IFilter呢,我們需要定義一個IFilterProvider來提供過濾器,它提供一個getFilters()方法來提供所有的IFilter的實例 我 們的框架可以實現(xiàn)一些自帶的IFilterProvider,比如AnnotationFilterProvider通過掃描Action或 Controller上的注解來獲取需要執(zhí)行的過濾器信息;比如我們還可以實現(xiàn)GlobalFilterProvider,開發(fā)人員可以直接通過配置或代 碼方式告知框架應用于全局的IFilter 既然我們實現(xiàn)了多個IFilterProvider,我們自然需要有一個類來管理這些IFilterProvider,我們實現(xiàn)一個FilterProviders類并提供getFilters()方法(這和我們的Routes類來管理IRoute是類似的,命名統(tǒng)一) view 各種IActionResult中最特殊最復雜的就是ViewResult,我們需要有一個單獨的包來處理ViewResult的邏輯 我們需要有IViewEngine來代表一個模版引擎,提供一個getViewEngineResult()方法返回ViewEngineResult ViewEngineResult包含視圖引擎尋找視圖的結果信息,里面包含IView和尋找的一些路徑等 IView自然代表的是一個視圖,提供render()方法(或者為了統(tǒng)一也可以叫做execute)來渲染視圖 我 們的框架可以實現(xiàn)常見的一些模版引擎,比如FreemarkerViewEngine、VelocityViewEngine 等,VelocityViewEngine返回的ViewEngineResult自然包含的是一個實現(xiàn)IView的VelocityView,不會返回 其它引擎的IView 同樣的,我們是不是需要一個ViewEngines來管理所有的IViewEngine呢,同樣也是實現(xiàn)findViewEngine()方法 common 這里可以放一些項目中各個模塊都要用到的一些東西 比 如各種context,context代表的是執(zhí)行某個任務需要的環(huán)境信息,這里我們可以定義HttpContext、 ControllerContext、ActionContext和ViewContext,后者繼承前者,隨著MVC處理流程的進行,View執(zhí)行時的 上下文相比Action執(zhí)行時的上下文信息肯定是多了視圖的信息,其它同理,之所以把這個信息放在common里面而不是放在各個模塊自己的包內是因為這 樣更清晰,可以一目了然各種對象的執(zhí)行上下文有一個立體的概念 比如各種helper或utility 接下去就不再詳細闡述model、plugin等模塊的內容了??吹竭@里,我們來總結一下,我們的MVC框架在組織結構上有著高度的統(tǒng)一:如果xxx本身并無選擇策略,但xxx的創(chuàng)建過程也不是一個new這么簡單的,可以由xxxFactory類來提供一個xxx 如果我們需要用到很多個yyy,那么我們會有各種yyyProvider(通過getyyy()方法)來提供這些yyy,并且我們需要有一個yyyProviders來管理這些yyyProvider 如果zzz的選擇是有策略性的,會按照需要選擇zzz1或zzzN,那么我們可能會有一個zzzs來管理這些zzz并且(通過findzzz()方法)來提供合適的zzz 同 時我們框架的相關類的命名也是非常統(tǒng)一的,可以一眼看出這是實現(xiàn)、還是抽象類還是接口;是提供程序,是執(zhí)行結果還是上下文。當然,在將來的代碼實現(xiàn)過程中 很可能會把很多接口變?yōu)槌橄箢愄峁┮恍┠J的實現(xiàn),這并不會影響項目的主結構。我們會在模式篇對框架常用的一些高層設計模式做更多的介紹。到了這里,我們的項目里已經(jīng)有幾十個空的(抽象)類、接口了,其中也定義了各種方法可以把各個模塊串起來(各種find()方法和execute()方法),可以說整個項目的龍骨已經(jīng)建立起來了,這種感覺很好,因為我們心里很有底,我們只需要在接下去的工作中做兩個事情:實現(xiàn)各種DefaultXXX來走通主流程 實現(xiàn)各種IyyyProvider和Izzz接口來完善支線流程 走通主線流程所謂走通主線流程,就是讓這個框架可以以一個HelloWorld形式跑起來,這就需要把幾個核心類的核心方法使用最簡單的方式進行實現(xiàn),還是拿我們的MVC框架來舉例子:從startup開始,可能需要實現(xiàn)ServletContextListener來動態(tài)注冊我們框架的入口Servlet,暫且起名為DispatcherServlet吧,在這個類中我們需要走一下主線流程 調用Routes.findRoute()獲得IRoute 調用IRoute.getRouteResult()來獲得RouteResult 使用拿到的RouteResult作為參數(shù)調用DefaultControllerFactory.createController()獲得IController(其實也是AbstractController) 調用IController.execute() 在 config中創(chuàng)建一個IConfig作為一種配置方式,我們實現(xiàn)一個DefaultConfig,把各種默認實現(xiàn)注冊到框架中去,也就是 DefaultRoute、DefaultControllerFactory、DefaultActionInvoker,然后把各種 IViewEngine加入ViewEngines 然后需要完成相關默認類的實現(xiàn): 實現(xiàn)Routes.findRoute() 實現(xiàn)DefaultRoute.getRouteResult() 實現(xiàn)DefaultControllerFactory.createController() 實現(xiàn)AbstractController.execute() 實現(xiàn)DefaultActionInvoker.invokeAction() 實現(xiàn)ViewResult.execute() 實現(xiàn)ViewEngines.findViewEngine() 實現(xiàn)VelocityViewEngine.getViewEngineResult() 實現(xiàn)VelocityView.render() 在這一步,我們并不一定要去觸碰filter和model這部分的內容,我們的主線流程只是解析路由,獲得控制器,執(zhí)行方法,找到視圖然后渲染視圖。過濾器和視圖模型的綁定屬于增強型的功能,屬于支線流程,不屬于主線流程。雖 然在這里我們說了一些MVC的實現(xiàn),但本文的目的不在于教你實現(xiàn)一個MVC框架,所以不用深究每一個類的實現(xiàn)細節(jié),這里想說的是,在前面的龍骨搭建完后, 你會發(fā)現(xiàn)按照這個龍骨為它加一點肉上去實現(xiàn)主要的流程是順理成章的事情,毫無痛苦。在整個實現(xiàn)的過程中,你可以不斷完善common下的一些 context,把方法的調用參數(shù)封裝到上下文對象中去,不但看起來清楚且符合開閉原則。到這里,我們應該可以跑起來在設計階段做的那個示例網(wǎng)站的 HelloWorld功能了。在這里還想說一點,有些人在實現(xiàn)框架的時候并沒有搭建龍骨的一步驟,直接以非OOP的方式實現(xiàn)了主線流程,這種方式有以下幾個缺點:不容易做到SRP單一指責原則,你很容易把各種邏輯都集中寫在一起,比如大量的邏輯直接寫到了DispatcherServlet中,輔助一些Service或Helper,整個框架就肥瘦不勻,有些類特別龐大有些類特別小。不容易做到OCP開閉原則,擴展起來不方便需要修改老的代碼,我們期望的擴展是實現(xiàn)新的類然后讓框架感知,而不是直接修改框架的某些代碼來增強功能。很難實現(xiàn)DIP依賴倒置原則,即使你依賴的確實是IService但其實就沒意義,因為它只有一個實現(xiàn),只是把他當作幫助類來用罷了。實現(xiàn)各種支線流程我們想一下,對于這個MVC框架有哪些沒有實現(xiàn)的支線流程?其實無需多思考,因為我們在搭建龍骨階段的設計已經(jīng)給了我們明確的方向了,我們只需要把除了主線之外的那些龍骨上也填充一些實體即可,比如:實現(xiàn)更多的IRoute,并注冊到Routes 實現(xiàn)更多的IViewEngine,并注冊到ViewEngines 實現(xiàn)必要的IFilterProvider以及FilterProviders,把IFilterProvider注冊到FilterProviders 增強DefaultActionInvoker.invokeAction()方法,在合適的時候調用這些IFilter 實現(xiàn)更多的IActionResult,并且為AbstractController實現(xiàn)更多的便捷方法來返回這些IActionResult 實現(xiàn)更多model模塊的內容和plugin模塊的內容 實現(xiàn)了這一步后,你會發(fā)現(xiàn)整個框架飽滿起來了,每一個包中不再是僅有的那些接口和默認實現(xiàn),而且會有一種OOP的爽快感,爽快感來源于幾個方面:面對接口編程抽象和多態(tài)的放心安心的爽快感 為抽象類實現(xiàn)具體類享受到父類大量實現(xiàn)的滿足的爽快感 實現(xiàn)了大量的接口和抽象類后充實的爽快感 我們再來總結一下之前說的那些內容,實現(xiàn)一個框架的第一大步就是:設計一套合理的接口 為框架進行模塊劃分 為框架搭建由抽象結構構成的骨架 在這個骨架的基礎上實現(xiàn)一個HelloWorld程序 為這個骨架的其它部分填充更多實現(xiàn) 經(jīng) 過這樣的一些步驟后可以發(fā)現(xiàn)這個框架是很穩(wěn)固的,很平衡的,很易于擴展的。其實到這里很多人覺得框架已經(jīng)完成了,有血有肉,其實個人覺得只能說開發(fā)工作實 現(xiàn)了差不多30%,后文會繼續(xù)說,畢竟直接把這樣一個血肉之軀拿出去對外有點嚇人,我們需要為它進行很多包裝和完善。單元測試在這之前我們寫的框架只能說是一個在最基本的情況下可以使用的框架,作為一個框架我們無法預測開發(fā)人員將來會怎么使用它,所以我們需要做大量的工作來確??蚣懿坏鞣N功能都是正確的,而且還是健壯的。寫應用系統(tǒng)的代碼,大多數(shù)項目是不會去寫單元測試的,原因很多:項目趕時間,連做一些輸入驗證都沒時間搞,哪里有時間寫測試代碼。 項目對各項功能的質量要求不高,只要能在標準的操作流程下功能可用即可。 項目基本不會去改或是臨時項目,一旦測試通過之后就始終是這樣子了,沒有迭代。 對于框架,恰恰相反,沒有配套的單元測試的框架(也就是僅僅使用人工的方式進行測試,比如在main中調用一些方法觀察日志或輸出,或者運行一下示例項目查看各種功能是否正常,是非??膳碌模┰蛉缦拢鹤詣踊潭雀?,回歸需要的時間短,甚至可以整合到構建過程中進行,這是人工測試無法實現(xiàn)的。 框架一定是有非常多的迭代和重構的, 每一次修改雖然只改了A功能,但是可能會影響到B和C功能,人工測試的話你可能只會驗證A是否正常,容易忽略B和C,使用單元測試的話只要所有功能都有覆蓋,那么幾乎不可能遺漏因為修改導致的潛在問題,而且還能反饋出來因為修改導致的兼容性問題。 之前說過,一旦框架開放出去,框架的使用者可能會以各種方式在各種環(huán)境來使用你的框架,環(huán)境不同會造成很多怪異的邊界輸入或非法輸入,需要使用單元測試對代碼進行嚴格的邊界測試,以確??蚣芸梢栽趪揽岬沫h(huán)境下生存。 單元測試還能幫助我們改善設計,在寫單元測試的時候如果發(fā)現(xiàn)目標代碼非常難以進行模擬難以構建有效的單元測試,那么說明目標代碼可能有強依賴或職責過于復雜,一個被單元測試高度覆蓋的框架往往是設計精良的,符合高內聚低耦合的框架。 如果框架的時間需求不是特別緊的話,單元測試的引入可以是走通主線流程的階段就引入,越早引入框架的成熟度可能就會越高,以后重構返工的機會會越小,框架的可靠性也肯定會大幅提高。之前我有寫過一個類庫項目,并沒有寫單元測試,在項目中使用了這個類庫一段時間也沒有出現(xiàn)任何問題,后來花了一點時間為類庫寫了單元測試,出乎我意料之外的是,我的類庫提供的所有API中有超過一半是無法通過單元測試的(原以為這是一個成熟的類庫,其實包含了數(shù)十個BUG),甚至其中有一個API是在我的項目中使用的。你可能會問,為什么在使用這個API的時候沒有發(fā)生問題而在單元測試的時候發(fā)生問題了呢?原因之前提到過,我是框架的設計者,我在使用類庫提供的API的時候是知道使用的最佳實踐的,因此我在使用的時候為類庫進行了一個特別的設置,這個問題如果不是通過單元測試暴露的話,那么其它人在使用這個類庫的時候基本都會遇到一個潛在的BUG。示范項目寫一個示例項目不僅僅是為了給別人參考,而且還能夠幫助自己去完善框架,對于示例項目,最好兼顧下面幾點:是一個具有一定意義的網(wǎng)站或系統(tǒng),而不是純粹為了演示特性而演示。這是因為,很多時候只有那些真正的業(yè)務邏輯才會暴露出問題,演示特性的時候我們總是有一些定勢思維會規(guī)避很多問題?;蛘呖梢蕴峁﹥蓚€項目,一個純粹演示特性,一個是示例項目。 覆蓋盡可能多的特性或使用難點,在項目的代碼中提供一些注釋,很多開發(fā)人員不喜歡閱讀文檔,反而喜歡看一下示例項目直接上手(模仿示例項目,或直接拿示例項目中的代碼來修改)。 項目中的代碼,特別是涉及到框架使用的代碼一定要規(guī)范,原因上面也說了,作為框架的設計者你不會希望大家復制的代碼粘帖的代碼一團糟吧。 如果你的項目針對的不僅僅是Web項目,那么示例項目最好提供Web和桌面兩個版本,一來你自己容易發(fā)現(xiàn)因為環(huán)境不同帶來的使用差異,二來可以給予不同類型項目不同的最佳實踐。 完善日志和異常一個好的框架不但需要設計精良,日志和異常的處理是否到位也是非常重要的標準,這里有一些反例:日志的各種級別的使用沒有統(tǒng)一的標準,甚至是永遠只使用某個級別的日志。 幾乎沒有任何的日志,框架的運行完全是一個黑盒。 記錄的日志多且沒有實際含義,只是調試的時候用來觀察變量的內容。 異常類型只使用Exception,不使用更具體化的類型,沒有自定義類型。 異常的消息文本只寫錯誤字樣,不寫清楚具體的問題所在。 永遠只是拋出異常,讓異常上升到最外層,交給框架的使用者去處理。 用異常來控制代碼流程,或本應該在方法未達到預期效果的時候使用異常卻使用返回值。 其實個人覺得,一個框架的主邏輯代碼并不一定是最難的,最難的是對一些細節(jié)的處理,讓框架保持一套規(guī)范的統(tǒng)一的日志和異常的使用反而對框架開發(fā)者來說是一個難點,下面是針對記錄日志的一些建議:1、首先要對框架使用的日志級別有一個規(guī)范,比如定義:DEBUG:用于觀察程序的運行流程,僅在調試的時候開啟 INFO:用于告知程序運行狀態(tài)或階段的變化,可以在測試環(huán)境開啟 WARNING:用于告知程序可以自己恢復的錯誤或異常,或不影響主線流程執(zhí)行的錯誤或問題,可以在正式環(huán)境開啟 ERROR:用于告知程序無法恢復,主線流程中斷,需要開發(fā)或運維人員知曉干預的錯誤或異常,需要在正式環(huán)境開啟 2、按照上面的級別規(guī)范,在需要記錄日志的地方記錄日志,除了DEBUG級別的日志其它日志不能記錄過多,如果框架總是在運行的時候輸出幾十個WARNNING也容易讓使用者忽略真正的問題。3、日志記錄的消息需要是明確的,最好包含一些上下文信息,比如無法在xxx下找到配置文件xxx.config,框架將采用默認的配置,而不是加載配置失??!下面是一些針對使用異常的建議:框架由于配置錯誤或使用錯誤或運行錯誤,不能完成API名字所表示的功能,考慮拋出轉化后的異常,讓調用者知道發(fā)什么了什么情況,同時框架可以建立自己的錯誤處理機制 對于可以預料的錯誤,并且錯誤類型可以枚舉,考慮以返回值的形式告知調用者可以根據(jù)不同的結果來處理后續(xù)的邏輯 對于框架內部功能實現(xiàn)上遇到的調用者無能力解決的錯誤,如果錯誤可以重試或不影響返回,可以記錄警告或錯誤日志 可以為每一個模塊都陪伴自定義的異常類型,包含相關的上下文信息(比如ViewException可以包含ViewContext),這樣出現(xiàn)異??梢院芊奖阒獣允悄膫€模塊出現(xiàn)問題并且可以得到出現(xiàn)異常時的環(huán)境信息 如果異常跨了實現(xiàn)層次(比如從框架到應用),那么最好進行一下包裝轉換(比如把文件讀取失敗的提示改為加載配置文件失敗的提示),否則上層人員是不知道怎么處理這些內部問題的,內部問題需要由框架自己來處理 異常的日志中可以記錄和當前操作密切相關的參數(shù)信息,比如搜索的路徑,視圖名等等,有關方法的信息不用過多記錄,異常一般都帶有調用棧信息 如果可能的話,出現(xiàn)異常的時候可以分析一下為什么會出現(xiàn)這樣的問題,在異常信息中給一些解決問題的建議或幫助鏈接方便使用者排查問題 異常處理從壞到好的層次是,出現(xiàn)了嚴重問題的時候: 使用者什么都不知道,程序的完整性和邏輯得到破壞 使用者既不知道出現(xiàn)了什么問題也不知道怎么去解決 使用者能明確知道出現(xiàn)了什么問題,但無法去解決 使用者不但知道發(fā)生了什么,還能通過異常消息的引導快速解決問題 完善配置配置的部分可以留到框架寫的差不多了再去寫,因為這個時候已經(jīng)可以想清楚哪些配置是:需要公開出去給使用者配置的,并且配置會根據(jù)環(huán)境不同而不同 需要公開出去給使用者來配置的,配置和部署環(huán)境無關 僅僅需要在框架內供框架開發(fā)人員來配置的 無需是一個配置,只要在代碼中集中存儲這個設定即可 一般來說配置有幾種方式:通過配置文件來配置,比如XML文件、JSON文件或property文件 通過注解或特性(Annotation/Attribute)方式(對類、方法、參數(shù))進行配置 通過代碼方式進行配置(比如單獨的配置類,或實現(xiàn)配置類或調用框架的配置API) 很多框架提供了多種配置方式,比如Spring MVC同時支持上面三種方式的配置,個人覺得對配置,我們還是應該區(qū)別對待,而不是無腦把所有的配置項都同時以上面三種方式提供配置,我們要考慮高內聚和低耦合原則,對于Web框架來說,高內聚需要考慮的比低耦合更多,我的建議是對不同的配置項提供不同的配置方式:如果配置項目是需要讓使用者來配置的,特別是和環(huán)境相關的,那么最好使用配置方式來配置,比如開放的端口、內存、線程數(shù)配置,不過要注意: 所有配置項目需要有默認值,如果找不到配置使用默認值,如果配置不合理使用默認值(你不會希望使用你框架的人把框架內部的線程池的min設置為999999,或定時器的間隔設置為0毫秒吧?) 框架啟動的時候檢測所有配置,如果不合理給予提示,大多人只會在啟動的時候看一下日志,使用的時候根本就不管 不知道大家對于配置文件的格式傾向于XML呢還是JSON呢還是鍵值對呢? 對于所有僅在開發(fā)時進行的配置,都盡量不要去使用配置文件,并且讓配置盡量和它所配置的對象靠在一起: 如果是對框架整體性進行的設置擴展類型的配置,那就可以提供代碼方式進行配置,比如我們要實現(xiàn)的MVC框架的各種IRoute、IViewEngine等,最好可以提供IConfig接口讓開發(fā)人員可以去實現(xiàn)接口,這樣他們可以知道有哪些東西可以配置,代碼就是文檔 如果是那種對模型、Action進行的配置,比如模型的驗證規(guī)則、Filter等一律采用注解的方式進行配置 有的人說使用配置文件進行配置非常靈活,使用代碼方式和注解方式來配置不靈活而且可能有侵入性。我覺得還是要權衡對待,我的建議是不要把太多框架內在的東西放在配置文件中,增加使用者的難度(而且很多時候,大多數(shù)人只是復制配置為了完成配置而配置,并不是為了真正的靈活性而去使用配置文件來配置你的框架,看看網(wǎng)上這么所SSH配置文件的抄來抄去就知道了)。 最后,我建議很多太內部的東西對于輕量級的應用型框架可以不去提供任何配置選項,只需要在某個常量文件中定義即可,讓真正有需求進行二次開發(fā)的開發(fā)人員去修改,對于一個框架如果一下子暴露上百個高級配置項給使用者,他們會暈眩的。 提供狀態(tài)服務所謂狀態(tài)服務就是反映框架內部運作狀態(tài)的服務,很多開源服務或系統(tǒng)(Nginx、Mongodb等)都提供了類似的模塊和功能,作為框架的話我覺得也有必要提供一些內部信息(主要是配置、數(shù)據(jù)統(tǒng)計以及內部資源狀態(tài))出來,這樣使用你框架的人可以在開發(fā)的時候或線上運作的時候了解框架的運作狀態(tài),我們舉兩個例子,對于一個我們之前提到的Web MVC框架來說,可以提供這些信息:路由配置 視圖引擎配置 過濾器配置 對于一個Socket框架來說,有一些不同,Socket框架是有狀態(tài)的,其狀態(tài)服務提供的信息除了當前生效的配置信息之外,更多的是反映當前框架內部一些資源的狀態(tài)以及統(tǒng)計數(shù)據(jù):各種配置(池配置、隊列配置、集群配置) Socket相關的統(tǒng)計數(shù)據(jù)(總打開、總關閉、每秒收發(fā)數(shù)據(jù)、總收發(fā)數(shù)據(jù)、當前打開等等) 各種池的當前狀態(tài) 各種隊列的當前狀態(tài) 狀態(tài)服務可以以下面幾種形式來提供:代碼方式,比如如果開發(fā)人員實現(xiàn)了IXXXStateAware接口的話,就可以為它的實現(xiàn)類來推送一些信息,也可以直接在框架中設立一個StateCenter來公開框架所有的狀態(tài)信息 自動日志方式,比如如果在配置中開啟了stateLoggingInterval=60s的選項,我們的框架就會自動一分鐘一次輸出日志,顯示框架內部的狀態(tài) 接口方式,比如開放一個Restful的接口或額外監(jiān)聽一個端口來提供狀態(tài)服務,方便使用者可以拿原始的數(shù)據(jù)和其它監(jiān)控平臺進行整合 內部外部工具方式 比如我們可以直接為框架提供一個專門的頁面(/_route)來呈現(xiàn)路由的配置(甚至我們可以在這個頁面上讓開發(fā)人員可以直接輸入地址來測試路由的匹配情況,狀態(tài)服務不一定只能看),這樣在開發(fā)和測試的時候可以更方便調試 我們也可以為框架提供一個專有工具來查看框架的狀態(tài)信息(當然,這個工具其實可能就是連接框架的某個網(wǎng)絡服務來獲取數(shù)據(jù)),這樣即使框架在多個機器中使用,我們可能也只有一個監(jiān)控工具即可 如果沒有狀態(tài)服務,那么在運行的時候框架就是一個黑盒,反之如果狀態(tài)服務足夠詳細的話,可以方便我們排查一些功能或性能問題。不過要注意的一點是,狀體服務可能會降低框架的性能,我們可能需要對狀態(tài)服務也進行
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 租地正規(guī)合同范本
- 2025至2030年中國灶前燃氣控制銅球閥數(shù)據(jù)監(jiān)測研究報告
- 泥工砌墻合同范本
- 2025年02月重慶永川區(qū)雙石鎮(zhèn)本土人才公開招聘1人筆試歷年典型考題(歷年真題考點)解題思路附帶答案詳解
- 2025至2030年中國柿子數(shù)據(jù)監(jiān)測研究報告
- 公積金繳納協(xié)議
- 苗木補償合同范本
- 駐馬店消防合同范本
- 學校課程共同體保證金合同
- 簡單醫(yī)病合同范本
- 年產(chǎn)60萬噸摻混肥項目可行性研究報告申請立項
- 2025年江蘇省中職《英語》學業(yè)水平考試高頻必練考試題庫400題(含答案)
- 2025年濟寧職業(yè)技術學院高職單招語文2018-2024歷年參考題庫頻考點含答案解析
- 高三一?!吧媾c強弱關系思辨”審題立意及范文
- 2025年湖南工程職業(yè)技術學院高職單招職業(yè)適應性測試近5年??及鎱⒖碱}庫含答案解析
- 2025年茂名市高三年級第一次綜合測試(一模)物理試卷(含答案)
- 《電子商務法律法規(guī)》電子商務專業(yè)全套教學課件
- 《產(chǎn)后出血預防與處理指南(2023)》解讀課件
- 全套教學課件《工程倫理學》
- 江蘇省建筑與裝飾工程計價定額(2014)電子表格版
- 清華大學考生自述
評論
0/150
提交評論