Spring系列之手寫一個SpringMVC

目錄

引言

在前面的幾個章節中咱們已經簡單的完成了一個簡易版的spring,已經包括容器,依賴注入,AOP和配置文件解析等功能。這一節咱們來實現一個本身的springMvc。html

關於MVC/SpringMVC

springMvc是一個基於mvc模式的web框架,SpringMVC框架是一種提供了MVC(模型 - 視圖 - 控制器)架構和用於開發靈活和鬆散耦合的Web應用程序的組件。git

MVC模式使得應用程序的不一樣部分分離,同時提供這些元素之間的鬆散耦合。github

  • 模型(Model)封裝了應用程序數據,一般是指普通的bean。
  • 視圖(View)負責渲染模型數據,通常來講它生成客戶端瀏覽器能夠解釋HTML輸出。
  • 控制器(Controller)負責處理用戶請求並獲取請求結果,將其傳遞給視圖進行渲染。

SpringMVC

SpringMVC處理請求流程

首先咱們來了解一下SpringMVC在處理http請求的整個流程中都在作些什麼事。web

SpringMVC請求處理流程

//圖片來源於網絡spring

從上圖中咱們能夠總結springmvc的處理流程:json

  1. 客戶端發送請求,被web容器(tomcat等)攔截到,web容器將請求交給DispatcherServlet。
  2. DispatcherServlet收到請求後將請求交給HandlerMapping(處理器映射器)查找該請求對應的Handler(處理器)。實際上這個過程在圖上是分爲兩個的,這是由於一個請求url可能會有多個請求處理器,好比GET請求,POST請求等就是不一樣的處理器對象來處理的,因此須要一個HandlerAdapter(處理器適配器)來根據不一樣的請求參數來獲取對應的處理器對象。
  3. 獲取到請求的處理器對象後,執行處理器的請求處理流程。這裏的處理流程通常是值咱們在開發中定義的業務流程。
  4. 處理流程執行完畢後將返回的結果包裝爲一個ModelAndView對象返回給DispatcherServlet。
  5. DispatcherServlet經過ViewResolver(視圖解析器)將ModelAndView解析爲View。
  6. 經過View渲染頁面,響應給用戶。

上面就是一個http請求從開始帶完成響應中由SpringMVC完成的流程,咱們的SpringMVC沒有實際的那麼複雜,不過相應的功能都會進行實現。後端

SpringMVC分析

咱們知道SpringMVC是實現了MVC模式的一個web框架,因此確定有ModelViewController三種角色。從上圖中咱們還能夠看到一個十分重要的類:DispatcherServlet。接下來咱們單獨來分析。設計模式

Controller

控制器(Controller)負責處理用戶請求並獲取請求結果,將其傳遞給視圖進行渲染。瀏覽器

在SpringMVC中Controller負責來處理由DispatcherServlet分發來的請求,並將進過業務處理後的請求結果包裝成一個Model提供給View使用。在SpringMVC中將請求映射到對應的Controller上是給咱們提供了兩種不一樣的方法:tomcat

  1. 實例級別的映射,每個請求都有一個對應的類實例來處理,相似於Struts2。這種方法實際不多使用。要實現這種類型須要實現一個Controller接口。
  2. 方法級別的映射,請求映射到bean的方法上,這樣每個Controller能夠對應多個請求,同時也能更易於保證併發請求的線程安全。
實例級別的映射

考慮如何實現實例級別的映射?

在實例級別映射中每個請求對應一個不一樣的類,即一個URL<==>一個Class,這樣咱們能夠將beanName和請求地址進行對應。

同時咱們框架須要提供一個請求處理的入口供使用者實現業務代碼。這裏咱們定義一個Controller接口,接口中提供一個處理方法。

Controller

接口中包含一個handlerRequest的處理方法,全部實例級別的Controller都須要Controller接口。在handlerRequest方法中完成業務邏輯。由於目前咱們還沒法判斷業務邏輯完成後須要返回那種類型的值,因此用Object代替。

這裏要肯定方法應該返回什麼,咱們首先得明白返回值用來幹嗎的?這個返回的值包含了業務處理的結果。而且返回給頁面用於頁面渲染。因此確定是持有一個結果值和須要返回的具體的頁面。考慮到返回的值不只僅是業務處理的結果,可能用戶須要設置一些其餘的值給頁面,咱們定義一個map類型來接收。

