本文主要講解Spring MVC 開發時應該瞭解的一些基本原理。利用Spring MVC咱們通常最多見的有兩種使用方式,一種是由view層的好比利用JSP / Velocity/Freemaker來作顯示層渲染數據,另外一種沒有view層,直接返回相似Json/Xml格式的數據的Restful使用方式。下面先具體講解下一下Spring MVC原理,而後在講解下使用RestController註解實現Restful接口的原理。前端
能夠說是一個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類須要的幾塊大內容,包括最主要的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); }
該方法主要是給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); // .....省落.......
這個方法是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);
這個方法主要是處理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結構不須要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 這種能夠經過配置靈活擴展的設計思想值得咱們借鑑。