前言
Spring MVC 是一个模型 - 视图 - 控制器(MVC)的Web框架。在具体使用时,我们通过Controller注解标明类是一个控制器,通过RequestMapping指明了请求地址。当我们的请求到来时,Spring就定位到类的方法。这一切看起来都这么完美,但是Spring在底下到底是怎么做的呢?
本章是解析SpringMVC的第一部分,先来看它初始化的时候做了哪些工作。
1、请求处理流程
在分析之前先来看一个SpringMVC请求处理最简单的流程图。
2、初始化
在完成所有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。
2.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.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
......
3、实例化RequestMappingHandlerMapping
上面我们看到<mvc:annotation-driven/>已经加载了RequestMappingHandlerMapping处理类,有意思的是,这个类的父类实现了InitializingBean接口。我们马上就会想到,在实例化之后就会调用afterPropertiesSet方法。
3.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方法调用了吗?没错,不过还少一个东西,就是参数怎么匹配的解析的问题。
4、实例化RequestMappingHandlerAdapter
我们来到RequestMappingHandlerAdapter类,惊喜的发现它也实现了InitializingBean接口,那么直接来到afterPropertiesSet方法。我们重点看两个
public void afterPropertiesSet() {
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
}
initControllerAdviceCache();
}
4.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;
}
4.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;
}
5、总结
通过本章节内容,我们看到SpringMVC在初始化的时候,加载了RequestMappingHandlerMapping和RequestMappingHandlerAdapter。
在实例化RequestMappingHandlerMapping的时候,过滤了所有类上有Controller、RequestMapping的Bean,获取他们的方法。注册methodMapping和urlMap。为以后的请求地址匹配做准备。
在实例化RequestMappingHandlerAdapter的时候,又注册了一堆解析器,包括参数解析器和返回值类型解析器。为请求的参数解析和返回类型解析做准备。
有了以上内容的准备,关于SpringMVC的请求处理我们大概有了一个轮廓,不过具体是怎么做的呢?下一节我们将看到这一过程。