ModelAndView

添加hasView()方法是由於咱們返回的並非必需要有頁面的信息,好比返回json值。

view的工做很是簡單,就是講咱們返回的值響應給瀏覽器。因此view的接口是這樣的:

View

方法級別的映射

用過SpringMVC的都很清楚,上面那種實例級別映射的方式基本上都不會使用。實例級別的映射一旦項目中的請求多了將會致使項目中的類特別的多。咱們平時用的多的是方法級別的映射。

咱們這裏方法級別的映射不須要實現Controller接口,這裏咱們仿造SpringMVC使用@Controller來表示一個控制器,使用@RequestMapping來表示不一樣的請求。

annotation

RequestMethod是指http請求類型,包括GEt,POST,PUT等類型,一個枚舉類型。

方法映射的處理除了映射到方法上外其餘和實例映射相似。

請求分發

客戶端發送請求,後端接收到請求後,須要將請求分發到對應的處理器上,從宏觀角度說就是請求交給DispatcherServlet,而後由DispatcherServlet分發給不一樣的處理器。咱們很明顯須要知道請求是如何分發處處理器上的。

請求分發

不一樣類型的映射對應的請求處理器確定是不同的,好比對於實例映射是經過實現Controller接口,處理也是關於接口的,而方法映射是經過註解實現。即不一樣的方式,映射方式不一樣,請求處理器也不同。

簡單的方法就是分別定義處理不一樣類型的處理器,而後在DispatcherServlet中經過判斷肯定具體的處理器。這樣處理思路很簡單,可是問題在於假設咱們要再添加一種處理的方式,那麼就須要改變原有的代碼,很明顯的違反了開閉原則,也會給代碼維護帶來麻煩。因此咱們但願有一種DispatcherServlet能避開這種改變的方式。

咱們這裏須要一個可以根據傳遞進來的不一樣的請求來調用不一樣的處理器,並且可以簡單的進行擴展而不改動原代碼。這裏確定就須要用到設計模式了,那麼使用什麼設計模式呢? 策略模式

HandlerMapping

咱們定義一個用於請求處理器映射的接口,該接口的做用就是獲取一個請求具體的請求處理器,不一樣方式的處理方式分別實現該接口。

handlerMapping

BeanNameUrlHandlerMapping就是實例映射的處理器映射器,RequestMappingHandlerMapping就是方法映射的處理器。而如何將請求和HandlerMapping對應起來呢?咱們能想到的就是url了,這裏咱們定義一個urlMaps用來存儲url和處理器映射器的對應關係。

HandlerAdapter

如今咱們有了HandlerMapping後就能夠獲取到某一種類型的處理方式的處理對象。可是實際上咱們仍是沒有獲取到實際的處理器,因此咱們還須要根據請求來獲取到實際的處理器,這裏咱們定義一個HandlerAdapter來獲取實際的處理器。

handlerAdapter

handler(...)就是具體的處理方法,實際上就是執行控制器,而support主要是用於判斷是不是一個處理器對象。

這裏很明顯對於實例映射來講咱們只須要執行方法中的handlerRequest(...)方法便可,可是對於方法映射就不是那麼簡單了,不一樣的方法根據@RequestMapping表示不一樣的請求。因此咱們還須要一個類來表示不一樣的方法信息,便於請求傳遞過來後直接取用。

requestMappinginfo

類的定義中classRequestMapping表示做用在類上的@RequestMappingmethodRequestMapping表示做用在方法上的@RequestMappingmethod表示做用在類上的方法信息。match(...)方法是用來檢測當前請求與這個RequestMappingInfo是否相匹配。

掃描註冊

基本上咱們的準備工做完成了,如今咱們須要考慮如何來識別咱們的控制器和生成RequestMappingInfo的信息。

首先建立的時機確定是在項目啓動的時候就講這些信息初始化好,由於請求過來後會馬上使用到這些信息。而初始化這些信息的行爲在哪裏發生呢?個人第一反應是交給DispatcherServlet,由於直觀來說是它來使用,實際上真正的使用這些信息的事HandlerMapping的實現類,經過請求和RequestMappingInfo等信息來獲取實際的處理器,因此初始化的信息應該交給HandlerMapping的實現類。

