在上一篇文章Spring与MVC(三)中,我们简单地分析了DispatcherServlet
的处理请求过程。在这篇文章中,我们详细分析一下DispatcherServlet
在处理请求时是如何找到正确的Controller,以及执行。
几个重要的接口和类
HandlerMethod
HandlerMethod
是一个封装了方法参数、方法注解、方法返回值等众多元素的类。
它的子类InvocableHandlerMethod
有两个重要的属性WebDataBinderFactory
和HandlerMethodArgumentResolverComposite
,很明显是对请求进行处理的。
InvocableHandlerMethod
的子类ServletInvocableHandlerMethod
有个重要的属性HandlerMethodReturnValueHandlerComposite
,很明显是对响应进行处理的。
ServletInvocableHandlerMethod
这个类在HandlerAdapter
对每个请求处理过程中,都会实例化一个出来(上面提到的属性由HandlerAdapter
进行设置),分别对请求和返回进行处理。
MethodParameter
HandlerMethod
类中的parameters属性类型,是一个MethodParameter
数组。MethodParameter
是一个封装了方法参数具体信息的工具类,包括参数的索引位置,类型,注解,参数名等信息。
HandlerMethod
在实例化的时候,构造函数中会初始化这个数组,这时只初始化部分数据,在HandlerAdapter
对请求处理过程中会完善其他属性,之后交与合适的HandlerMethodArgumentResolver
接口处理。
RequestCondition
RequestCondition
是SpringMVC的映射基础中的请求条件,可以进行combine
,compareTo
,getMatchingCondition
操作。这个接口是映射匹配的关键接口,其中getMatchingCondition
方法关乎是否能找到合适的映射。
RequestMappingInfo
RequestMappingInfo
是一个封装了各种请求映射条件并实现了RequestCondition
接口的类。
包含各种RequestCondition
实现类属性,patternsCondition
、methodsCondition
、paramsCondition
、headersCondition
、consumesCondition
、producesCondition
,分别代表http请求的路径模式、方法、参数、头部等信息。
RequestMappingHandlerMapping
与寻找Controller关系最密切的一个类是RequestMappingHandlerMapping
,我们首先来看一下这个类在初始化时所做的工作:
1 | protected void initHandlerMethods() { |
首先找出Spring容器中所有的bean。
然后遍历这些bean,调用isHandler
方法判断这个bean是不是handler,isHandler
判断该类的注解中是否有@Controller
或@RequestMapping
注解。如果这个bean是handler则调用detectHandlerMethods
方法获取handlerMethod。
1 | protected void detectHandlerMethods(final Object handler) { |
调用MethodIntrospector.selectMethods
方法获取handler中所有的方法与RequestMappingInfo的映射,返回一个Map<Method, RequestMappingInfo>
映射。其中的关键方法是getMappingForMethod
,具体实现由子类RequestMappingHandlerMapping
实现:
1 | protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { |
getMappingForMethod
方法通过方法以及类上加的@RequestMapping
的注释来创建RequestMappingInfo
。创建RequestMappingInfo
的工作由createRequestMappingInfo
方法完成:
1 | private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { |
回到getMappingForMethod
方法,从method中获得RequestMappingInfo
之后再从类中获得RequestMappingInfo
,然后调用combine
方法将两者结合起来返回:
1 | public RequestMappingInfo combine(RequestMappingInfo other) { |
我们可以看到,两个RequestMappingInfo
的结合就是将RequestMappingInfo
中各个元素结合起来再创建一个新的RequestMappingInfo
返回。
回到detectHandlerMethods
方法,获取到Method
和RequestMappingInfo
的映射之后。调用registerHandlerMethod
方法注册这个映射关系,本质是在MappingRegistry
类的mappingLookup
中添加mapping与handlerMethod的映射关系;在urlLoopup
中添加url与mapping的映射关系;在registry
中添加mapping与MappingRegistration的映射关系。
HandlerExecutionChain的获取
经过上一篇文章Spring与MVC(三)的分析,我们已经知道DispatcherServlet.doDispatch
是真正处理请求的方法,方法中首先调用getHandler
方法获取HandlerExecutionChain
,其中包括了目标方法以及拦截器。下面我们来更详细地分析一下这个获取HandlerExecutionChain
的过程。
首先来看getHandler
:
1 | protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { |
遍历当前handlerMappings
中保存的HandlerMapping
,与当前请求相关的是RequestMappingHandlerMapping
,其中getHandler
方法在RequestMappingHandlerMapping
的父类AbstractHandlerMapping
中:
1 | public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { |
首先调用getHandlerInternal
方法根据请求获取对应的处理方法(HandlerMethod),该方法在AbstractHandlerMapping
的子类AbstractHandlerMethodMapping
中实现:
1 | protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { |
首先调用getLookupPathForRequest
方法获取这个request请求的路径。
然后调用AbstractHandlerMethodMapping.lookupHandlerMethod
方法寻找HandlerMethod
:
1 | protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { |
调用mappingRegistry.getMappingsByUrl
从mappingRegistry
中获取请求路径对应的RequestMappingInfo
。
然后调用addMatchingMappings
获取请求路径对应的Match
列表,这个列表中包含RequestMappingInfo
以及对应的HandlerMethod
。
经过对其中RequestMappingInfo
的排序,获得一个最佳的匹配(如果第一个匹配和第二个匹配是相同的话,抛出异常),返回其中的HandlerMethod
。
addMatchingMappings方法
addMatchingMappings
方法的功能就是遍历所有的请求映射关系,寻找所有匹配请求路径的方法。调用流程如下:
- RequestMappingInfoHandlerMapping.getMatchingMapping
- RequestMappingInfo.getMatchingConditon
RequestMappingInfo.getMatchingConditon
方法中调用PatternsRequestConditon.getMatchingConditon
检查请求路径与Mapping中的映射路径规则是否匹配,调用流程如下:
- PatternsRequestCondition.getMatchingPatterns
- PatternsRequestCondition.getMatchingPattern
- AntPathMatcher.match
可以看到路径的匹配检查调用的是AntPathMatcher.match
方法。
回到getHandler
方法。通过getHandlerInternal
方法获得请求对应的HandlerMethod
之后,再调用getHandlerExecutionChain
方法获得HandlerExecutionChain
:
1 | protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { |
可以看到HandlerExecutionChain
中包含了HandlerMethod
以及HandlerInterceptor
(拦截器)列表:包含ConversionServiceExposingInterceptor
, ResourceUrlProviderExposingInterceptor
。
HandlerExecutionChain的执行
回到doDispatch
方法,调用getHandlerAdapter
方法根据HandlerExecutionChain
中的handler获取HandlerAdapter
:
1 | protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { |
这里的handlerAdapters
有三个(RequestMappingHandlerAdapter
、HttpRequestHandlerAdapter
、SimpleControllerHandlerAdapter
),遍历然后判断是否支持handler(主要是通过handler的类型来判断),这里返回的是RequestMappingHandlerAdapter
。
回到doDispatch
方法。在正式调用handler之前首先调用applyPreHandle
执行拦截器的preHandle
方法。
然后调用HandlerAdapter.handle
方法执行handler方法。
AbstractHandlerMethodAdapter.handle
方法委托给RequestMappingHandlerAdapter.handleInternal
方法来执行。
handleInternal
方法调用RequestMappingHandler.invokeHandlerMethod
来执行handler。
invokeHandlerMethod
方法调用ServletInvocableHandlerMethod.invokeAndHandle
:
1 | public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, |
首先调用invokeForRequest
执行用户定义的逻辑方法,然后调用HandlerMethodReturnValueHandlerComposite.handleReturnValue
来处理Controller方法返回的值:
1 | public void handleReturnValue(Object returnValue, MethodParameter returnType, |
首先调用selectHandler
方法获取处理返回值的处理器(HandlerMethodReturnValueHandler
),对于渲染页面来说这里返回的是ViewNameMethodReturnValueHandler
,ViewNameMethodReturnValueHandler.handleReturnValue
方法中在mavContainer中设置viewName。
回到RequestMappingHandlerAdapter.invokeHandlerMethod
,得到了渲染页面的名称之后,再调用getModelAndView
方法来创建ModelAndView
。
返回到Dispatcher.doDispatch
方法,执行完handle方法,获得了ModelAndView
,接着调用applyPostHandle
执行拦截器的postHandle
方法。最后调用processDispatchResult
方法渲染页面或者处理异常。