SpringMVC是怎麼工做的,SpringMVC的工做原理

SpringWeb MVC 是怎麼工做的,SpringMVC的原理,SpringMVC源碼 分析。java

介紹

SpringWeb MVC是Spring Framework中的一部分,當咱們須要使用spring框架建立web應用的時候就須要引入springweb mvc。對於程序員來講,咱們只須要增長@Controller ,@RequestMapping註解而後,瀏覽器中的請求就會到達springweb應用。咱們只須要在 controller中編寫相關邏輯便可。然而,請求是在哪裏接收的?@Controller ,@RequestMapping註解作了什麼?本文咱們來探討一下。程序員

從一個項目開始

本文假定你已經能熟練的使用springmvc。爲了展開後續的討論,假設咱們新建了一個spring-mvc-demo的項目。並由此項目來展開討論。項目中有一個控制器,代碼以下:web

@Controller
@RequestMapping("/app")
public class AppController {
  @RequestMapping(method=RequestMethod.GET,value="/hello")
    public @ResponseBody String hello(HttpServletRequest request,String name) {
    return "Hello,"+name;
  }
}

控制器寫好以後,咱們將項目打車war包,放入tomcat容器中,並使用8080端口啓動tomcat,運行項目,而後在瀏覽器中輸入http://localhost:8080/app/hello?name=world.spring

咱們在瀏覽器中能夠看到:Hello,world的輸出。編程

咱們先記住這個例子,下面咱們帶着一些疑問繼續看,這個請求是怎麼被接收到的?請求是怎麼交給AppController處理的?api

Servlet是Java Web應用的基石

當你在瀏覽器中輸入 http://loalhost:8080/ ,按下enter建,而後請求命中了服務器,這是怎麼發生的?又如何從這個請求中獲得瀏覽器中可見的頁面?
本例中,咱們給出的是一個簡單的spring-mvc應用,並放入了tomcat中(springboot 內嵌tomcat啓動其實也是同樣的)。 Tomcat 是一個servlet容器,這點我想每一個Java程序員都十分清楚,咱們在沒有使用spring-mvc以前,就是使用servlet+jsp來開發web應用。瀏覽器

因爲Tomcat是一個web容器,每個發送給Tomcat服務器的HTTP請求天然會被一個Java Servlet處理。因此,SpringMvc 一定有一個servlet,SpringWeb應用的入口一定是一個Servlet,這應該不難想到。spring-mvc

簡單來講,Servlet是任何Java Web應用的核心組件(除非你不用servlet規範,好比你使用netty)。Servlet它是低層次的,而且不會像MVC那樣強加於特定的編程模式。它只是可讓你寫一個偶一個Servlet,一個HTTP Servlet能夠接受一個HTTP請求,而後處理它,並返回一個響應。tomcat

而springmvc 就是使用了一個大的servlet,下面咱們就來講這個大的servlet。

DispatcherServlet是Spring MVC的核心

上面咱們已經提到Servlet 是Java web應用的基石Spring應用入口一定是一個Servlet,這個Servlet 其實就是DispatcherServlet

做爲WEB應用的開發人員,咱們真正想作的是抽象出如下繁瑣和模板化的任務,並專一於有用的業務邏輯:

  • 映射一個HTTP請求到某個處理方法。

  • 將HTTP請求數據,和頭信息轉換成數據對象(DTO / domain object)。

  • 模型 - 視圖 - 控制器 之間的交互。

  • 從DTO,域對象等生成響應

Spring DispatcherServlet提供了這些。它是Spring Web MVC框架的核心, 這個核心組件接收全部請求到您的應用程序。
DispatcherServlet具備很強的可擴展性。 例如,它容許您插入不一樣的現有或新適配器以執行大量任務:

  • 將請求映射到應該處理它的類或方法(HandlerMapping接口的實現)
  • 使用特定模式處理請求,例如常規servlet,更復雜的MVC工做流或者POJO bean中的方法(HandlerAdapter接口的實現)
  • 經過名稱解析視圖,容許您使用不一樣的模板引擎,XML,XSLT或任何其餘視圖技術(ViewResolver接口的實現)
  • 經過使用默認的Apache Commons文件上傳實現或編寫本身的MultipartResolver來解析multipart請求
  • 使用任何LocaleResolver實現解決語言環境,包括Cookie,會話,Accept HTTP標頭或用於肯定用戶指望的語言環境的任何其餘方式

