前言
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种类型。
MappingJacksonHttpMessageConverterByteArrayHttpMessageConverterStringHttpMessageConverter......复制代码
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 Mapmappings = new IdentityHashMap (); final Class userType = ClassUtils.getUserClass(handlerType); //获取userType对象上的所有方法 返回JDK中的Method对象集合 Set 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 patterns = getMappingPathPatterns(mapping); for (String pattern : patterns) { if (!getPathMatcher().isPattern(pattern)) { this.urlMap.add(pattern, mapping); } }}复制代码
主要有两个缓存比较重要,handlerMethods和urlMap。 这里以indexController为例,看一下里面的数据是什么样的。
@Controllerpublic 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) { Listresolvers = getDefaultArgumentResolvers(); } if (this.returnValueHandlers == null) { List handlers = getDefaultReturnValueHandlers(); } initControllerAdviceCache();}复制代码
4.1、加载参数解析器
第一个方法就是加载默认的参数解析器。想象一下,在我们Controller的方法里面,可能会有很多参数,而且参数类型迥然不同,有的参数还可以加上了注解。那么,怎么把数据转换的呢?就是靠这些解析器
private ListgetDefaultArgumentResolvers() { //基于注解的参数解析 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 ListgetDefaultReturnValueHandlers() { //单用途返回值类型 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的请求处理我们大概有了一个轮廓,不过具体是怎么做的呢?下一节我们将看到这一过程。