前言
Spring MVC 是一個模型 - 檢視 - 控制器(MVC)的Web框架。在具體使用時,我們通過Controller註解標明類是一個控制器,通過RequestMapping指明瞭請求地址。當我們的請求到來時,Spring就定位到類的方法。這一切看起來都這麼完美,但是Spring在底下到底是怎麼做的呢? 本章是解析SpringMVC的第一部分,先來看它初始化的時候做了哪些工作。
一、請求處理流程
在分析之前先來看一個SpringMVC請求處理最簡單的流程圖。
二、初始化
在完成所有Bean的例項化後,Spring又載入了一系列策略方法,用於SpringMVC。
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
複製程式碼
我們重點關注兩個,initHandlerMappings和initHandlerAdapters。
1、 initHandlerMappings
初始化handlerMappings,就是載入處理器對映,它的載入有三種方式。
-
配置檔案具體指明
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
-
配置
<mvc:annotation-driven/>
,這種方式的話,Spring在解析標籤的時候會載入兩個handlerMappings,分別是RequestMappingHandlerMapping和BeanNameUrlHandlerMapping。 -
什麼都不配。如果什麼都沒有配置,Spring會預設載入/org/springframework/web/servlet/DispatcherServlet.properties檔案,此檔案裡面配置了handlerMappings和handlerAdapters。
2、initHandlerAdapters
它的載入也有三種方式,和上面一樣。或者配置mvc:annotation-driven/,或者具體指明Adapters,或者什麼也不配,走Spring預設方式。
這裡筆者推薦mvc:annotation-driven/。它在解析的時候,就已經載入了HandlerMapping和HandlerAdapter。
new RootBeanDefinition(RequestMappingHandlerMapping.class);
new RootBeanDefinition(BeanNameUrlHandlerMapping.class);
new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
new RootBeanDefinition(RequestMappingHandlerAdapter.class);
new RootBeanDefinition(HttpRequestHandlerAdapter.class);
new RootBeanDefinition(SimpleControllerHandlerAdapter.class);
複製程式碼
並預設新增了RequestMappingHandlerAdapter介面卡的訊息轉換器。
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
複製程式碼
messageConverters預設包含7種型別。
MappingJacksonHttpMessageConverter
ByteArrayHttpMessageConverter
StringHttpMessageConverter
......
複製程式碼
三、例項化RequestMappingHandlerMapping
上面我們看到mvc:annotation-driven/已經載入了RequestMappingHandlerMapping處理類,有意思的是,這個類的父類實現了InitializingBean介面。我們馬上就會想到,在例項化之後就會呼叫afterPropertiesSet方法。
1 、initHandlerMethods
此方法會遍歷所有的Bean例項,過濾包含Controller註解和RequestMapping註解的類,然後查詢類上的方法,獲取方法上的URL。最後把URL和方法的對映註冊到容器。
protected void initHandlerMethods() {
//這裡的beanNames就是所有的Bean
String[] beanNames = (getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
if (isHandler(getApplicationContext().getType(beanName))){
detectHandlerMethods(beanName);
}
}
}
//根據beanName找到它的Class物件,判斷類是否包含下面這個註解
protected boolean isHandler(Class<?> beanType) {
return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
(AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
}
複製程式碼
protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = handler.getClass());
final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
final Class<?> userType = ClassUtils.getUserClass(handlerType);
//獲取userType物件上的所有方法 返回JDK中的Method物件集合
Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
@Override
public boolean matches(Method method) {
//method封裝成RequestMappingInfo物件
//物件包含了url和HTTP請求的其他資訊 比如params、method、headers等
T mapping = getMappingForMethod(method, userType);
if (mapping != null) {
mappings.put(method, mapping);
return true;
}
else {
return false;
}
}
});
for (Method method : methods) {
registerHandlerMethod(handler, method, mappings.get(method));
}
}
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
//把beanName和Method物件封裝成HandlerMethod物件。
//HandlerMethod物件包含了beanName、beanFactory、Method物件和方法引數集合
HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
//註冊對映關係
this.handlerMethods.put(mapping, newHandlerMethod);
Set<String> patterns = getMappingPathPatterns(mapping);
for (String pattern : patterns) {
if (!getPathMatcher().isPattern(pattern)) {
this.urlMap.add(pattern, mapping);
}
}
}
複製程式碼
主要有兩個快取比較重要,handlerMethods和urlMap。 這裡以indexController為例,看一下里面的資料是什麼樣的。
@Controller
public class IndexController {
@RequestMapping("/index")
public @ResponseBody String index(String id) {
System.out.println("--------------------");
return "Hello! My name is ACAL!";
}
}
複製程式碼
以上面的Controller為例,handlerMethods裡面的資料如下:
{{[/index],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}=public java.lang.String com.viewscenes.netsupervisor.controller.IndexController.index(java.lang.String)}
可以看到,它是mapping和HandlerMethod物件的對映。
urlMap裡面的資料如下:
{/index=[{[/index],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}]}
它是URL和mapping的對映。看到這,我們可以大膽的預估,瀏覽器如果請求了/index,先通過urlMap找到對應的mapping,然後通過handlerMethods會找到HandlerMethod物件。HandlerMethod物件包含了Method物件和引數列表,這樣不就可以通過invoke方法呼叫了嗎?沒錯,不過還少一個東西,就是引數怎麼匹配的解析的問題。
四、例項化RequestMappingHandlerAdapter
我們來到RequestMappingHandlerAdapter類,驚喜的發現它也實現了InitializingBean介面,那麼直接來到afterPropertiesSet方法。我們重點看兩個
public void afterPropertiesSet() {
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
}
initControllerAdviceCache();
}
複製程式碼
1、載入引數解析器
第一個方法就是載入預設的引數解析器。想象一下,在我們Controller的方法裡面,可能會有很多引數,而且引數型別迥然不同,有的引數還可以加上了註解。那麼,怎麼把資料轉換的呢?就是靠這些解析器
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
//基於註解的引數解析
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));@RequestParam
resolvers.add(new PathVariableMethodArgumentResolver()); --@PathVariable
resolvers.add(new MatrixVariableMethodArgumentResolver()); --@MatrixVariable
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters())); --@RequestBody
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters())); --@RequestPart
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory())); --@RequestHeader
...未完
//基於型別的引數解析
resolvers.add(new ServletRequestMethodArgumentResolver()); --Request
resolvers.add(new ServletResponseMethodArgumentResolver()); -- Response
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters())); --HttpEntity
resolvers.add(new ModelMethodProcessor()); --Model
resolvers.add(new MapMethodProcessor()); --Map
...未完
// 定義的解析器
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
return resolvers;
}
複製程式碼
2、載入返回值型別解析器
上面說了引數解析,那麼在方法返回的時候,型別也是各不相同的。它也是靠各種解析器來支援的。
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
//單用途返回值型別
handlers.add(new ModelAndViewMethodReturnValueHandler()); -- ModelAndView
handlers.add(new ModelMethodProcessor()); --Model
handlers.add(new ViewMethodReturnValueHandler()); --View
handlers.add(new HttpEntityMethodProcessor(); --HttpEntity
...未完
// 基於註解的返回值型別
handlers.add(new ModelAttributeMethodProcessor(false)); --@ModelAttribute
handlers.add(new RequestResponseBodyMethodProcessor()); --@ResponseBody
// 多用途返回值型別
handlers.add(new ViewNameMethodReturnValueHandler()); --void或者String型別
handlers.add(new MapMethodProcessor()); --Map
// 自定義返回值型別
if (getCustomReturnValueHandlers() != null) {
handlers.addAll(getCustomReturnValueHandlers());
}
return handlers;
}
複製程式碼
五、總結
通過本章節內容,我們看到SpringMVC在初始化的時候,載入了RequestMappingHandlerMapping和RequestMappingHandlerAdapter。
在例項化RequestMappingHandlerMapping的時候,過濾了所有類上有Controller、RequestMapping的Bean,獲取他們的方法。註冊methodMapping和urlMap。為以後的請求地址匹配做準備。 在例項化RequestMappingHandlerAdapter的時候,又註冊了一堆解析器,包括引數解析器和返回值型別解析器。為請求的引數解析和返回型別解析做準備。
有了以上內容的準備,關於SpringMVC的請求處理我們大概有了一個輪廓,不過具體是怎麼做的呢?下一節我們將看到這一過程。