處理HTTP請求

首先,讓咱們來追蹤一個簡單的HTTP請求到達controller中的方法,而後返回到 瀏覽器/客戶端的處理過程。
DispatcherServlet 有一個很長的繼承關係。它的繼承關係是這樣的:

GenericServlet <- HttpServlet <- HttpServletBean <- FreamworkServlet <- DispatcherServlet

GenericServlet

GenericServlet是Servlet規範的一部分,它並不直接關注HTTP。它定義了一個service()方法用來接收傳遞過來的請求,併產生響應。(這裏的請求和響應不是指HTTP請求)

public abstract void service(ServletRequest req, ServletResponse res) 

  throws ServletException, IOException;

注意,這裏的參數中的ServletRequest,ServletResponse並非和HTTP協議綁定的,Http有具體協議Servlet。

HttpServlet

顧名思義,HttpServlet類就是規範中定義的基於HTTP的Servlet實現。

更實際的說,HttpServlet是一個具備service()方法實現的抽象類,它經過HTTP方法類型分割請求,大體以下所示:

protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    String method = req.getMethod();
    if (method.equals(METHOD_GET)) {
        // ...
        doGet(req, resp);
    } else if (method.equals(METHOD_HEAD)) {
        // ...
        doHead(req, resp);
    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
        // ...
    }

根據請求的不一樣, get,post方法會分別被不一樣方法處理。

HttpServletBean

上面咱們展現過 DispatcherServlet的繼承關係,在這個繼承鏈中,HttpServletBean是進入spring的第一個層次。從HttpServletBean開始往下的幾個servlet都是spring中的類。HttpServletBean 就是一個servlet,它繼承自HttpServlet,就像是咱們在使用servlet+jsp開發時候定義的servlet同樣。

根據servlet的生命週期咱們知道,servlet會被容器初始化,初始化時候,其init()方法會被調用。在springmvc框架中 HttpServletBean使用從web.xml或WebApplicationInitializer收到的servlet init-param值來注入bean的屬性。
在請求應用程序的狀況下,爲這些特定的HTTP請求調用doGet(),doPost()等方法。

FrameworkServlet

FrameworkServlet將Servlet功能與Web應用程序上下文集成,實現ApplicationContextAware接口。但它也可以自行建立Web應用程序上下文。
正如上面所說,FrameworkServlet的超類HttpServletBean將init-param注入爲bean屬性。因此,若是servlet的contextClass init-param中提供了context類的名字,那麼這個context類的實例將會被建立,用做應用的context。不然,將會使用XmlWebApplicationContext做爲默認的。

DispatcherServlet: 統一請求處理

有了上面鋪墊,咱們這裏能夠開始關鍵的內容,即DispatcherServlet統一請求處理。在Springmvc的項目中,咱們一般會在web.xml配置一個servlet,以下:

<servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/spring-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

上面咱們提到,DispatcherServlet繼承關係,其父類中正兒八經的servlet類是HttpServletBean這個servlet類是應用啓動入口。其生命週期的第一階段init()方法完成初始化工做。

doService()方法設置請求信息

DispatcherServlet 初始化以後,即可以工做了。當請求到達之時,會調用其doService()方法。doService()方法的代碼以下:

@Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) 
     throws Exception {
    // 刪除一下‘非核心代碼’方便查看
        // Make framework objects available to handlers and view objects.
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());      
    try {
            doDispatch(request, response);
        }
        finally {
            if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                return;
            }
            // Restore the original attribute snapshot, in case of an include.
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }

  }

能夠看到,doService()方法先設置一些 request信息,這些信息在後續的處理過程當中會用到。設置完後,它調用doDispatch() 方法。

doDispatch()方法分發請求

doService()方法最終調用了doDispatch(),看名知意,這個方法是作分發工做的。其代碼以下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) 
       throws Exception {
        //刪除一些代碼方便閱讀
        HandlerExecutionChain mappedHandler = null;
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                // 刪除一些代碼方便閱讀
              mappedHandler = getHandler(processedRequest, false);
              HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                try {
                    // Actually invoke the handler.
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                }
                finally {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                }
                applyDefaultViewName(request, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;  // 這裏捕獲了異常TypeMismatchException
            }
            processDispatchResult(processedRequest, response, mappedHandler, mv,                                                                            dispatchException);
        }
        catch (Exception ex) {
        }
        finally {
            // 刪除一些代碼
        }
}

