Spring MVC 工做機制

本文主要講解Spring MVC 開發時應該瞭解的一些基本原理。利用Spring MVC咱們通常最多見的有兩種使用方式,一種是由view層的好比利用JSP / Velocity/Freemaker來作顯示層渲染數據,另外一種沒有view層,直接返回相似Json/Xml格式的數據的Restful使用方式。下面先具體講解下一下Spring MVC原理,而後在講解下使用RestController註解實現Restful接口的原理。前端

普通Spring MVC模式

Spring MVC 流程

DispatcherServlet

能夠說是一個servlet支撐了Spring MVC,這裏有必要細講一下DispatcherServlet。 輸入圖片說明java

從上面的類圖中能夠看出DispatcherServlet本質上是一個普通的Servlet類,所以咱們能夠把須要使用Spring Mvc的請求的類所有映射到這個Servlet中。咱們學習Servlet時都知道,Servlet經過doService來處理請求,這個doService參數是HttpServletRequest request/HttpServletResponse response這兩個,開發web項目必定很熟悉了。Servlet容器(Tomcat/Jetty)幫咱們實例化了這個兩個參數,request負責封裝請求的參數,response負責由服務器往客戶端寫數據。那麼Spring MVC 無非也是利用這個兩個參數來實現數據到服務端的讀,而後在把數據經過response寫到客戶端。好比咱們搭建Spring MVC項目時都會在web.xml中配置以下內容,其實和配置一個普通的servlet沒有區別,除了多了和Spring Context交互的部分,從上面的類繼承圖中FrameworkServlet能拿到Spring Context來作IoC相關的工做:web

<servlet>
   <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--指明配置文件的文件名,不使用默認配置文件名-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/dispatcher-servlet.xml</param-value>
           <!-- 這個主要是由其父類 FrameworkServlet 來處理,爲servlet管理Spring Context相關的工做都是由它完成的-->
        </init-param>
        <load-on-startup>1</load-on-startup><!--啓動順序-->
</servlet>
<servlet-mapping>
       <servlet-name>DispatcherServlet</servlet-name>
       <url-pattern>/</url-pattern> <!--攔截URL帶「/」的請求。-->
</servlet-mapping>

DispatcherServlet主要方法。

initStrategies(ApplicationContext context)

這個方法主要是初始化DispatcherServlet類須要的幾塊大內容,包括最主要的handlerMapping和viewResolver。spring

//該方法主要是來初始化DispatcherServlet相關的內容的。
	protected void initStrategies(ApplicationContext context) {
        //這主要是填寫上傳文件時用哪一個MultipartResolver,一般咱們會配個CommonsMultipartResolver
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
    //在容器中拿到全部的Spring Context中的Controller ,放倒handlerMapping list中。
		initHandlerMappings(context);
		initHandlerAdapters(context);
    //異常處理配置
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
    // 配置顯示層,好比咱們是用jsp 仍是velocity等等,默認用InternalResourceViewResolver,這個咱們都均可以在配置文件裏靈活的更改。
		initViewResolvers(context);
		initFlashMapManager(context);
	}

void doService(HttpServletRequest request, HttpServletResponse response)

該方法主要是給request添加一些屬性,好比把applicationContext添加到其屬性上,使其在後面處理過程,更容易拿到applicationContext, 也會添加LOCALE_RESOLVER_ATTRIBUTE,THEME_RESOLVER_ATTRIBUTE等等。這個方法最重要的一句代碼是 doDispatch(request, response),起到具體分發請求,處理請求的做用,下個方法具體分析。緩存

//.....省落.......
// 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());
// .....省落.......
doDispatch(request, response);
// .....省落.......

doDispatch(request, response)

這個方法是Dispatcher最主要的方法:1 負責查找請求和handler(就是咱們寫的Controller)的映射,具體到該請求所對應的具體的Controller方法,handler對應的Spring beanFactory,該請求對應的攔截器等; 2 處理文件上傳,及加入管理異步請求的邏輯。服務器

doDispatch關鍵代碼,下面是一個基本的請求處理流程app

// Determine handler for the current request. 拿到對應的handler
mappedHandler = getHandler(processedRequest);

// 找到handler 對應的適配器,適配器的做用是起到對handler調用時作一些額外的事情
/*
默認的 SimpleControllerHandlerAdapter 是什麼事情都沒有作,直接觸發handler方法,
這個HandlerAdapter接口設計主要是爲了一些定製MVC workflow。This interface is not intended for application developers. It is available to handlers who want to develop their own web workflow.
*/
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
//是否走緩存
if (isGet || "HEAD".equals(method)) {
	long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
       if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
			return;
	}
}
// 觸發攔截器的 preHandle方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
}

