目录
3. RequestMappingHandlerMapping的实例化和初始化
3.1 RequestMappingHandlerMapping的实例化
3.2 RequestMappingHandlerMapping的初始化(web方法收集)
5. RequestMappingHandlerAdapter
5.1 RequestMappingHandlerAdapter的实例化
5.2 RequestMappingHandlerAdapter的初始化
6. ExceptionHandlerExceptionResolver
6.1 ExceptionHandlerExceptionResolver的实例化
6.2 ExceptionHandlerExceptionResolver的初始化
8.1 getHandler获取请求路径对应的HandlerMethod和拦截器
8.2 getHandlerAdapter获取HandlerAdapter
8.3 mappedHandler.applyPreHandle正序调用拦截器前置处理方法
8.5 mappedHandler.applyPostHandle倒序调用拦截器的中置处理方法
8.6.1 ControllerAdvice异常处理方法调用
(本篇中用到的演示项目地址:https://gitee.com/yejuan/spring-learning-no-xml 对应tag: spring-c6)
1. Spring MVC使用示例
我们通过类与方法上面的RequestMapping拼接出来的路径/user/showUser就可以调用到showUser方法,本篇将分析下是如何实现的。
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
IUserService userService;
@ResponseBody
@RequestMapping("/showUser")
public String showUser(){
LogUtil.getLogger().info("invoke showUser");
User user = new User();
user.setName("ZhangSan");
user.setAge(18);
return userService.showUser(user);
}
}
2. 主要结构
DispatcherServlet中有三个重要的容器handlerMappings、handlerAdapters、handlerExceptionResolvers三个容器中RequestMappingHandlerMapping、RequestMappingHandlerAdapter、ExceptionHandlerExceptionResolver三个实例对web请求的处理尤为重要:
- RequestMappingHandlerMapping:DispatcherServlet是我们配置的用来接收所有请求的Servlet, Servlet接收到用户请求后根据请求路径在RequestMappingHandlerMapping 中mappingRegistry的urlLookup匹配到RequestMappingInfo,再在mappingRegistry的mappingLookup中根据RequestMappingInfo找到对应的HandlerMethod,这样就找到了请求路径对应的具体方法,再进行反射调用就可以处理请求了;
- RequestMappingHandlerAdapter:通过RequestMappingHandlerAdapter的参数解析器和返回值解析器完成请求的参数解析和返回值解析;
- ExceptionHandlerExceptionResolver:可通过ControllerAdvice注解自定义Controller调用发生异常后的增强,ControllerAdvice类型bean的收集和调用通过ExceptionHandlerExceptionResolver支持。
3. RequestMappingHandlerMapping的实例化和初始化
AbstractApplicationContext#refresh方法中的finishRefresh()(调用发生在spring容器bean实例化之后)会通过Listener监听机制调用到DispatcherServlet#onRefresh方法进行初始化。
org.springframework.web.servlet.DispatcherServlet#onRefresh
3.1 RequestMappingHandlerMapping的实例化
默认会读取DispatcherServlet.properties中的配置的key为org.springframework.web.servlet.HandlerMapping对应的value,value为HandlerMapping接口实现类的全限定名,创建相应的beanDefinition再通过Spring那套实例化、初始化流程拿到对应的实例。
默认读取DispatcherServlet.properties配置文件
拿到实现类后,封装生成beanDefinition调用spring那套进行实例化、初始化
org.springframework.web.servlet.DispatcherServlet#getDefaultStrategies
3.2 RequestMappingHandlerMapping的初始化(web方法收集)
RequestMappingHandlerMapping实现了InitializingBean接口在初始化过程中会调用到afterPropertiesSet方法进行web方法(web请求路径对应的方法)的收集。org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#afterPropertiesSet
过滤类上有 Controller、RequestMapping注解的类
遍历方法如果方法上有RequestMapping注解则该方法为web方法结合类上的RequestMapping注解信息包装生成RequestMappingInfo对象, RequestMappingInfo对象中有web方法对应的请求路径与方法,建立web方法方法与RequestMappingInfo映射关系缓存到容器。org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods
- 将RequestMappingInfo与HandlerMethod的映射关系加入mappingRegistry的mappingLookup容器,HandlerMethod包装有web方法对应的method、beanName、beanFactory方便反射调用;
- 将请求路径字符串与RequestMappingInfo的映射关系加入mappingRegistry的urlLookup容器
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#register
到这里我们就完成了web方法的收集和缓存,为处理web请求做好准备。
4. 拦截器
4.1 拦截器使用示例
实现HandlerInterceptor接口定义自己的拦截器
public class MyHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("-------MyHandlerInterceptor preHandle ------------");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("-------MyHandlerInterceptor postHandle ------------");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("-------MyHandlerInterceptor afterCompletion ------------");
}
}
配置拦截器,配置拦截器的拦截路径,通过@Bean将拦截器加入Spring容器
4.2 拦截器的收集
由2中的结构图可知RequestMappingHandlerMapping的adaptedInterceptors容器中缓存了所有的拦截器,下面我们来分析整理下拦截器的收集过程。spring在初始化RequestMappingHandlerMapping时会调用到RequestMappingHandlerMapping的initApplicationContext进行拦截器的收集。
org.springframework.web.servlet.handler.AbstractHandlerMapping#initApplicationContext
5. RequestMappingHandlerAdapter
RequestMappingHandlerAdapter主要是完成了web方法调用的参数解析和返回值解析
5.1 RequestMappingHandlerAdapter的实例化
同HandlerMapping的实例化一样,RequestMappingHandlerAdapter的实例化也是在AbstractApplicationContext#refresh方法中的finishRefresh()调用到的DispatcherServlet#onRefresh方法中进行的
org.springframework.web.servlet.DispatcherServlet#onRefresh
默认读取DispatcherServlet.properties中的key为org.springframework.web.servlet.HandlerAdapter的配置进行实例化、初始化。
org.springframework.web.servlet.DispatcherServlet#getDefaultStrategies
5.2 RequestMappingHandlerAdapter的初始化
RequestMappingHandlerAdapter实现了InitializingBean接口在初始化过程中会调用到afterPropertiesSet方法,afterPropertiesSet方法中完成了参数解析器与返回值解析器的创建和添加。
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#afterPropertiesSet
参数解析器的创建
返回值解析器的创建
6. ExceptionHandlerExceptionResolver
6.1 ExceptionHandlerExceptionResolver的实例化
ExceptionHandlerExceptionResolver的实例化也是通过DispatcherServlet#onRefresh方法完成的,默认的HandlerExceptionResolver同样的配置在DispatcherServlet.properties文件中。
6.2 ExceptionHandlerExceptionResolver的初始化
ExceptionHandlerExceptionResolver的初始化方法afterPropertiesSet完成了有ControllerAdvice注解bean的收集、解析,以及ControllerAdvice类型bean的参数解析器、返回值解析器的创建和设置。
一个ControllerAdviceBean对应一个ExceptionHandlerMethodResolver对象,ControllerAdviceBean与ExceptionHandlerMethodResolver的映射关系缓存在exceptionHandlerAdviceCache容器中,ExceptionHandlerMethodResolver中的mappedMethods容器缓存了异常类型与异常处理方法的映射关系。
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache
7. Spring mvc容器中的bean注入
在spring源码分析的第二篇中我们分析了spring web项目中有两个容器一个专门装载Controller类型的bean为了区分我们把它称为Spring mvc容器,另一个Spring mvc的parent 容器即root 容器,专门缓存Controller之外的其他bean。Controller bean与其他类型的bean不在同一个容器,Controller中的依赖注入是如何完成的呢,我们来分析下。
前面讲过AutowiredAnnotationBeanPostProcessor#postProcessProperties会对bean中有@Autowired的属性调用getBean获取实例,进行属性的注入。getBean获取实例时如果父容器不为空并且当前容器(子容器)中没有相关的bean,调用父容器doGetBean获取相关实例。
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
8. 请求的处理流程
8.1 getHandler获取请求路径对应的HandlerMethod和拦截器
根据请求路径从urlLookup拿到RequestMappingInfo,根据RequestMappingInfo从mappingLookup拿到HandlerMethod,从List<HandlerInterceptor> adaptedInterceptors容器中过滤筛选与请求路径匹配的拦截器,包装拦截器与HandlerMethod得到HandlerExecutionChain
org.springframework.web.servlet.DispatcherServlet#getHandler
8.2 getHandlerAdapter获取HandlerAdapter
应用策略模式根据HandlerMethod获取到匹配的HandlerAdapter为RequestMappingHandlerAdapter
org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
8.3 mappedHandler.applyPreHandle正序调用拦截器前置处理方法
正序调用拦截器的前置处理,如果请求被拦截,直接倒序调用拦截器的后置处理方法后返回
8.4 ha.handle调用请求路径对应的web方法
不同的参数和返回值对应的解析器和消息转换器不同,但整个流程框架大致一样的,我们以下面的代码为例分析web方法的执行过程
@Controller
@RequestMapping("/account")
public class AccountController {
@Autowired
AccountService accountService2;
@ResponseBody
@RequestMapping("/show")
public String showUser(@RequestBody String requestBody){
LogUtil.getLogger().info("requestBody :{}", requestBody);
return "AccountController show";
}
}
整体流程如下
8.4.1 参数解析
解析web方法的参数,策略模式通过supportsParameter方法找到能解析参数的解析器,如RequestResponseBodyMethodProcessor#supportsParameter 匹配有RequestBody注解的参数,我们的示例代码匹配的解析器为RequestResponseBodyMethodProcessor。
org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite#getArgumentResolver
解析参数,根据参数类型和请求的contentType找到匹配的消息解析器,从请求的输入流中读取内容进行解析
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#resolveArgument
AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type)
8.4.2 反射调用web方法
org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
8.4.3 返回值解析
返回值的解析与参数解析的流程相似,同样通过策略模式找到返回值匹配的解析器,再从解析器中找到消息转换器,解析返回值将返回值写入请求对应的输出流
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
匹配返回值解析器
获取返回值匹配的解析器,如RequestResponseBodyMethodProcessor匹配类或方法上有ResponseBody注解的返回值org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#supportsReturnType
解析返回值,根据返回值类型找到匹配的消息转换器,示例代码将匹配到StringHttpMessageConverter消息转换器,将返回值写入请求的输出流
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue
AbstractMessageConverterMethodProcessor#writeWithMessageConverters(T, org.springframework.core.MethodParameter, org.springframework.http.server.ServletServerHttpRequest, org.springframework.http.server.ServletServerHttpResponse)
8.5 mappedHandler.applyPostHandle倒序调用拦截器的中置处理方法
org.springframework.web.servlet.HandlerExecutionChain#applyPostHandle
8.6 processDispatchResult
8.6.1 ControllerAdvice异常处理方法调用
发生异常时会调用到到异常处理器ExceptionHandlerExceptionResolver,通过exceptionHandlerAdviceCache容器遍历所有的ControllerAdvice类型的bean,通过ControllerAdvice注解中配置的basePackages、assignableTypes、annotations判断抛出异常的Controller是否被当前ControllerAdvice类型的bean匹配,如果匹配通过异常类型在mappedMethods容器中找到匹配的方法
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#getExceptionHandlerMethod
获取异常对应的ControllerAdvice类型bean的异常处理方法后,设置参数解析器,设置返回值解析器,调用异常处理方法,将异常和请求路径对应的HandlerMethod作为参数传入,解析异常处理方法的参数反射进行异常处理方法方法调用org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException
8.6.2 倒序调用拦截器后置处理方法
org.springframework.web.servlet.DispatcherServlet#processDispatchResult
到这里就完成了spring mvc主流程的源码分析,截止本篇也就完成了spring 整个源码包括spring ioc、spring aop、spring mvc的简要分析。