SpringBoot源码解读与原理分析(三十七)SpringBoot整合WebMvc(二)DispatcherServlet的工作全流程

文章目录

    • 前言
    • 12.4 DispatcherServlet的工作全流程
      • 12.4.1 DispatcherServlet#service
      • 12.4.2 processRequest
      • 12.4.3 doService
        • 12.4.3.1 isIncludeRequest的判断
        • 12.4.3.2 FlashMapManager的设计
      • 12.4.4 doDispatch
        • 12.4.4.1 处理文件上传请求
        • 12.4.4.2 获取可用的Handler
          • (1)HandlerMapping.getHandler
          • (2)getHandlerInternal
          • (3)getHandlerExecutionChain
        • 12.4.4.3 获取HandlerAdapter
        • 12.4.4.4 回调拦截器
        • 12.4.4.5 执行Handler
          • (1)handleInternal
          • (2)invokeHandlerMethod
            • (a)初始化参数绑定器
            • (b)参数预绑定
            • (c)创建方法执行对象
            • (d)执行Controller的方法```invokeAndHandle```
            • (e)包装ModelAndView
        • 12.4.4.6 再次回调拦截器
        • 12.4.4.7 处理视图、解析异常
          • (1)处理异常
          • (2)渲染视图
          • (3)第三次回调拦截器
      • 12.4.5 DispatcherServlet工作流程总结
    • 12.5 小结

前言

WebMvc的核心组件装配完成之后,DispatcherServlet作为WebMvc的核心前端控制器正式投入工作,默认接收客户端的所有请求,并调度其它核心组件处理请求,最终响应结果给客户端。

本节内容研究WebMvc在实际运行期间DispatcherServlet对于请求处理和响应结果的全流程执行原理。本文内容由于不可割裂,因此具有超长预警。

本文沿用 SpringBoot源码解读与原理分析(三十六)SpringBoot整合WebMvc(一)@Controller控制器装配原理 12.1 SpringBoot整合WebMvc案例 中编写好的示例项目,并进行一些小改动。

  • 新增CustomAdvice类,标注@ControllerAdvice注解,并编写两个标注@InitBinder和@ExceptionHandler的方法
@ControllerAdvice
public class CustomAdvice {@InitBinderpublic void customDataBinder(WebDataBinder dataBinder) {// 请求URL中的时间格式为yyyy-MM-dd HH:mm:ss// 自动绑定到Date属性参数中dataBinder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));}@ExceptionHandler({Exception.class})public String customExceptionHandler(Exception ex) {System.out.println("自定义异常发生了," + ex.getMessage());return "自定义异常返回";}}
  • 修改UserController类,新增标注@InitBinder注解的方法,test方法新增一个Date类型的参数
@Controller
@RequestMapping("/user")
public class UserController {@InitBinderpublic void myDataBinder(WebDataBinder dataBinder) {// 如果参数是字符串类型,则去除字符串的前后空格dataBinder.registerCustomEditor(String.class, new StringTrimmerEditor(true));}@RequestMapping(method = RequestMethod.GET, value = "/test")@ResponseBodypublic String test(String name, Date time) {System.out.println("请求参数 name=" + name);System.out.println("请求参数 time=" + time);return name;}
}
  • 新增拦截器CustomInterceptor类
public class CustomInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("拦截器的preHandle方法执行了...");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("拦截器的postHandle方法执行了...");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("拦截器的afterCompletion方法执行了...");}
}
  • 修改配置类,加载拦截器及配置其路径
@Configuration
public class PathConfig implements WebMvcConfigurer {@Overridepublic void configurePathMatch(PathMatchConfigurer configurer) {configurer.addPathPrefix("/api", c -> c.isAnnotationPresent(Controller.class));}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new CustomInterceptor()).addPathPatterns("/api/user/test");}
}

至此,示例项目代码编写完成,执行主启动类的main方法启动项目。使用浏览器访问 http://127.0.0.1:8080/api/user/test?name=aaa&time=2024-02-29 12:12:12,控制台打印结果如下:

拦截器的preHandle方法执行了...
请求参数 name=aaa
请求参数 time=Thu Feb 29 12:12:12 CST 2024
拦截器的postHandle方法执行了...
拦截器的afterCompletion方法执行了...

12.4 DispatcherServlet的工作全流程

启动示例项目后,在DispatcherServlet的父类FrameworkServlet的service方法上打入断点,随后使用浏览器发起请求,待程序停在断点处,开始Debug调试。

12.4.1 DispatcherServlet#service

源码1FrameworkServlet.javaprotected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());if (httpMethod == HttpMethod.PATCH || httpMethod == null) {// 对PATCH类型的请求单独处理processRequest(request, response);} else {// 调用父类的HttpServlet的service方法super.service(request, response);}
}@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);
}

由 源码1 可知,service方法会对PATCH类型的请求单独处理,但通常在项目开发中不会使用PATCH类型

继续向下执行else块的super.service方法,FrameworkServlet的父类HttpServlet会根据不同的请求类型将方法转发至doXxx方法中,所以最终执行的是FrameworkServlet中重写的doGetdoPostdoPutdoDelete等方法,而这些方法最终都会调用processRequest方法。

12.4.2 processRequest

