還記得在web.xml中配置的DispatcherServlet嗎?其實那個就是SpringMVC框架的入口,這也是struts2和springmvc不一樣點之一,struts2是經過filter的,而springmvc是經過servlet的。看下servlet的結構圖 html
從上面這張圖很明顯能夠看出DispatcherServlet和Servlet以及Spring的關係。而咱們今天的重點就從DispatchServlet提及。java
在分析以前我用SpringBoot搭建了一個很簡單的後臺項目,用於分析。代碼以下web
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
private String address;
public User() {
}
}
/** * @author generalthink */
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public User getUser(HttpServletRequest request,@PathVariable Integer id) {
//建立一個user,不走數據庫只是爲了分析springmvc源碼
User user = User.builder()
.id(id)
.age(ThreadLocalRandom.current().nextInt(30))
.name("zzz" + id)
.address("成都市").build();
return user;
}
@RequestMapping(value = "/condition",method = RequestMethod.GET)
public User getByNameOrAge(@RequestParam String name,@RequestParam Integer age) {
User user = User.builder().name(name).age(age).address("成都市").id(2).build();
return user;
}
@PostMapping
public Integer saveUser(@RequestBody User user) {
Integer id = user.getName().hashCode() - user.getAge().hashCode();
return id > 0 ? id : -id;
}
}
複製代碼
這裏爲了方便調試把關注點更多集中在SpringMVC源碼中,因此這裏的數據都是僞造的。並且這裏的關注點也集中到使用註解的Controller(org.springframework.stereotype.Controlle
r),而不是Controller接口(org.springframework.web.servlet.mvc.Controller
),這二者的區別主要在乎一個只用標註註解,一個須要實現接口,可是它們都能完成處理請求的基本功能。咱們都知道訪問servlet的時候默認是訪問service方法的,因此咱們將斷點打在HttpServlet的service方法中,此時查看整個調用棧以下 spring
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
//注意這裏放回的是HandlerExecutionChain對象
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
ModelAndView mv = null;
Exception dispatchException = null;
//檢查是否存在文件上傳
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 根據當前request獲取handler,handler中包含了請求url,以及最終定位到的controller以及controller中的方法
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 經過handler獲取對應的適配器,主要完成參數解析
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 調用Controller中的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
複製代碼
能夠看到核心邏輯其實很是簡單,首先檢查是否是multipart request
,若是是則對當前的request進行必定的封裝(提取文件等),而後獲取對應的handler(保存了請求url對應的controller以及method以及一系列的Interceptor),而後在經過handler獲取到對應的handlerAdapter
(參數組裝),經過它來進行最終方法的調用數據庫
那麼是如何解析當前請求是文件上傳請求呢?這裏直接進入到checkMultipart方法看看是如何解析的:編程
//我精簡了下代碼,只提取了核心邏輯
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
return this.multipartResolver.resolveMultipart(request);
}
return request;
}
複製代碼
從這裏能夠看出經過multipartResolver
判斷當前請求是不是文件上傳請求,若是是則返回MultipartHttpServletRequest
(繼承自HttpServletRequest).不是則返回本來request對象。 那麼問題來了multipartResolver
是何時初始化的呢?json
咱們在idea中能夠直接將斷點定位到multipartResolver屬性上,進行請求訪問這個時候會發現斷點直接進入到了initMultipartResolver方法中,接着跟蹤整個調用棧,能夠發現調用關係以下: api
圖上代表了是在初始化servlet的時候對multipartResolver進行了初始化的。private void initMultipartResolver(ApplicationContext context) {
//從Spring中獲取id爲multipartResolver的類
this.multipartResolver = context.getBean("multipartResolver", MultipartResolver.class);
}
複製代碼
MultipartResolver接口有CommonsMultipartResolve
以及StandardServletMultipartResolver
2種實現,CommonsMultipartResolver接口是依賴於commons-upload組件實現的,而 StandardServletMultipartResolver是依賴於Servlet的part(servlet3才存在)實現的.二者判斷是不是文件上傳請求的方法isMultipart均是經過斷定請求方法是否爲post以及content-type頭是否包含multipart/來進行斷定的。數組
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context); //初始化multipartResolver
initLocaleResolver(context);//初始化localeResolver
initThemeResolver(context);//初始化themResolver
initHandlerMappings(context);//初始化handerMappings
initHandlerAdapters(context);//初始化handlerAdapters
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);//初始化試圖解析器
initFlashMapManager(context);
}
複製代碼
這些初始化的內容都會在後面被逐一使用,這裏先有一個印象。mvc
仍是進入到getHander方法中看看到底作了什麼?
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
複製代碼
根據HandlerMapping來查看對應的handler,那麼進入到initHandlerMappings方法中查看如何初始化handlerMappings
其中獲取默認的handlerMappings是去spring-webmvc的org.springframework.web.servlet
中的DispatcherServlet.properties中查找,文件內容是這樣的
detechAllhanderMappings
默認爲true,因此會獲取到全部HanderMapping的實現類,來看看它的類圖結構是怎樣的
這幾個HandlerMapping的做用以下:
SimpleUrlHandlerMapping : 容許明確指定URL模式和Handler的映射關係,內部維護了一個urlMap來肯定url和handler的關係
BeanNameUrlHandlerMapping: 指定URL和bean名稱的映射關係,不經常使用,咱們的關注點也主要集中在
RequestMappingHandlerMapping
中
這裏也基本明確了HandlerMapping的做用:幫助DispatcherServlet進行Web請求的URL到具體類的匹配,之因此稱爲HandlerMapping是由於在SpringMVC中並不侷限於 必須使用註解的Controller咱們也能夠繼承Controller接口,也一樣可使用第三方接口,好比Struts2中的Action
接着看下getHandler的實現:@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
複製代碼
返回的handler是HandlerExecutionChain,這其中包含了真實的handler以及攔擊器,能夠在執行前,執行後,執行完成這三個階段處理業務邏輯。 RequestMappingHandlerMapping
的getHandler的調用邏輯以下:
會遍歷全部Controller的url查看是否有符合條件的match(head,url,produce,consume,method都要知足要求),採用antMatcher的方式來進行url匹配,若是匹配上了則返回對應的handler,不然返回null,若是映射發現有重複的映射(url映射相同,請求方法相同,參數相同,請求頭相同,consume相同,produce相同,自定義參數相同),則會拋出異常。
而SimpleUrlHandlerMapping的調用邏輯以下:
其中維護了url到handler的映射,先經過url到urlMap中找對應的handler,若是沒有找到則嘗試pattenMatch,成功則返回對應的handler,未匹配則返回null。會發現處理HandlerMapping這裏運用了模板方法,在抽象類中定義好了業務邏輯,具體實現只須要實現本身的業務邏輯便可。同時也符合開閉原則,徹底是面向接口編程,不得不讓人歎服這裏的涉及邏輯。
分析到這裏的時候咱們會發現咱們以前定義的Controller明顯是符合RequestMappingHandlerMapping
的策略的,因此返回的HandlerExecutionChain已經包含了須要訪問的方法的全路徑了。
HandlerMapping會經過HandlerExecutionChain
返回一個Object類型的Handler對象,用於Web請求處理,這裏的Handler並無限制具體是什麼類型,通常來講任何類型的Handler均可以在 SpringMVC中使用,只要它是用於處理Web請求的處理對象就行。
不過對於DispatcherServlet來講就存在問題了,它沒法判斷到底使用的是什麼類型的Handler,也沒法知道是調用Handler的哪一個方法來處理請求,爲了以贊成的方式來調用各類類型的Handler, DispatcherServlet將不一樣Handler的調用職責轉交給了一個成爲HandlerAdapte
r的角色。
先看一下HandlerAdpter接口的定義
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
複製代碼
主要關注supports和handle方法。先看下DispatcherServlet
中handlerAdapters
的初始化過程,和handlerMappings
的初始化過程是相似的
RequestMappingHandlerAdapter
。
找到對應的適配器以後,這個時候就能夠調用真正的邏輯了。在這以前使用者能夠經過攔截器作一些事兒,好比記錄日誌,打印執行時間等,因此若是想要在執行的方法以前添加一條語句,咱們只須要配置本身的攔擊器便可。
接下來咱們重點分析handle方法,看看它到底會作什麼?,先看一下handle方法的執行流程,一樣的adapter一樣使用了模板方法,先在父類裏面定義流程,子類只須要實現邏輯便可,因此這裏首先會調用AbstracthandlerMethodAdapter的invokeHadlerMethod方法,其中對HandlerMethod進行了封裝。 咱們進入到第一步,看看invokeForRequest方法中主要作了什麼發現這個方法的調用邏輯實際上很簡單,就是解析參數,而後調用方法。咱們來看一下如何進行參數解析的呢?
能夠看到幾乎全部的核心邏輯都集中到了argumentResovlers
中去,那麼支持的arguementResolver有哪些?又是在哪裏初始化的呢?
首先須要定位到這個屬性是從哪裏過來的,RequestMappingHandlerAdapter
實現了InitializingBean
,因此在初始化的時候會執行afterPropertiesSet
方法,在這其中對arguementResolvers
以及returnValueHandlers
進行了初始化。 不一樣的resovler支持的參數解析不同,好比說有支持HttpServletRequest注入的,有支持HttpServletREsponse注入的還有支持body體注入的等等。
至此,springmvc的整個調用流程基本就清晰了。 可是到了這裏問題仍然沒有結束,由於咱們還不知道參數具體是如何解析的。好比get方式提交的數據?post方式提交的數據?如何轉換成對象的?這寫問題都還存在,那咱們繼續研究。 這裏我使用postman工具來發起請求,首先訪問 Get http://localhost:8080/user/condition?name=zhangsan&age=25,定位到resolveArgument
方法
接着又執行revolver.resolveArgument
方法,一樣的這裏仍是使用的模板方法,在抽象類AbstractNamedValueMethodArgumentResolver
中定義流程,各個子類只須要實現本身的邏輯便可。RequestParamMethodArgumentResolver
的參數就是經過request.getParameter來獲取到的。獲取到了參數以後就執行反射調用,這個時候就執行了咱們寫的UserController的對應方法,獲取到了User對象,接下來就是處理返回值了,經過returnValueHandlers進行處理
handler會根據返回的類型對數據進行處理,好比說這裏就經過response向請求方輸出數據,輸出數據也是經過messageConverter來實現的
最後獲取ModalAndView對象,可是這裏因爲沒有modalAndView因此返回的null.最後在DispatcherServlet的processDispatchResult方法的調用邏輯以下麼對於這樣的請求又時如何解析的呢?
@PostMapping
public Integer saveUser(@RequestBody User user) {
Integer id = user.getName().hashCode() - user.getAge().hashCode();
return id > 0 ? id : -id;
}
複製代碼
一樣咱們聚焦在解析參數的時候,在上一個get請求的示例中我說了會先訪問AbstractNamedValueMethodArgumentResolver
,可是在處理@RequestBody
的參數中它使用的是RequestResponseBodyMethodProcessor
,它複寫了resolveArgument
方法。因此不會去執行父類的邏輯。
這裏最後會定位到jakson的objectMapper中, 在spring boot中,默認使用Jackson來實現java對象到json格式的序列化與反序列化。固然是能夠配置messageConvert的,只須要實現Spring的HttpMessageConverter
便可。
源碼分析到這裏就結束了,固然其中還存在一些沒有講的地方,好比View的渲染呀,通常視圖是多種多樣的,又html,xml,jsp等等,因此springmvc也提供了接口供用戶選擇本身須要的模板,只須要實現ViewResolver接口便可。還有關於Theme,MessageResource,Exception的處理等等,若是鋪開來說篇幅實在是太長了,我更相信掌握了核心流程看其餘的處理就會很簡單了,因此這裏也就不對其餘枝節內容作分析了。