Spring源码分析(四)SpringMVC初始化

1,579 阅读5分钟

前言

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的请求处理我们大概有了一个轮廓,不过具体是怎么做的呢?下一节我们将看到这一过程。