// Actually invoke the handler.觸發具體的controller邏輯,生成modelAndView對象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//設置view
applyDefaultViewName(processedRequest, mv);
//觸發攔截器的 postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);

//處理result,下面單獨分析
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

processDispatchResult 方法

這個方法主要是處理handler產生的結果或者異常,若是有異常則調用咱們配置好的異常處理的resolver來處理,若是沒有則調用具體的View來渲染handler產生的model。異步

boolean errorView = false;

		if (exception != null) {
        //處理異常
			if (exception instanceof ModelAndViewDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}

		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
           //渲染結果
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
		        。。。。。。。。
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}

Spring MVC Restful模式

輸入圖片說明 由流程圖咱們能夠查出Spring MVC Restful結構不須要view來渲染界面。下面咱們看其實現原理。jsp

開發Rest接口用到了RestController註解。從RestController這個註解繼承中,看出它繼承了Controller和ResponseBody兩個註解,也就是說 若是咱們在一個Controller類上加上@RestController 等價於 @Controller 和@ResponseBody這個兩個註解。也就是Spring MVC對有註解 ResonseBody 的方法的返回值作了特殊處理,這裏咱們還常常用到RequestBody這個註解放到具體的參數bean前,這樣會自動把前端提交的內容轉換爲咱們想要的對象。post

這裏先簡單理解下,由於Http消息協議都是字符串,也就是說Spring MVC把請求的字符串幫咱們轉換爲java 對象,處理完邏輯後,在把java 對象轉換爲Json或者xml字符串寫出去。這裏有幾個類必須提一下:

HttpInputMessage:這個Http消息流向服務端的一個接口抽象

HttpOutputMessage:Http消息寫出的抽象接口

HttpMessageConverter :消息轉換基本接口類

public interface HttpMessageConverter<T> {


	boolean canRead(Class<?> clazz, MediaType mediaType);


	boolean canWrite(Class<?> clazz, MediaType mediaType);

	List<MediaType> getSupportedMediaTypes();


	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;


	void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

知道大概有這麼回事,那Spring MVC的DispatcherServlet是怎麼把這些轉換串起來的呢,下面咱們接着看。

記得咱們上面提過DispatcherServlet的initStrategies方法,裏面講到它幫咱們作了不少初始化工做,包括初始化handler及handlerAdapter,咱們一直沒看到handlerAdapter起到什麼大做用。其實咱們的消息轉換就是靠它幫咱們織入的。在RequestMappingHandlerAdapter中咱們能夠看到messageConverters屬性,它是一個HttpMessageConverter List,包含了咱們須要的各類消息轉換器。

咱們斷點調試時能夠看到,在doDispatch根據handler取到對應handlerAdapter,裏面包含了各類messageConverter。 輸入圖片說明

輸入圖片說明

那準備工做完了之後,這些messageConverts在什麼時候何地被誰調用呢,繼續跟下去 輸入圖片說明 RequestMappingHandlerAdapter中 輸入圖片說明

先封裝一個invocableMethod出來,設置入參(methodArgument)和出參(methodReturnValue)的resolvers,接着看下面具體的觸發過程

輸入圖片說明 輸入圖片說明

上面的returenValueHandlers就是剛剛咱們上面set的出參(methodReturnValue)的resolvers,接着下去,找到能處理咱們Response註解的resolver類 RequestResponseBodyMethodProcessor

輸入圖片說明

RequestResponseBodyMethodProcessor 在其父類方法中遍歷converts,找到匹配的messageConvert而後進行轉換,而後寫出。至此處理過程結束,即時走到DispatcherServlet中的processDispatchResult,因爲view爲空,因此不會作渲染的邏輯。

至此,SpringMVC 兩種常見的用法分析完畢,從Spring MVC的dispatcher類的設計中,咱們能夠看到不少處理邏輯是靈活可配置的,好比當咱們用不一樣的view顯示就直接在xml中配對應的ViewResolver,例如常見的用JSP時,咱們常常會像下邊這樣配置

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</bean>

還有若是上傳文件時,咱們會配置multipartResolver,像下邊這樣

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

還有上面咱們講到作消息轉換用到的handlerAdapter和messageConvert,均可以靈活的配置的。Spring MVC 這種能夠經過配置靈活擴展的設計思想值得咱們借鑑。

https://my.oschina.net/robinyao/blog/795723

相關文章
相關標籤/搜索