手寫SpringMvc

一也榄、簡介

空閑之余手撕一把springMVC,以加深對spring的理解司志,盡可能寫的全面甜紫,源碼中注釋也會很詳細。話不多說開搞骂远!

二囚霸、項目搭建

在IDEA上用MAVEN創(chuàng)建一個webApp項目:

項目構(gòu)建.png

原來的springMVC中,最重要的一個類就是DispatchServlet即前端請求控制器吧史,我們自定義自己的DispatchServlet邮辽,繼承HttpServlet唠雕。
因為要繼承HttpServlet,利用pom引入servlet-api的jar包:
...
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>3.0-alpha-1</version>
</dependency>

...
繼承HttpServelet新建DispatchServlet類吨述,重寫doGet岩睁、doPost、和init方法揣云。在init方法中實現(xiàn)包掃描捕儒、IOC容器初始化等一系列操作。這里面操作會在tomcat加載項目后初始化完成邓夕。
DispatchServlet.png

同時在web.xml配置DispatchServlet:
...
<servlet>
<servlet-name>DispatchServlet</servlet-name>
<servlet-class>com.zjx.myspringmvc.servelet.DispatchServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatchServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

...

三刘莹、流程解讀

3.1.自定義注解

常用的@Controller、@Service焚刚、@RequestMapping点弯、@RequestParam、@Qualifier等矿咕。我們對應(yīng)定義自己的注解@MyController抢肛、@MyService、@MyRequestMapping碳柱、@MyRequestParam捡絮、@MyQualifier
自定義注解首先了解四種元注解: @Retention @Target @Document @Inherited莲镣。

@Retention:注解的保留位置         
@Retention(RetentionPolicy.SOURCE)//注解僅存在于源碼中福稳,在class字節(jié)碼文件中不包含
@Retention(RetentionPolicy.CLASS)//默認的保留策略,注解會在class字節(jié)碼文件中存在瑞侮,但運行時無法獲得
@Retention(RetentionPolicy.RUNTIME)//注解會在class字節(jié)碼文件中存在的圆,在運行時可以通過反射獲取到

@Target:注解的作用目標
@Target(ElementType.TYPE)//接口、類区岗、枚舉略板、注解
@Target(ElementType.FIELD)//字段、枚舉的常量
@Target(ElementType.METHOD)//方法

*@Target(ElementType.PARAMETER)//方法參數(shù)*
*@Target(ElementType.CONSTRUCTOR)//構(gòu)造函數(shù)*
*@Target(ElementType.LOCAL_VARIABLE)//局部變量*
*@Target(ElementType.ANNOTATION_TYPE)//注解*
*@Target(ElementType.PACKAGE)//包*  

@Document:說明該注解將被包含在javadoc中*

@Inherited:說明子類可以繼承父類中的該注解*

我們自定義的注解用到前面三個慈缔,其中@Retention(RetentionPolicy.RUNTIME)叮称、@Document是相同的。使用@Target不同的注解類各不相同藐鹤。具體定義代碼如下:
...
@Target({ElementType.TYPE})//可以用在接口瓤檐、類、枚舉
@Retention(RetentionPolicy.RUNTIME)//可以通過反射得到
@Documented//該注解將被包含在javadoc中
public @interface MyController {
String value() default " ";//
}

...
@Target({ElementType.FIELD})//用于字段娱节、枚舉的常量
@Retention(RetentionPolicy.RUNTIME)//可以通過反射得到
@Documented//該注解將被包含在javadoc中
public @interface MyQualifier {
String value() default " ";
}

...
@Target({ElementType.METHOD,ElementType.TYPE})//用于方法,類
@Retention(RetentionPolicy.RUNTIME)//可以通過反射得到
@Documented//該注解將被包含在javadoc中
public @interface MyRequestMapping {
String value() default " ";
}

...
@Target({ElementType.PARAMETER})//用于方法參數(shù)
@Retention(RetentionPolicy.RUNTIME)//可以通過反射得到
@Documented//該注解將被包含在javadoc中
public @interface MyRequestParam {
String value() default " ";
}

...
@Target({ElementType.TYPE})//可以用在接口挠蛉、類、枚舉
@Retention(RetentionPolicy.RUNTIME)//可以通過反射得到
@Documented//該注解將被包含在javadoc中
public @interface MyService {
String value() default " ";
}

...

3.2.init方法

