Spring原始碼分析(四)SpringMVC初始化

清幽之地發表於2019-02-26

前言

Spring MVC 是一個模型 - 檢視 - 控制器(MVC)的Web框架。在具體使用時,我們通過Controller註解標明類是一個控制器,通過RequestMapping指明瞭請求地址。當我們的請求到來時,Spring就定位到類的方法。這一切看起來都這麼完美,但是Spring在底下到底是怎麼做的呢? 本章是解析SpringMVC的第一部分,先來看它初始化的時候做了哪些工作。

一、請求處理流程

在分析之前先來看一個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的請求處理我們大概有了一個輪廓,不過具體是怎麼做的呢?下一節我們將看到這一過程。

相關文章