源码2FrameworkServlet.javaprotected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 记录接收请求的时间long startTime = System.currentTimeMillis();Throwable failureCause = null;// 获取当前线程的LocaleContextLocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();// 创建当前线程的LocaleContextLocaleContext localeContext = buildLocaleContext(request);// 获取当前线程的RequestAttributesRequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();// 创建当前线程的RequestAttributesServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());// 初始化ContextHolder,传入新封装好LocaleContext和RequestAttributesinitContextHolders(request, localeContext, requestAttributes);try {// 真正处理请求的方法,但这是子类的模板方法doService(request, response);} // catch ...finally {// 重新设置当前线程的LocaleContext和RequestAttributesresetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);// 发布ServletRequestHandledEvent事件publishRequestHandledEvent(request, response, startTime, failureCause);}
}private void initContextHolders(HttpServletRequest request,@Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {// 将全新的LocaleContext和RequestAttributes设置到当前线程中if (localeContext != null) {LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);}if (requestAttributes != null) {RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);}
}private void resetContextHolders(HttpServletRequest request,@Nullable LocaleContext prevLocaleContext, @Nullable RequestAttributes previousAttributes) {// 将预先保存的LocaleContext和RequestAttributes设置回线程中LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable);
}

由 源码2 可知,processRequest方法会对请求做一些前置处理,再转调doService方法真正处理请求。

在该方法的前置处理中,做了线程之间的隔离。首先获取当前线程的LocaleContext和RequestAttributes并暂存在方法中,随后创建全新的LocaleContext和RequestAttributes,调用initContextHolders方法设置到当前线程中,以此完成线程之间的隔离。

待请求完成处理后,在finally代码块中,调用resetContextHolders方法将预先保存好的LocaleContext和RequestAttributes设置回线程中,以恢复原来的线程。

12.4.3 doService

源码3DispatcherServlet.javaprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);Map<String, Object> attributesSnapshot = null;// 判断请求是否由<jsp:include>标签而来if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap<>();Enumeration<?> attrNames = request.getAttributeNames();while (attrNames.hasMoreElements()) {String attrName = (String) attrNames.nextElement();if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {attributesSnapshot.put(attrName, request.getAttribute(attrName));}}}// ......if (this.flashMapManager != null) {FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));}request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);}try {// 真正处理请求的方法doDispatch(request, response);} // finally ...
}

由 源码3 可知,doService方法也是在进行一些前置处理之后,转调doDispatch方法真正处理请求。

12.4.3.1 isIncludeRequest的判断
源码4WebUtils.javapublic static final String INCLUDE_REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
public static boolean isIncludeRequest(ServletRequest request) {// 判断当前请求中是否包含名为“javax.servlet.include.request_uri”的属性return (request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE) != null);
}

由 源码4 可知,doService方法的第一个if判断结构会调用WebUtils.isIncludeRequest(request)方法。该方法会判断当前请求中是否包含名为“javax.servlet.include.request_uri”的属性。

在JSP中,使用 <jsp:incluedepage=“xxx.jsp”> 标签可以组合其他JSP页面,那么这个被组合的JSP页面的加载请求就会带上“javax.servlet.include.request_uri”属性。因此,isIncludeRequest方法的作用是区别页面的加载是否由<jsp:include>标签而来。

12.4.3.2 FlashMapManager的设计

在用户登录的业务场景中,如果是前后端不分离的情况,通常是使用POST请求将用户名、密码等信息传入后端以供认证,认证成功后使用重定向将客户端引导至系统主页。

在这个前提下有一个特殊的场景:如果用于登录时提交的登录表单中,有一些需要在跳转至主页时渲染的数据,则仅放入request域中无法解决问题。

Spring引入FlashMapManager来解决这个问题,可以在页面重定向发生跳转时,将需要渲染的数据暂时放入session中,这样浏览器即便刷新也不会影响数据渲染。

12.4.4 doDispatch

该方法是处理请求和响应的核心方法,由于篇幅很长,下面拆解来看。

12.4.4.1 处理文件上传请求
源码5DispatcherServlet.javaprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// 处理文件上传请求processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// ......
}protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {// logger ...}} else if (hasMultipartException(request)) {// logger ...} else {try {return this.multipartResolver.resolveMultipart(request);} // catch ...}}return request;
}
源码6StandardServletMultipartResolver.java@Override
public boolean isMultipart(HttpServletRequest request) {return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
}

由 源码5 可知,doDispatch方法的第一部分逻辑是处理带有文件上传的请求,其核心组件是MultipartResolver。首先判断请求是否是multipart请求,如果是,则将本由Servlet容器处理的HttpServletRequest对象转换为可以访问请求中的文件对象的MultipartHttpServletRequest子接口对象。

由 源码6 可知,判断请求是否是multipart请求的方法是判断请求头content-type是否以"multipart/"开头。

由于当前Debug的请求只是一个普通的GET请求,所以不会进入该部分代码。

12.4.4.2 获取可用的Handler
源码7DispatcherServlet.javaprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 处理文件上传的请求 ...// 获取可用的HandlermappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// ......
}protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;
}

由 源码7 可知,doDispatch方法的第二部分逻辑是获取可用的Handler。在getHandler方法中,遍历所有的HandlerMapping,从中找出可以返回非空HandlerExecutionChain对象的HandlerMapping。

HandlerMapping集合
Debug至getHandler方法的内部,可以发现有5个HandlerMapping可以选择。由于实例项目编写的Handler是以@Controller+@RequestMapping注解实现的,因此最终选择出来的HandlerMapping是与之相匹配的RequestMappingHandlerMapping。

(1)HandlerMapping.getHandler
源码8AbstractHandlerMapping.javapublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 留给子类实现的模板方法Object handler = getHandlerInternal(request);// ......// 构建HandlerExecutionChain对象HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);// ......return executionChain;
}

由 源码8 可知,HandlerMapping的getHandler的方法的核心逻辑有两步:由子类具体获取Handler的具体对象;根据Handler对象构建HandlerExecutionChain对象。

(2)getHandlerInternal

Debug进入getHandlerInternal方法,发现程序会进入RequestMappingHandlerMapping的直接父类RequestMappingInfoHandlerMapping中。

源码9RequestMappingInfoHandlerMapping.java@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);try {return super.getHandlerInternal(request);} finally {ProducesRequestCondition.clearMediaTypesAttribute(request);}
}

