六、Spring MVC

tech2022-07-17  159

目录

 

1. Spring MVC使用示例

2. 主要结构

3. RequestMappingHandlerMapping的实例化和初始化

3.1 RequestMappingHandlerMapping的实例化

3.2 RequestMappingHandlerMapping的初始化(web方法收集)

4. 拦截器

4.1 拦截器使用示例

4.2 拦截器的收集

5. RequestMappingHandlerAdapter

5.1 RequestMappingHandlerAdapter的实例化

5.2 RequestMappingHandlerAdapter的初始化

6. ExceptionHandlerExceptionResolver

6.1 ExceptionHandlerExceptionResolver的实例化

6.2    ExceptionHandlerExceptionResolver的初始化

7. Spring mvc容器中的bean注入

8. 请求的处理流程

8.1 getHandler获取请求路径对应的HandlerMethod和拦截器

8.2 getHandlerAdapter获取HandlerAdapter

8.3 mappedHandler.applyPreHandle正序调用拦截器前置处理方法

8.4 ha.handle调用请求路径对应的web方法

8.4.1 参数解析

8.4.2 反射调用web方法

8.4.3 返回值解析

8.5 mappedHandler.applyPostHandle倒序调用拦截器的中置处理方法

8.6 processDispatchResult

8.6.1 ControllerAdvice异常处理方法调用

8.6.2 倒序调用拦截器后置处理方法


(本篇中用到的演示项目地址: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的简要分析。

最新回复(0)