咱們要明白的事提取帶有Controller註解的bean或者是實現類Controller接口的類確定是在bean初始化以後進行的,因此咱們須要提供一個在初始化後獲取控制器類型的接口。同時獲取已經初始化好的類那麼確定會使用到ApplicationContext。咱們如今對RequestMapping接口修改下。

image

afterPropertiesSet()方法中獲取控制器類型的bean。在咱們以前完成的代碼中只提供了經過beanName獲取bean的方法,因此這裏咱們還須要提供一種獲取全部的執行類型的方法。

public void afterPropertiesSet() {
    String[] beanNameForType = applicationContext.getBeanNameForType(Object.class);
    for(String beanName:beanNameForType){
        Class type = applicationContext.getType(beanName);
        //判斷是不是控制器類型
        if (isHandler(type)) {
            //註冊控制器的類型
            detectHandlerMethod(type);
        }
    }
}
複製代碼

DispatcherServlet

好了,到如今對於控制器的準備已經差很少了,如今咱們須要來實現DispatcherServlet了。

DispatcherServlet名字來看就知道這是一個Servlet,咱們的框架是基於Servlet來完成的,SpringMVC框架自己也是基於Servlet的。固然也能夠根據其餘技術來實現,好比基於Filter的Struts2。

咱們先來捋一下DispatcherServlet須要完成的任務吧:

  1. 建立ApplicationContext容器對象
  2. 從容器中獲取HandlerMappingHandlerAdapter對象。
  3. 分發請求
  4. view轉發

熟悉Servlet的都應該知道Servlet提供了一系列生命週期的API,上面的這些事情都須要在Servlet生命週期的不一樣階段來完成。

  • 容器對象的初始化和獲取HandlerMappingHandlerAdapter對象在init(...)完成。
  • 請求分發由service(HttpServletRequest req, HttpServletResponse res)完成。
  • destroy()完成關閉後的處理。

DispatcherServlet

View

在以前咱們定義控制器的時候有說到由控制器來返回一個ModelAndView對象,該對象肯定具體返回哪個頁面和處理結果的數據。這樣不只須要提供ModelAndView對象,同時還須要提供一個View的對象,如今咱們但願這個過程可以儘可能的簡單,使用者能夠僅僅提供一個視圖的名稱,而後框架就能夠自動的找到對應的頁面而後進行渲染。

咱們如今須要從新定義ModelAndViewView類。

ModelAndView

這樣用戶能夠傳遞一個名稱過來,而後由HandlerAdapter根據傳遞的handler來生成ModelAndView,同時也能夠自定義ModelAndView對象。

ViewResolver

當咱們前面的準備工做都作好了並不表明就已經能夠完成了,由於對於不一樣的視圖可能會有不一樣的操做,好比直接轉發給一個URL,可能還會重定向到另外一個URL,或者直接就是返回json串的。因此咱們還須要定義不一樣的視圖解析器來將ModelAndView解析成相應的View。

ViewResolver

這裏定義了一個解析JSP視圖的解析器,同理也能夠定義其餘的處理器。

定義好了視圖解析器後咱們還須要定義幾個用於處理不一樣狀況的視圖類。

View

這裏的View類型還能夠根據不一樣的需求添加其餘類型的處理器,好比freemarker、JSTL等。對於json處理咱們還須要像SpringMVC那樣來定義一個@ResponseBody的註解。當使用了該註解的時候咱們就將返回值轉換爲JSON串而後直接經過response返回給客戶端便可。

小結

SpringMVC到這裏就基本結束了,總得來講這一篇的內容稍微比較麻煩。主要是涉及到的內容較多,再加上這段時間比較忙,平時就下班後抽時間整理,目前也只是將思路基本捋完。代碼也只是整理了一個框架,內容尚未進行填充。因此文章中可能會有一些錯誤的地方,你們若是發現了能夠指出來。後面有時間會將代碼實現的。代碼都在這裏:Spring

總結

Spring的手寫框架差很少就是這些了,寫這一系列文章是爲了鞏固個人Spring學習的成果,固然若是能幫助你們學習Spring固然是更好了。Spring的內容十分的繁雜,涉及的內容多,這一系列文章只能幫助你們對Spring的原理有一個最初的瞭解,在看Spring源碼的過程當中不至於徹底就是一頭霧水。由於技術水平的緣由,文章中可能還存在着一些錯誤,歡迎你們指出。

相關文章
相關標籤/搜索