其實這里就是整個項目的核心肄满,首先重寫init方法谴古。在這個方法里按序?qū)崿F(xiàn)包掃描质涛、實例化(即初始化IOC容器)、依賴注入以及建立url與方法的映射關(guān)系掰担,init方法如下汇陆。
...
@Override
public void init(ServletConfig config) throws ServletException {
//1、掃描哪些需要被實例化 class(包及包以下的class)
scanPackage("com.zjx");
//2带饱、掃描出來的類 進實例化
instance();
//依賴注入 這里項目簡單 只有service層注入controller 后續(xù)讀者可以自實現(xiàn)dao->service
iocInject();
// 4毡代、建立一個path與method的映射關(guān)系
HandlerMapping();
}

...
下面分別說明一下每個方法的流程,具體實現(xiàn)代碼就不貼出來了勺疼,因為這幾個方法在實現(xiàn)的過程中有許多相似的地方教寂,都是基于反射得到實例,再根據(jù)不同的注解進行處理执庐,具體可以參考最后的源碼酪耕。

  • scanPackage("com.zjx")
    根據(jù)初始傳入的包名進行掃描,這里會用到遞歸耕肩,掃描已經(jīng)編譯好的項目下所有的類因妇,最后把所有的類名存放到一個List集合中问潭。

  • instance()
    遍歷掃描到的class文件猿诸,利用反射Class.forName()方法得到class對象,這里注意上一步得到的類名會有.class后綴狡忙,需要去掉梳虽。得到class類后先判斷是否有注解,這里為了簡單我們只關(guān)注了@MyService 灾茁、@MyController類注解窜觉,如果有該類注解,則繼續(xù)利用反射方法創(chuàng)建實例,該實例作為值北专,同時得到對應(yīng)注解上的參數(shù)值作為key(springMVC是將類名首字母小寫作為key禀挫,這里我們簡單起見),保存到一個Map容器中拓颓。
    關(guān)鍵代碼如下:

    image.png

  • iocInject()
    進行依賴注入语婴,上一步中IOC容器中已經(jīng)存放所有我們所關(guān)心的實例。遍歷容器驶睦,首先得到當(dāng)前遍歷到的類的實例和類class,根據(jù)類class來獲取注解信息砰左,如果當(dāng)前類有@MyController注解,先獲取類里面的所有屬性场航,遍歷所有屬性缠导,判斷類屬性上是否有自動裝配(依賴注入)的注解@Autowired或Qualifier,如果有獲得該注解的value溉痢,即IOC容器中的key僻造,根據(jù)該key獲得對應(yīng)實例憋他,最后給該屬性設(shè)值(即注入)。注意點:類中屬性一般是private髓削,所以設(shè)值前需要field.setAccessible(true)獲得許可举瑰,不然無法設(shè)值。
    關(guān)鍵代碼如下:

    image.png

  • HandlerMapping
    請求路徑url與方法的映射關(guān)系,主要是獲得Controller上注解的值和方法上注解的值。同樣還是遍歷IOC容器郊愧,得到類class背苦,判斷是否有@MyController注解,如果有得到注解上的值拟逮,同時獲取該類下所有的方法,遍歷判斷哪些方法有@MyRequestMapping注解,得到該注解上的值坎怪,和@MyController注解的值拼接成了請求路徑url,最后將拼接成的url作為key廓握,方法作為值存放到一個Map中搅窿。
    關(guān)鍵代碼:

    image.png

3.3.處理請求

DispatchServlet處理請求的兩個方法doGet、doPost隙券,這里我們只需實現(xiàn)doPost男应,doGet直接在方法里面調(diào)用doPost方法。
處理請求的實現(xiàn)的基本流程是:

  • 獲取到請求路徑
  • 根據(jù)請求路徑獲得對應(yīng)要執(zhí)行的方法(請求路徑和方法的映射我們在init方法中已經(jīng)得到)
  • 取得控制類(controller)的實例
  • 獲得方法執(zhí)行的參數(shù)值
  • 調(diào)用方法的invoke娱仔,方法執(zhí)行完成

這里最重要的一步就是解析執(zhí)行方法上的參數(shù),因為參數(shù)類型可能很多牲迫,直接if..else依次判斷的話耐朴,會很繁瑣,且代碼不夠優(yōu)雅盹憎。這里我們采用策略模式筛峭,每一種類型的參數(shù),都對應(yīng)一種解析器陪每,然后通過處理器執(zhí)行得到我們想要測參數(shù)影晓。關(guān)于什么是策略模式,可以參考:策略模式奶稠。
這里我們定義了三種參數(shù)類型的解析器類:HttpServletRequestParamResolver俯艰、HttpServletResponsetParamResolver、 MyRequestParamResolver锌订,由類名可以知道對應(yīng)那種參數(shù)類型竹握,這里不再贅述。這三個處理器都實現(xiàn)同一個解析器接口ParamResolver辆飘。解析器中都實現(xiàn)了兩個方法:

  • boolean support(Class<?> type, int paramIndex, Method method)
    該方法是判斷當(dāng)前傳進來的方法參數(shù)是否是該解析器對應(yīng)的類型啦辐。
  • Object paramResolver(HttpServletRequest request, HttpServletResponse response, Class<?> type, int paramIndex, Method method)
    返回對應(yīng)方法參數(shù)的值谓传。