由 源码9 可知,RequestMappingInfoHandlerMapping中的getHandlerInternal也是直接使用super.getHandlerInternal方法调用父类AbstractHandlerMethodMapping中的getHandlerInternal方法。

源码10AbstractHandlerMethodMapping.java@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {// 获取本次请求的URI,并设置到request域中String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);request.setAttribute(LOOKUP_PATH, lookupPath);this.mappingRegistry.acquireReadLock();try {// 获取可以处理当前URI请求的HandlerMethod对象HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);// 创建全新的HandlerMethod对象return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);} finally {this.mappingRegistry.releaseReadLock();}
}

由 源码10 可知,getHandlerInternal方法首先会借助UrlPathHelper获取本次请求URI(示例项目是"/api/user/test"),并设置到request域中。随后调用lookupHandlerMethod方法,获取可以处理当前URI请求的HandlerMethod对象(实例项目是UserController的test方法)。

请求URI和HandlerMethod对象
注意,获取到HandlerMethod对象之后,还要调用createWithResolvedBean方法创建一个全新的HandlerMethod对象。为什么还要再次创建呢?

注意观察上图,发现HandlerMethod对象中的bean属性是一个字符串"userController",而不是BeanFactory中真实存在的UserController对象,其他属性也没有UserController对象的持有。

源码11HandlerMethod.javapublic HandlerMethod createWithResolvedBean() {Object handler = this.bean;if (this.bean instanceof String) {Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");String beanName = (String) this.bean;// 从BeanFactory中取出bean对象handler = this.beanFactory.getBean(beanName);}// 重新封装HandlerMethodreturn new HandlerMethod(this, handler);
}

由 源码11 可知,createWithResolvedBean方法的作用就是从BeanFactory中取出UserController对象并重新封装。

(3)getHandlerExecutionChain

回到 源码8 ,getHandlerInternal执行完后获得HandlerMethod对象,然后将该HandlerMethod对象传入getHandlerExecutionChain方法,组合跟该Handler相关的拦截器,并封装为执行链HandlerExecutionChain对象。

源码12AbstractHandlerMapping.javaprotected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {// 构造HandlerExecutionChain对象HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));// 获取请求路径String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);for (HandlerInterceptor interceptor : this.adaptedInterceptors) {if (interceptor instanceof MappedInterceptor) {// 匹配路径的拦截器处理MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {chain.addInterceptor(mappedInterceptor.getInterceptor());}} else {// 普通拦截器直接添加chain.addInterceptor(interceptor);}}return chain;
}

由 源码12 可知,getHandlerExecutionChain会根据HandlerInterceptor的类型分别进行处理,最终构造一个HandlerExecutionChain对象。

getHandlerExecutionChain方法
由上图可知,对示例项目来说,构造的HandlerExecutionChain对象组合了三个拦截器,分别是ConversionServiceExposingInterceptor、ResourceUrlProviderExposingInterceptor,以及示例项目自定义的CustomInterceptor

获得HandlerMethod对象并组合拦截器封装成HandlerExecutionChain对象之后,HandlerMapping的工作全部完成,接下来回到DispatcherServlet的doDispatch方法。

12.4.4.3 获取HandlerAdapter
源码13DispatcherServlet.javaprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// 处理文件上传请求// 获取可用的Handler// 获取HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// ......
}protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {if (adapter.supports(handler)) {return adapter;}}}// throw ex ...
}

由 源码13 可知,doDispatch方法的第三部分逻辑是获取可以处理当前请求的HandlerAdapter,匹配规则是通过HandlerAdapter的supports方法。

Debug至getHandlerAdapter方法,发现有4个可选的HandlerAdapter实现类对象:

getHandlerAdapter方法
由上图可知,示例项目选择的HandlerAdapter实现类对象是RequestMappingHandlerAdapter。

源码14AbstractHandlerMethodAdapter.java@Override
public final boolean supports(Object handler) {return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
源码15RequestMappingHandlerAdapter.java@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {return true;
}

由 源码14-15 可知,supports方法仅是判断Handler类是否是HandlerMethod对象,supportsInternal方法默认返回true,此处supports方法一定返回true,因此最终会选择可选HandlerAdapter对象集合中下标为0的对象,即RequestMappingHandlerAdapter。

12.4.4.4 回调拦截器
源码16DispatcherServlet.javaprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// 处理文件上传请求// 获取可用的Handler// 获取HandlerAdapter// 回调拦截器if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// ......
}boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {// 获取HandlerExecutionChain对象中组合的拦截器HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = 0; i < interceptors.length; i++) {HandlerInterceptor interceptor = interceptors[i];// 遍历拦截器,执行preHandle方法if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}}return true;
}

由 源码16 可知,doDispatch方法的第四部分逻辑是回调HandlerExecutionChain对象中组合的拦截器,首先会获取HandlerExecutionChain对象中组合的拦截器,然后遍历这些拦截器,执行其preHandle方法。

注意,每个拦截器preHandle方法如果有一个返回false,则applyPreHandle方法直接返回false,doDispatch方法直接结束不再执行下面的逻辑。

由 12.4.4.2 节的getHandlerExecutionChain方法分析可知,HandlerExecutionChain对象中组合的拦截器有3个,分别是ConversionServiceExposingInterceptor和ResourceUrlProviderExposingInterceptor,以及示例项目自定义的CustomInterceptor。

源码17ResourceUrlProviderExposingInterceptor.java@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {try {request.setAttribute(RESOURCE_URL_PROVIDER_ATTR, this.resourceUrlProvider);} // catch ...return true;
}
源码18ConversionServiceExposingInterceptor.java@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws ServletException, IOException {request.setAttribute(ConversionService.class.getName(), this.conversionService);return true;
}
源码19CustomInterceptor.javapublic class CustomInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("拦截器的preHandle方法执行了...");return true;}
}