這個方法主要做用就是找到合適的 handler 來處理請求。handler一般是一個某個類型的對象,而且不限定特定的接口。所以spring須要找到這個handler的適配器。這個Handler一般是一個HandlerMethod實例,

爲了找到與請求匹配handler,spring須要從已註冊的HandlerMapping接口實現類裏邊去找。這個查找過程就是在上面的getHandler() 方法完成獲得的是一個HandlerExecutionChain。 這裏體現了責任鏈模式

這個getHandler() 會遍歷一個HandlerMapping的map。因爲咱們通常都使用註解形式:@Controller,@RequestMapping註解。所以這裏找到HandlerMapping實現就是RequestMappingHandlerMapping

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

getHandlerAdapter()方法找到最終的handler適配器,找到的適配器就是RequestMappingHandlerAdapter,(由於咱們使用的是@RequestMapping註解形式)。

本例中,咱們定義了AppController 的hello()方法,並用@Controller,@RequestMapping對其分別進行註解,所以這裏獲得的適配器HandlerAdapter 所適配HandlerMethod就是 AppController 的hello()方法的 。

HandlerAdapter處理請求

上面經過 肯定了HandlerAdapter以後,就要執行handle() 方法了,即上面代碼中,try語句塊裏邊的ha.handle()。handle()方法定義爲:

ModelAndView handle(HttpServletRequest request, 
                     HttpServletResponse response, 
                     Object handler) throws Exception;

這個方法有兩種處理方式:

  • 本身將數據寫到response,而後return null

  • 返回一個ModelAndView 對象,讓DispatcherServlet本身來處理後面渲染工做。

HandlerAdapter有多重類型,例如

SimpleControllerHandlerAdapter處理spring mvc 的controller實例(注意,不要把這裏的controller實例和@Controller註解POJO混淆,這裏controller 指的是org.springframework.web.servlet.mvc.Controller ),並返回ModelAndView,代碼以下:

public ModelAndView handle(HttpServletRequest request, 
  HttpServletResponse response, Object handler) throws Exception {
    return ((Controller) handler).handleRequest(request, response);
}

SimpleServletHandlerAdapter 適配的是 Servlet做爲request handler的狀況,Servlet是不知道MovelAndView的,因此,它的方法並不負責渲染頁面,所以沒有返回ModelAndView,只是返回null:

public ModelAndView handle(HttpServletRequest request, 
  HttpServletResponse response, Object handler) throws Exception {
    ((Servlet) handler).service(request, response);
    return null;
}

RequestMappingHandlerAdapter 就是咱們上面提到的,用來處理@Controller和@RequestMapping註解的handler。

渲染視圖

handle()方法調用以後, DispatcherServlet 能夠獲得一個ModelAndView,固然也多是null。對於ModelAndView不爲null的時候,DispatcherServlet 將會調用render()方法。ModelAndView中可能已經包含了一個view或者只是一個view的名字。若是controller方法指定的是一個字符串形式的視圖名字,那麼就須要進行試圖查找:

for (ViewResolver viewResolver : this.viewResolvers) {
    View view = viewResolver.resolveViewName(viewName, locale);
    if (view != null) {
        return view;
    }
}

render()方法完成以後,最終的HTML頁面會被髮送至瀏覽器端。

固然,springmvc不只能渲染出頁面,也能夠返回JSON形式或者XML形式。這種狀況controller方法通常都是由@RequestBody標註的。這種狀況就須要 HttpMessageConverter,例如渲染JSON的時候可使用Jackson包,咱們要返回的對象將由,MappingJackson2HttpMessageConverter來轉換。


到此,咱們就大概說完了springmvc的整個流程。因此,springmvc其實就是一個大的Servlet,接收請求,分發執行請求,咱們的每個controller中的方法都是一個handler,而後最終渲染視圖。

spring源碼分析400異常處理流程及解決方法
spring如何啓動的?這裏結合spring源碼描述了啓動過程

相關文章
相關標籤/搜索