下面具體介紹一下這三個解析器類:

  • HttpServletRequestParamResolver
    判斷參數(shù)是否是HttpServletRequest類型,就看是否實現(xiàn)了ServletRequest接口芹关。paramResolver方法返回的還是原來的HttpServletRequest续挟。
    關(guān)鍵代碼:
    image.png
  • HttpServletResponsetParamResolver
    和上面的一樣,判斷參數(shù)是否是HttpServletResponse類型,看是否實現(xiàn)了ServletResponse接口侥衬,也是原參數(shù)返回诗祸。
    關(guān)鍵代碼:
    image.png
  • MyRequestParamResolver
    這里判斷和上面就不同了,因為這是加在參數(shù)上的@MyRequestParam注解轴总,可能有多個直颅,所以每次先獲得方法注解的參數(shù)類型,由方法method.getParameterAnnotations()獲得怀樟,該方法獲得的是一個二維數(shù)組功偿,再根據(jù)傳進來的參數(shù)下標,可以取得當(dāng)前參數(shù)的注解類型往堡,判斷是否是@MyRequestParam的注解類型械荷。是該注解,取出注解的值虑灰,執(zhí)行request.getParameter(value)可以獲得對應(yīng)參數(shù)的值吨瞎。
    關(guān)鍵代碼:
    image.png

    image.png

    最后定義一個處理器類,來執(zhí)行這些策略瘩缆,返回方法執(zhí)行的參數(shù)值數(shù)組关拒。流程如下:
  • 拿到當(dāng)前待執(zhí)行的方法有哪些參數(shù)
  • 拿到所有的解析器類
  • 遍歷所有參數(shù)應(yīng)用對應(yīng)解析器,得到參數(shù)值庸娱。
  • 返回最終結(jié)果數(shù)組
    關(guān)鍵代碼:


    image.png

    image.png

四、總結(jié)和一些坑

具體的流程就是上面所寫的谐算,關(guān)鍵代碼也都貼出來了熟尉,另外還有一些坑和不完善的地方做一下說明:

  • 瀏覽器會發(fā)起這么一個請求:/favicon.ico,瀏覽器會請求網(wǎng)站根目錄的這個圖標洲脂,如果網(wǎng)站根目錄也沒有這圖標會產(chǎn)生404斤儿,因為我們沒有對應(yīng)的controller類來進行處理,這里遇到這個請求就直接return了恐锦。
  • 目前只參數(shù)類型只能是String往果,因為通過request.getParameter(value)獲得的就是String,還沒有做到再根據(jù)參數(shù)類型強轉(zhuǎn)一铅。
    這只是小小的實現(xiàn)了一把陕贮,很多細節(jié)還不完善,希望大家多多指正潘飘。
    附上源碼:mySpringMvc
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肮之,一起剝皮案震驚了整個濱河市掉缺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌戈擒,老刑警劉巖眶明,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異筐高,居然都是意外死亡搜囱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門柑土,熙熙樓的掌柜王于貴愁眉苦臉地迎上來犬辰,“玉大人,你說我怎么就攤上這事冰单』戏欤” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵诫欠,是天一觀的道長涵卵。 經(jīng)常有香客問我,道長荒叼,這世上最難降的妖魔是什么轿偎? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮被廓,結(jié)果婚禮上坏晦,老公的妹妹穿的比我還像新娘。我一直安慰自己嫁乘,他們只是感情好昆婿,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蜓斧,像睡著了一般仓蛆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挎春,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天看疙,我揣著相機與錄音,去河邊找鬼直奋。 笑死能庆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的脚线。 我是一名探鬼主播搁胆,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了丰涉?” 一聲冷哼從身側(cè)響起拓巧,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎一死,沒想到半個月后肛度,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡投慈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年承耿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伪煤。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡加袋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出抱既,到底是詐尸還是另有隱情职烧,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布防泵,位于F島的核電站蚀之,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捷泞。R本人自食惡果不足惜足删,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锁右。 院中可真熱鬧失受,春花似錦、人聲如沸咏瑟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽响蕴。三九已至谆焊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浦夷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工辜王, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留劈狐,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓呐馆,卻偏偏與公主長得像肥缔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子汹来,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容