有 源码17-19 以及CustomInterceptor类可知,四两个拦截器的preHandle方法一定会返回true,因此applyPreHandle方法返回true,doDispatch方法继续执行下面的逻辑。

12.4.4.5 执行Handler
源码20DispatcherServlet.javaprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// 处理文件上传请求// 获取可用的Handler// 获取HandlerAdapter// 回调拦截器// 实际执行Handlermv = ha.handle(processedRequest, response, mappedHandler.getHandler());// ......
}

由 源码20 可知,doDispatch方法的第五部分逻辑是调用HandlerAdapter的```handle``方法实际执行Handler,返回一个ModelAndView。

借助IDE,可以发现handle方法的实现在DispatcherServlet的父类AbstractHandlerMethodAdapter中,其handle方法又会转调子类RequestMappingHandlerAdapter的handleInternal方法。

(1)handleInternal
源码21AbstractHandlerMethodAdapter.java@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return handleInternal(request, response, (HandlerMethod) handler);
}
源码22RequestMappingHandlerAdapter.javaprivate boolean synchronizeOnSession = false;
@Override
protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;checkRequest(request);// 同步session的配置,默认为fasleif (this.synchronizeOnSession) {// ......} else {// 执行Handler方法mav = invokeHandlerMethod(request, response, handlerMethod);}// ......return mav;
}

由 源码21-22 可知,handleInternal方法最核心的逻辑是else代码块中的invokeHandlerMethod方法。

(2)invokeHandlerMethod

由于invokeHandlerMethod方法的篇幅很长,下面拆解来看。

(a)初始化参数绑定器

在WebMvc中,有两个和参数绑定相关的注解:@InitBinder和@ControllerAdvice。

@InitBinder注解可以单独声明在一个Controller类中,执行当前Controller类中的方法时,会先执行标注了@InitBinder注解的方法,初始化一些参数绑定器的逻辑。

@ControllerAdvice注解可以配合@InitBinder注解标注的方法,实现全局的参数绑定器预初始化。

源码23RequestMappingHandlerAdapter.javaprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 初始化参数绑定器WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);// ......
}public static final MethodFilter INIT_BINDER_METHODS = method ->AnnotatedElementUtils.hasAnnotation(method, InitBinder.class);
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {Class<?> handlerType = handlerMethod.getBeanType();Set<Method> methods = this.initBinderCache.get(handlerType);if (methods == null) {// 获取当前Controller中全部标注了@InitBinder注解的方法,并放到initBinderCache缓存中methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);this.initBinderCache.put(handlerType, methods);}List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();// 遍历标注了@ControllerAdvice注解的类this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> {if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {Object bean = controllerAdviceBean.resolveBean();// 遍历这些类中的全部标注了@InitBinder注解的方法,添加到initBinderMethods缓存中for (Method method : methodSet) {initBinderMethods.add(createInitBinderMethod(bean, method));}}});// 遍历当前Controller中全部标注了@InitBinder注解的方法// 将这些方法添加到initBinderMethods缓存中// 相当于合并了Controller类中和标注了@ControllerAdvice注解的类中的方法for (Method method : methods) {Object bean = handlerMethod.getBean();initBinderMethods.add(createInitBinderMethod(bean, method));}// 创建数据绑定工厂return createDataBinderFactory(initBinderMethods);
}

底层收集和执行@InitBinder注解的方法就是invokeHandlerMethod方法中的getDataBinderFactory方法。

由 源码23 可知,getDataBinderFactory方法会分别收集Controller类中标注了@InitBinder注解的方法、标注了@ControllerAdvice注解的类中标注了@InitBinder注解的方法,并合并起来共同组成参数绑定器。

示例项目中编写的两类绑定器都被收集起来了,如图:

自定义的参数绑定器
在UserController中定义的参数绑定器是:

@InitBinder
public void myDataBinder(WebDataBinder dataBinder) {// 如果参数是字符串类型,则去除字符串的前后空格dataBinder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}

如果暂时把@InitBinder注解注释掉,该绑定器不再生效,执行结果如下:

没有@InitBinder时有空格
如果添加了把@InitBinder注解注解,该绑定器生效,执行结果如下:

有@InitBinder时无空格

CustomAdvice类中定义的参数绑定器是:

@ControllerAdvice
public class CustomAdvice {@InitBinderpublic void customDataBinder(WebDataBinder dataBinder) {// 请求URL中的时间格式为yyyy-MM-dd HH:mm:ss// 自动绑定到Date属性参数中dataBinder.addCustomFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));}
}

如果时间类型的参数不按照 yyyy-MM-dd HH:mm:ss 的格式传递,在浏览器访问:http://127.0.0.1:8080/api/user/test?name=aaa&time=2024年2月29日 12时12分12秒,结果如下:

IllegalArgumentException: Parse attempt failed for value [2024年2月29日 12时12分12秒]

如果时间类型的参数按照 yyyy-MM-dd HH:mm:ss 的格式传递,在浏览器访问:http://127.0.0.1:8080/api/user/test?name=aaa&time=2024-02-29 12-12-12,则正常访问。

通过以上测试,发现自定义的参数绑定器均生效了。

(b)参数预绑定
源码24RequestMappingHandlerAdapter.javaprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 初始化参数绑定器 ...// 参数预绑定ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);// ......
}public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->(!AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) &&AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class));
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);Class<?> handlerType = handlerMethod.getBeanType();Set<Method> methods = this.modelAttributeCache.get(handlerType);if (methods == null) {// 获取当前Controller中全部标注了@ModelAttribute注解且没有被@RequestMapping注解标注的方法// 并放到modelAttributeCache缓存中methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);this.modelAttributeCache.put(handlerType, methods);}List<InvocableHandlerMethod> attrMethods = new ArrayList<>();// 遍历标注了@ControllerAdvice注解的类this.modelAttributeAdviceCache.forEach((controllerAdviceBean, methodSet) -> {if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {Object bean = controllerAdviceBean.resolveBean();// 遍历这些类中的全部标注了@ModelAttribute注解的方法,添加到attrMethods缓存中for (Method method : methodSet) {attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));}}});// 遍历当前Controller中全部标注了@ModelAttribute注解的方法// 将这些方法添加到attrMethods缓存中// 相当于合并了Controller类中和标注了@ControllerAdvice注解的类中的方法for (Method method : methods) {Object bean = handlerMethod.getBean();attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));}return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}

由 源码24 可知,getModelFactory方法和上一步的getDataBinderFactory方法的逻辑结构几乎一样,都是将Controller类中的标注了某个注解的方法,与全局类中标注了某个注解的方法合并起来。

不同的是,getDataBinderFactory方法找的是@InitBinder注解,而getModelFactory方法找的是@ModelAttribute注解;相同的是,全局类都标注@ControllerAdvice注解。

(c)创建方法执行对象
源码25RequestMappingHandlerAdapter.javaprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 初始化参数绑定器 ...// 参数预绑定 ...// 创建方法执行对象ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);// ......
}protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {return new ServletInvocableHandlerMethod(handlerMethod);
}

由 源码25 可知,createInvocableHandlerMethod方法只是将HandlerMethod对象二度封装为ServletInvocableHandlerMethod对象。

(d)执行Controller的方法invokeAndHandle
源码26RequestMappingHandlerAdapter.javaprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 初始化参数绑定器 ...// 参数预绑定 ...// 创建方法执行对象 ...// 创建ModelAndView的容器ModelAndViewContainer ...(不重要)// 对异步请求的支持 ...(不重要)// 执行Controller的方法invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}// 包装ModelAndViewreturn getModelAndView(mavContainer, modelFactory, webRequest);} // finally ...
}

由 源码26 可知,创建方法执行对象ServletInvocableHandlerMethod后,又创建了ModelAndView的容器ModelAndViewContainer,以及处理了对异步请求的支持,最后调用方法执行对象ServletInvocableHandlerMethod的invokeAndHandle方法执行Handler方法。

由于invokeAndHandle方法的篇幅很长,下面拆解来看。

  • (i)反射执行Controller方法
源码27ServletInvocableHandlerMethod.javapublic void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 反射执行Controller方法Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);// ......
}
源码28InvocableHandlerMethod.javapublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 获取参数值列表Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);// logger ...return doInvoke(args);
}protected Object doInvoke(Object... args) throws Exception {ReflectionUtils.makeAccessible(getBridgedMethod());try {// 利用反射机制执行Handlerreturn getBridgedMethod().invoke(getBean(), args);}// catch ...
}

由 源码27-28 可知,invokeAndHandle方法调用invokeForRequest方法,会先收集好执行Handler方法所需的参数列表,然后利用反射机制执行目标Handler方法。doInvoke方法执行完毕后,意味着项目中编写的Controller类的方法已经执行完毕。

  • (ii)处理方法返回值
源码29ServletInvocableHandlerMethod.javapublic void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 反射执行Controller方法Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);// ......// 处理方法返回值try {this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);} // catch ...
}
源码30HandlerMethodReturnValueHandlerComposite.java@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {// 筛选出可以处理当前Controller返回的HandlerMethodReturnValueHandler对象HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {// throw ......}handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {boolean isAsyncValue = isAsyncReturnValue(value, returnType);// 遍历HandlerMethodReturnValueHandler集合for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {continue;}// 找出支持当前返回值类型的if (handler.supportsReturnType(returnType)) {return handler;}}return null;
}

由 源码29-30 可知,处理方法返回值的handleReturnValue方法会先筛选出可以处理当前Controller返回的HandlerMethodReturnValueHandler对象,再调用其handleReturnValue方法处理返回值。

筛选HandlerMethodReturnValueHandler对象的selectHandler方法,是利用for循环遍历HandlerMethodReturnValueHandler对象集合,找出其中支持当前返回值类型的HandlerMethodReturnValueHandler对象。

借助Debug工具,可以发现可选的HandlerMethodReturnValueHandler对象有15个,示例项目选择的是RequestResponseBodyMethodProcessor。

筛选HandlerMethodReturnValueHandler对象
在示例项目中,UserController的test方法上标注了@ResponseBody注解,因此响应的是JSON数据。处理JSON数据响应的底层实现就是RequestResponseBodyMethodProcessor。

源码31RequestResponseBodyMethodProcessor.java@Override
public boolean supportsReturnType(MethodParameter returnType) {return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||returnType.hasMethodAnnotation(ResponseBody.class));
}

由 源码31 可知,RequestResponseBodyMethodProcessor中supportsReturnType方法就是判断方法所在类或者方法上是否标注@ResponseBody注解。再一次印证RequestResponseBodyMethodProcessor是处理JSON数据响应的底层实现。

  • (iii)处理JSON数据响应

获取RequestResponseBodyMethodProcessor对象后,调用其handleReturnValue方法正式处理返回值。

源码32RequestResponseBodyMethodProcessor.java@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {mavContainer.setRequestHandled(true);ServletServerHttpRequest inputMessage = createInputMessage(webRequest);ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);// 使用JSON序列化的方式将方法返回的数据转化为文本// 并写入HttpServletResponse的输出流中writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

由 源码32 可知,handleReturnValue方法会执行writeWithMessageConverters方法,使用JSON序列化的方式将方法返回的数据转化为文本,并写入HttpServletResponse的输出流中。

  • (iiii)处理视图返回

示例项目中是响应JSON数据,但在实际开发中也有可能是响应视图。对于返回视图和返回JSON数据,底层使用的HandlerMethodReturnValueHandler并不相同。

如果一个Controller方法要跳转视图,则方法的返回值一定是一个字符串,并且方法和类上都没有标注@ResponseBody注解,这种情况下底层使用的HandlerMethodReturnValueHandler是ViewNameMethodReturnValueHandler。

源码33ViewNameMethodReturnValueHandler.java@Override
public boolean supportsReturnType(MethodParameter returnType) {Class<?> paramType = returnType.getParameterType();// 判断返回值类型是否是CharSequencereturn (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {if (returnValue instanceof CharSequence) {String viewName = returnValue.toString();// 将String类型的返回值作为逻辑视图名放入ModelAndView容器中mavContainer.setViewName(viewName);if (isRedirectViewName(viewName)) {mavContainer.setRedirectModelScenario(true);}} else if (returnValue != null) {// should not happen// throw ...}
}

由 源码33 可知,处理视图响应的handleReturnValue方法会将String类型的返回值作为逻辑视图名称,并放入ModelAndView容器ModelAndViewContainer对象中。

至此,invokeAndHandle方法执行完毕,Controller类中的方法已执行,ModelAndViewContainer中已经封装了响应视图名称,或者将需要响应的数据写入了HttpServletResponse中。

(e)包装ModelAndView
源码34RequestMappingHandlerAdapter.javaprotected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 初始化参数绑定器 ...// 参数预绑定 ...// 创建方法执行对象 ...// 创建ModelAndView的容器ModelAndViewContainer ...(不重要)// 对异步请求的支持 ...(不重要)// 执行Controller的方法 ...// 包装ModelAndViewreturn getModelAndView(mavContainer, modelFactory, webRequest);} // finally ...
}private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {// ......// 取出容器中的ModelMapModelMap model = mavContainer.getModel();// 取出视图名称,构造ModelAndViewModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());// ......return mav;
}

由 源码34 可知,invokeHandlerMethod方法的最后一步是调用getModelAndView方法包装ModelAndView对象。在该方法在中会从上一步封装的ModelAndViewContainer对象中取出ModelMap和视图名称,构造成ModelAndView对象并返回。

简言之,getModelAndView方法完成了ModelAndViewContainer对象到ModelAndView对象的转换。

至此,invokeHandlerMethod方法执行完毕,HandlerAdapter的工作全部完成,流程回到DispatcherServlet的doDispatch方法。

12.4.4.6 再次回调拦截器
源码35DispatcherServlet.javaprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// 处理文件上传请求 ...// 获取可用的Handler ...// 获取HandlerAdapter ...// 回调拦截器 ...// 执行Handler ...// 再次回调拦截器mappedHandler.applyPostHandle(processedRequest, response, mv);// ......
}
源码36HandlerExecutionChain.javavoid applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {// 此处是倒序回调for (int i = interceptors.length - 1; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];interceptor.postHandle(request, response, this.handler, mv);}}
}

由 源码35-36 可知,再次回调拦截器的applyPostHandle方法和 12.4.4.4 节的applyPreHandle的逻辑是几乎一样,不一样的是,这次回调拦截器的顺序是反过来的,调用的是拦截器的postHandle方法。

12.4.4.7 处理视图、解析异常
源码37DispatcherServlet.javaprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// 处理文件上传请求 ...// 获取可用的Handler ...// 获取HandlerAdapter ...// 回调拦截器 ...// 执行Handler ...// 再次回调拦截器 ...mappedHandler.applyPostHandle(processedRequest, response, mv);} // catch ...// 处理视图、解析异常processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);// ......
}

由 源码37 可知,DispatcherServlet的doDispatch方法的最后一个关键步骤是processDispatchResult方法,该方法会进行视图处理,以及解析整个请求处理中抛出的异常。

该方法分为三个步骤,下面拆解来看。

(1)处理异常
源码38DispatcherServlet.javaprivate void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;// 处理异常if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {// logger ...mv = ((ModelAndViewDefiningException) exception).getModelAndView();} else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// ......
}

由 源码38 可知,processDispatchResult会根据异常的类型做不同的处理。

DispatcherServlet在处理客户端发起的请求时,中间调用Controller或者Service等组件时抛出的异常都几乎不可能是ModelAndViewDefiningException(前面的源码分析也没有见过这个异常),因此if代码块不重要。

在else代码块中,会调用processHandlerException方法,该方法才是处理异常的核心方法。

源码39DispatcherServlet.javaprotected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {// ......// 从HandlerExceptionResolver集合中找到可以处理当前异常的// 构造成ModelAndView对象并返回ModelAndView exMv = null;if (this.handlerExceptionResolvers != null) {for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {exMv = resolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}}// ......throw ex;
}

由 源码39 可知,processHandlerException方法会从HandlerExceptionResolver集合中找到可以处理当前异常的,构造成ModelAndView对象并返回。

在示例项目中,CustomAdvice类标注了@ControllerAdvice注解,并且声明了标注@ExceptionHandler注解的方法,可实现全局统一异常处理。

@ControllerAdvice
public class CustomAdvice {@ExceptionHandler({Exception.class})public String customExceptionHandler(Exception ex) {System.out.println("自定义异常发生了," + ex.getMessage());return "自定义异常返回";}}

在UserController的test方法加一行代码:int i = 1/0,发起请求时一定会抛出异常,控制台的结果如下:

自定义异常发生了,/ by zero

可见,全局统一异常处理生效了。

(2)渲染视图
源码40DispatcherServlet.javaprivate void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {// 处理异常 ...// 渲染视图if (mv != null && !mv.wasCleared()) {render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}// ......
}protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// 国际化处理 ...View view;String viewName = mv.getViewName();if (viewName != null) {// 根据视图名解析出视图view = resolveViewName(viewName, mv.getModelInternal(), locale, request);if (view == null) {// throw ...}} else {// 否则,直接获取视图;如果还没有视图,则抛出异常view = mv.getView();if (view == null) {// throw ...}}// logger ...try {if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}// 渲染视图view.render(mv.getModelInternal(), request, response);} // catch ...
}

由 源码40 可知,无论是正常响应还是抛出异常,最终都会生成一个ModelAndView对象,紧接着就要进行视图的渲染,而渲染视图的核心方法是render方法。

渲染视图的render方法会从ModelAndView中获取逻辑视图的名称,如果有名称则借助ViewResolver去匹配视图,如果成功匹配到则返回,如果匹配不到则抛出异常。匹配生成视图View对象后,执行view.render方法实际渲染视图。

(3)第三次回调拦截器
源码41DispatcherServlet.javaprivate void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {// 处理异常 ...// 渲染视图 ...// 第三次回调拦截器if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, null);}
}
源码42HandlerExecutionChain.javavoid triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {// 遍历拦截器,执行其afterCompletion方法for (int i = this.interceptorIndex; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];try {interceptor.afterCompletion(request, response, this.handler, ex);} // catch ...}}
}

由 源码41-42 可知,视图渲染完成后,最后的收尾动作是回调所有拦截器的afterCompletion方法。

这已经是第三次回调拦截器了,第一次执行拦截器的preHandle方法,第二次倒序执行拦截器的postHandle方法,第三次执行拦截器的afterCompletion方法。其中,第一次是在Handler方法执行之前执行的,第二次和第三次是在之后执行的。

拦截器执行流程
因此,控制台的输出如下:

拦截器的preHandle方法执行了...
请求参数 name=aaa
请求参数 time=Thu Feb 29 12:12:12 CST 2024
拦截器的postHandle方法执行了...
拦截器的afterCompletion方法执行了...

至此,processDispatchResult方法执行完毕,DispatcherServlet的doDispatch方法也执行完毕,一次完整的DispatcherServlet请求处理和响应完成了。

12.4.5 DispatcherServlet工作流程总结

DispatcherServlet工作全流程示意图如下:

DispatcherServlet工作全流程

1. 客户端向服务端发起请求,由DispatcherServlet接收请求;
2. DispatcherServlet委托HandlerMapping,根据本次请求的URL匹配合适的Controller方法;
3. HandlerMapping找到合适的Controller方法后,组合可以应用于当前请求的拦截器,并封装为一个HandlerExecutionChain对象返回给DispatcherServlet;
4. DispatcherServlet接收到HandlerExecutionChain对象后,委托HandlerAdapter,将该请求转发给选定的Handler;
5. Handler接收到请求后,实际执行Controller方法;
6. Controller方法执行完毕后返回一个ModelAndView对象给HandlerAdapter;
7. HandlerAdapter接收到ModelAndView对象后返回给DispatcherServlet;
8. DispatcherServlet接收到ModelAndView对象后,委托ViewResolver进行视图渲染;
9. ViewResolver视图渲染完成后返回给DispatcherServlet,由DispatcherServlet负责响应视图。

12.5 小结

第12章到此就梳理完毕了,本章的主题是:SpringBoot整合WebMvc。回顾一下本章的梳理的内容:

(三十六)SpringBoot整合WebMvc(一)@Controller控制器装配原理
(三十七)SpringBoot整合WebMvc(二)DispatcherServlet的工作全流程

更多内容请查阅分类专栏:SpringBoot源码解读与原理分析

第13章主要梳理:SpringBoot整合WebFlux。主要内容包括:

  • 响应式编程与Reactive;
  • SpringBoot整合WebFlux的快速使用;
  • SpringBoot整合WebFlux的核心自动装配;
  • DispatcherHandler的工作全流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/718378.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

优优嗨聚集团:美团代运营服务,商家增长的新引擎

在当今数字化时代&#xff0c;线上平台已成为商家拓展业务、提升品牌影响力的重要渠道。美团作为国内领先的本地生活服务平台&#xff0c;拥有庞大的用户群体和丰富的商业资源。然而&#xff0c;对于许多商家而言&#xff0c;如何在美团平台上进行有效运营&#xff0c;实现业务…

【LeetCode:232. 用栈实现队列 + 栈 | 队列】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

力扣74. 搜索二维矩阵(二分查找)

Problem: 74. 搜索二维矩阵 文章目录 题目描述思路复杂度Code 题目描述 思路 思路1&#xff1a;映射为一维数组二分查找 1.由于题目矩阵中的元素整体是升序的&#xff0c;我们可以将其放置在一个大小为 m n m \times n mn的一维数组array中进行二分查找 2.对应的映射关系是ar…

NACOS在Windows和Linux下的安装教程

目录 1、Windows安装 1.1、下载安装包 1.2、解压 1.3、端口配置 1.4、启动 1.5、访问 2、Linux安装 2.1、安装JDK 2.2、上传安装包 2.3、解压 2.4、端口配置 2.5、启动 3、Nacos的依赖 1、Windows安装 开发阶段采用单机安装即可。 1.1、下载安装包 在Nacos的Git…

Vue+SpringBoot打造图书借阅系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 登陆注册模块2.2 图书管理模块2.3 图书评论模块2.4 图书预定模块2.5 图书资讯模块 三、系统设计3.1 系统结构设计3.1.1登陆注册模块的结构设计3.1.2图书管理模块的结构设计3.1.3图书评论模块的结构设计3.1.4图书预定模块…

clickhouse 随心所欲的聚合模型-AggregatingMergeTree

clickhouse 强大的 MergeTree 系列引擎令人信服&#xff0c;其 ReplacingMergeTree、SummingMergeTree 在数据唯一性和汇总场景中表现非凡。但你是否还有保留最小(大)、平均等预聚合需求&#xff0c;甚至在一个模型中既有唯一性语意也有汇总、最小、最大、平均值语意该如何处理…

【C语言】剖析qsort函数的实现原理

主页&#xff1a;17_Kevin-CSDN博客 专栏&#xff1a;《C语言》 本文将从回调函数&#xff0c;qsort函数的应用&#xff0c;qsort函数的实现原理三个方面进行讲解&#xff0c;请自行跳转至相对位置进行阅读~ 目录 回调函数 qsort函数的应用 qsort函数实现原理 回调函数 什…

独立游戏《星尘异变》UE5 C++程序开发日志1——项目与代码管理

写在前面&#xff1a;本日志系列将会向大家介绍在《星尘异变》这款模拟经营游戏&#xff0c;在开发时用到的与C相关的泛用代码与算法&#xff0c;主要记录UE5C与原生C的用法区别&#xff0c;以及遇到的问题和解决办法&#xff0c;因为这是我本人从ACM退役以后第一个从头开始的项…

冒泡排序 和 qsort排序

目录 冒泡排序 冒泡排序部分 输出函数部分 主函数部分 总代码 控制台输出显示 总代码解释 冒泡排序优化 冒泡排序 主函数 总代码 代码优化解释 qsort 排序 qsort 的介绍 使用qsort排序整型数据 使用qsort排序结构数据 冒泡排序 首先&#xff0c;我先介绍我的冒泡…

模糊搜索小案例

C#窗体实现数据录入与模糊搜索小案例 记录一下 主要代码 private void button1_Click(object sender, EventArgs e){string name textBox1.Text;string hometown textBox4.Text;string school textBox6.Text;string sex textBox5.Text;string lat textBox3.Text;string …

c#打印BarTend标签提示:具名数据源没有cuckoo*具名数据(解决)

c#打印BarTend标签提示&#xff1a;具名数据源没有cuckoo*具名数据&#xff08;解决&#xff09; 今天咕咕更新打印模板的时候遇到的问题&#xff0c;就是在模版中配置了字段名&#xff0c;但是启动c#应用&#xff0c;后端发送json数据打印的时候c#报错提示&#xff0c;没有在…

python 小游戏《2048》字符版非图形界面

参考链接&#xff1a; 闲谈2048小游戏和数组的旋转及翻转和转置 目录 2048 一、方阵类 二、随机插入1或2 三、 合并和递增 四、 判断和移动 五、 键盘控制 完整源代码 玩法过程 2048 上回说到2048小游戏中数组的各种旋转、翻转的方法&#xff0c;就是为代码编程作准…

第十六天-爬虫selenium库

目录 1.介绍 2.使用 selenium 1.安装 2.使用 1.测试打开网页&#xff0c;抓取雷速体育日职乙信息 2.通过xpath查找 3.输入文本框内容 send_keys 4.点击事件 click 5.获取网页源码&#xff1a; 6.获取cookies 7.seleniumt提供元素定位方式&#xff1a;8种 8.控制浏览…

CSS3单独制作移动端页面布局方式(流式布局、flex弹性布局)

目录 1. 流式布局(百分比布局)2. flex弹性布局(强烈推荐)2.1 介绍2.2 Flex容器常见属性2.2.1 flex-direction2.2.2 justify-content2.2.3 flex-wrap2.2.4 align-items2.2.5 align-content2.2.6 flex-flow 2.3 Flex项目常见属性2.3.1 flex2.3.2 align-self和order 1. 流式布局(百…

银河麒麟之Workstation安装

一、VMware Workstation简介 VMware Workstation是一款由VMware公司开发的虚拟化软件&#xff0c;它允许用户在一台物理计算机上运行多个操作系统&#xff0c;并在每个操作系统中运行多个虚拟机。VMware Workstation提供了一个可视化的用户界面&#xff0c;使用户可以轻松创建、…

程序环境和预处理(2)

文章目录 3.2.7 命名约定 3.3 #undef3.4 命令行定义3.5 条件编译3.6 文件包含3.6.1 头文件被包含的方式3.6.2 嵌套文件包含 4. 其他预处理指令 3.2.7 命名约定 一般来讲函数和宏的使用语法很相似&#xff0c;所以语言本身没法帮我们区分二者&#xff0c;那我们平时的一个习惯是…

Redis安全加固策略:绑定Redis监听的IP地址 修改默认端口 禁用或者重命名高危命令

Redis安全加固策略&#xff1a;绑定Redis监听的IP地址 & 修改默认端口 & 禁用或者重命名高危命令 1.1 绑定Redis监听的IP地址1.2 修改默认端口1.3 禁用或者重命名高危命令1.4 附&#xff1a;redis配置文件详解&#xff08;来源于网络&#xff09; &#x1f496;The Beg…

驱动开发面试复习

创建字符设备 1 创建设备号 alloc_chrdev_region 2.创建cdev cdev_init 3.添加一个 cdev,完成字符设备注册到内核 cdev_add 4.创建类 class_create 5.创建设备 device_create 1.内核空间与用户空间数据 copy_from_user 和copy_to_user 俩个函数来完成。 copy_from_user 函数…

618快递准点到达,别忘了感谢它!

进入6月以来&#xff0c;全国快递日均业务量飞速上涨。 虽然618大促是电商的主场&#xff0c;但作为不可或缺的物流环节&#xff0c;为了这场年中大考&#xff0c;快递企业在此期间也使尽浑身解数&#xff0c;竞相比拼配送速度。那么&#xff0c;为了更快的时效&#xff0c;快递…

【Wio Terminal】使用WiFi(3)- Wi-F的高级使用

使用WiFi&#xff08;3&#xff09; Wi-F的高级使用HTTPClient 的使用HTTP GETHTTPs GETHTTP POSTWebServerHTTP Authentication Web ServerDNSServermDNSmDNS-SDWiFiManager Wi-F的高级使用 本节介绍了一些WiFi的高级库用法&#xff0c;如HTTPClient、DNSServer和WebServer库…