SpringMVC 请求流程源码分析

  • 三哥

内容来自【自学星球】

欢迎大家来了解我的星球,和星主(也就是我)一起学习 Java ,深入 Java 体系中的所有技术。我给自己定的时间是一年,无论结果如何,必定能给星球中的各位带来点东西。

想要了解更多,欢迎访问👉:自学星球

--------------SSM系列源码文章及视频导航--------------

创作不易,望三连支持!

SSM源码解析视频

👉点我

Spring

  1. Spring 中注入 Bean 的各种骚操作做
  2. Spring 中Bean的生命周期及后置处理器使用
  3. Spring 中容器启动分析之refresh方法执行之前
  4. Spring refresh 方法分析之一
  5. Spring refresh 方法之二 invokeBeanFactoryPostProcessors 方法解析
  6. Spring refresh 方法分析之三
  7. Spring refresh 方法之四 finishBeanFactoryInitialization 分析
  8. Spring AOP源码分析一
  9. Spring AOP源码分析二
  10. Spring 事务源码分析

SpringMVC

  1. SpringMVC 启动流程源码分析
  2. SpringMVC 请求流程源码分析

MyBatis

  1. MyBatis 源码分析之 SqlSessionFactory 创建
  2. MyBatis 源码分析之 SqlSession 创建
  3. MyBatis 源码分析之 Mapper 接口代理对象生成及方法执行
  4. MyBatis 源码分析之 Select 语句执行(上)
  5. MyBatis 源码分析之 Select 语句执行(下)
  6. MyBatis 源码分析一二级缓存

---------------------【End】--------------------

一、SpringMVC的请求流程

我们知道 SpringMVC 只配置了一个 Servlet 即 DispatcherServlet ,它会拦截我们所有的 URL 请求统一在其内部进行调用处理。

所以我们分析 SpringMVC 的请求流程理所应当会来到 DispatcherServlet 类中的 service 方法。

但本次我想从 doDispatch 方法开始分析,因为最终不论是何种请求方法都会来到这个方法进行处理调用。

org.springframework.web.servlet.DispatcherServlet#doDispatch

protected 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 {
            // 这里判断当前请求是否为一个文件请求,这里的判断方式就是要求当前请求满足两点:①请求
            // 方式是POST;②判断contentType是否以multipart/开头。如果满足这两点,那么就认为当前
            // 请求是一个文件请求,此时会将当前请求的request对象封装为一个
            // MultipartHttpServletRequest对象,这也是我们在定义文件请求的Controller时
            // 能够将request参数写为MultipartHttpServletRequest的原因。这里如果不是文件请求,
            // 那么会将request直接返回。
            processedRequest = checkMultipart(request);
            // 这里判断原始request与转换后的request是否为同一个request,如果不是同一个,则说明
            // 其是一个文件请求
            multipartRequestParsed = (processedRequest != request);
            // 这里getHandler()方法就是通过遍历当前Spring容器中所有定义的HandlerMapping对象,
            // 通过调用它们的getHandler()方法,看当前的HandlerMapping能否将当前request映射
            // 到某个handler,也就是某个Controller方法上,如果能够映射到,则说明该handler能够
            // 处理当前请求
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                // 如果每个HandlerMapping都无法找到与当前request匹配的handler,那么就认为
                // 无法处理当前请求,此时一般会返回给页面404状态码
                noHandlerFound(processedRequest, response);
                return;
            }

            // 通过找到的handler,然后在当前Spring容器中找到能够支持将当前request请求适配到
            // 找到的handler上的HandlerAdapter。这里需要找到这样的适配器的原因是,我们的handler
            // 一般都是Controller的某个方法,其是一个Java方法,而当前request则是一种符合http
            // 协议的请求,这里是无法直接将request直接应用到handler上的,因而需要使用一个适配器,
            // 也就是这里的HandlerAdapter。由于前面获取handler的时候,不同的HandlerMapping
            // 所产生的handler是不一样的,比如ReqeustMappingHandlerMapping产生的handler是一个
            // HandlerMethod对象,因而这里在判断某个HandlerAdapter是否能够用于适配当前handler的
            // 时候是通过其supports()方法进行的,比如RequestMappingHandlerAdapter就是判断
            // 当前的handler是否为HandlerMethod类型,从而判断其是否能够用于适配当前handler。
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            // 这里判断请求方式是否为GET或HEAD请求,如果是这两种请求的一种,那么就会判断
            // 当前请求的资源是否超过了其lastModified时间,如果没超过,则直接返回,
            // 并且告知浏览器可以直接使用缓存来处理当前请求
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, 
                                                       mappedHandler.getHandler());
                if (logger.isDebugEnabled()) {
                    logger.debug("Last-Modified value for [" + getRequestUri(request) 
                                 + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response)
                    .checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            // 这里在真正处理请求之前会获取容器中所有的拦截器,也就是HandlerInterceptor对象,
            // 然后依次调用其preHandle()方法,如果某个preHandle()方法返回了false,那么就说明
            // 当前请求无法通过拦截器的过滤,因而就会直接出发其afterCompletion()方法,只有在
            // 所有的preHandle()方法都返回true时才会认为当前请求是能够使用目标handler进行处理的
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // 在当前请求通过了所有拦截器的预处理之后,这里就直接调用HandlerAdapter.handle()
            // 方法来处理当前请求,并且将处理结果封装为一个ModelAndView对象。该对象中主要有两个
            // 属性:view和model,这里的view存储了后续需要展示的逻辑视图名或视图对象,而model
            // 中则保存了用于渲染视图所需要的属性
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            // 如果当前是一个异步任务,那么就会释放当前线程,等待异步任务处理完成之后才将
            // 任务的处理结果返回到页面
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            // 如果返回的ModelAndView对象中没有指定视图名或视图对象,那么就会根据当前请求的url
            // 来生成一个视图名
            applyDefaultViewName(processedRequest, mv);
            // 在请求处理完成之后,依次调用拦截器的postHandle()方法,对请求进行后置处理
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        } catch (Exception ex) {
            dispatchException = ex;
        } catch (Throwable err) {
            // 将处理请求过程中产生的异常封装到dispatchException中
            dispatchException = new NestedServletException("Handler dispatch failed", 
                                                           err);
        }

        // 这里主要是请求处理之后生成的视图进行渲染,也包括出现异常之后对异常的处理。
        // 渲染完之后会依次调用拦截器的afterCompletion()方法来对请求进行最终处理
        processDispatchResult(processedRequest, response, mappedHandler, mv, 
                              dispatchException);
    } catch (Exception ex) {
        // 如果在上述过程中任意位置抛出异常,包括渲染视图时抛出异常,那么都会触发拦截器的
        // afterCompletion()方法的调用
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    } catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                               new NestedServletException("Handler processing failed", err));
    } finally {
        // 如果当前异步任务已经开始,则触发异步任务拦截器的afterConcurrentHandlingStarted()方法
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, 
                                                                  response);
            }
        } else {
            // 如果当前是一个文件请求,则清理当前request中的文件数据
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

该方法是用户请求的重要方法,主要执行流程有:

  1. 检查是否是multipart请求,如果是则将请求封装成上传类型的请求MultipartHttpServletRequest,并置标记multipartRequestParsed为true。(使用了组件MultipartResolver)
  2. 获取handler处理器链(HandlerExecutionChain),包含了与当前请求对应的处理器Handler和拦截器Interceptor集。(使用了组件HandlerMapping)
  3. 处理Get和Head请求的缓存问题(Last-Modified),如果未过期直接返回。
  4. 调用Interceptor集的preHanlde。
  5. 由HandlerAdapter处理请求(controller层方法的执行入口)。(使用了组件HandlerAdapter)
  6. 检查请求是否是异步的。如果是异步请求直接返回。
  7. 如果ModelAndView中没有设置view,则采取默认view进行设置。
  8. 调用Interceptor集的postHandler。
  9. 调用processDiapatchResult对执行结果或异常进行处理。即使执行过程抛出异常,也会在catch语句中捕捉并调用Interceptor的afterCompletion方法。(使用了组件LocaleResolver, ViewResolver和ThemeResolver(view#render))
  10. finally语句块进行收尾,异步请求回调MapperHandler的applyAfterConcurrentHandlingStarted方法,multipart请求删除上传资源。

下面我们再来细聊 doDispatch 中的几个正要流程

1.1 getHandler

该方法分析过了,在 4.1 小结中

1.2 getHandlerAdapter

org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        // 遍历当前容器中所有的HandlerAdapter,通过调用其supports()方法,判断当前HandlerAdapter
        // 能否用于适配当前的handler,如果可以,则直接使用该HandlerAdapter
        for (HandlerAdapter ha : this.handlerAdapters) {
            if (logger.isTraceEnabled()) {
                logger.trace("Testing handler adapter [" + ha + "]");
            }
            if (ha.supports(handler)) {
                return ha;
            }
        }
    }

    // 如果找不到任何一个HandlerAdapter用于适配当前请求,则抛出异常
    throw new ServletException("No adapter for handler [" + handler 
                               + "]: The DispatcherServlet configuration needs to include a HandlerAdapter" 
                               + " that supports this handler");
}

该方法会去遍历事先注册好的 HandlerAdapter 实现类,去调用实现类的 supports 方法判断是否支持当前的 handler实例的处理,一旦找到匹配的 HandlerAdapter 实现类,直接返回 HandlerAdapter 实例。

通过 DispatcherServlet.properties 默认配置文件可知,HandlerAdapter 默认有三种值:

  • org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter
  • org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter
  • org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

这里,我们关注 RequestMappingHandlerAdapter 。

该类继承结构体

在这里插入图片描述

调用 supports 方法,会来到下面实现类:

org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#supports

public final boolean supports(Object handler) {
    // 判断当前handler是否为HandlerMethod类型,并且判断supportsInternal()方法返回值是否为true,
    // 这里supportsInternal()方法是提供给子类实现的一个方法,对于RequestMappingHandlerAdapter
    // 而言,其返回值始终是true,因为其只需要处理的handler是HandlerMethod类型的即可
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#supportsInternal

protected boolean supportsInternal(HandlerMethod handlerMethod) {
    // 这里RequestMappingHandlerAdapter只是对supportsInternal()返回true,因为其只需要
    // 处理的handler类型是HandlerMethod类型即可
    return true;
}

1.3 applyPreHandle

org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 获取容器中所有拦截器
    HandlerInterceptor[] interceptors = getInterceptors();
    // 拦截器集合不为空
    if (!ObjectUtils.isEmpty(interceptors)) {
        // 遍历拦截器,调用 preHandle 方法
        for (int i = 0; i < interceptors.length; i++) {
            HandlerInterceptor interceptor = interceptors[i];
            if (!interceptor.preHandle(request, response, this.handler)) {
                triggerAfterCompletion(request, response, null);
                return false;
            }
            this.interceptorIndex = i;
        }
    }
    return true;
}

拦截器执行拦截,对客户端请求requset进行拦截,一旦某个HandlerInterceptor的preHandle方法返回false,则直接return,不在对请求在进行处理

1.4 handle

该方法是处理具体的 Controller 方法的,其主要流程有以下几点:

  • 获取当前 Spring 容器中在方法上配置的标注了 @ModelAttribute 但是没标注 @RequestMapping 注解的方法,在真正调用具体的 handler 之前会将这些方法依次进行调用;
  • 获取当前 Spring 容器中标注了 @InitBinder 注解的方法,调用这些方法以对一些用户自定义的参数进行转换并且绑定;
  • 根据当前 handler 的方法参数标注的注解类型,如 @RequestParam@ModelAttribute 等,获取其对应的 ArgumentResolver,以将 request 中的参数转换为当前方法中对应注解的类型;
  • 配合转换而来的参数,通过反射调用具体的 handler 方法;
  • 通过 ReturnValueHandler 对返回值进行适配,比如 ModelAndView 类型的返回值就由 ModelAndViewMethodReturnValueHandler 处理,最终将所有的处理结果都统一封装为一个 ModelAndView 类型的返回值,这也是 RequestMappingHandlerAdapter.handle() 方法的返回值类型。

源码:

org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle

public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {

    return handleInternal(request, response, (HandlerMethod) handler);
}

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal

protected ModelAndView handleInternal(HttpServletRequest request,
                                      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    // 检测当前请求,验证请求方法合法性和session合法性
    checkRequest(request);

    // Execute invokeHandlerMethod in synchronized block if required.
    // 根据synchronizeOnSession值判断当前是否需要支持在同一个session中只能线性地处理请求
    if (this.synchronizeOnSession) {
        // 获取当前请求的session对象
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 获取最佳互斥锁,即同步当前回话对象;如未能获取到互斥锁,将返回HttpSession对象本身
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                // 对HandlerMethod进行参数等的适配处理,并调用目标handler
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No HttpSession available -> no mutex necessary
            // 即无最佳互斥锁,也未能获取到HttpSession,则当前会话无需串行化访问
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    }
    else {
        // No synchronization on session demanded at all...
        // 如果当前不需要对session进行同步处理,则直接对HandlerMethod进行适配
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }

    // 相应信息不包含Cache-Control
    // 判断当前请求头中是否包含Cache-Control请求头,如果不包含,则对当前response进行处理,
    // 为其设置过期时间
    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        // 如果当前SessionAttribute中存在配置的attributes,则为其设置过期时间。
        // 这里SessionAttribute主要是通过@SessionAttribute注解生成的
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        }
        else {
            // 如果当前不存在SessionAttributes,则判断当前是否存在Cache-Control设置,
            // 如果存在,则按照该设置进行response处理,如果不存在,则设置response中的
            // Cache的过期时间为-1,即立即失效
            prepareResponse(response);
        }
    }

    return mav;
}

该方法执行流程主要分为两部分

  • 判断当前是否对 session 进行同步处理,如果需要,则对其调用进行加锁,不需要则直接调用
  • 判断请求头中是否包含 Cache-Control 请求头,如果不包含,则设置其 Cache 立即失效

可以看到该方法的主要功能在 invokeHandlerMethod 方法,源码如下:

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod

protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                                           HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        // 获取容器中全局配置的InitBinder和当前HandlerMethod所对应的Controller中
        // 配置的InitBinder,用于进行参数的绑定
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        // 获取容器中全局配置的ModelAttribute和当前当前HandlerMethod所对应的Controller
        // 中配置的ModelAttribute,这些配置的方法将会在目标方法调用之前进行调用
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        // 将handlerMethod封装为一个ServletInvocableHandlerMethod对象,
        // 该对象用于对当前request的整体调用流程进行了封装
        ServletInvocableHandlerMethod invocableMethod =
            createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
            // 设置当前容器中配置的所有ArgumentResolver
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            // 设置当前容器中配置的所有ReturnValueHandler
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        // 将前面创建的WebDataBinderFactory设置到ServletInvocableHandlerMethod中
        invocableMethod.setDataBinderFactory(binderFactory);
        // 设置ParameterNameDiscoverer,该对象将按照一定的规则获取当前参数的名称
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
        // 创建ModelAndViewContainer,并初始化Model对象
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        // 这里initModel()方法主要作用是调用前面获取到的@ModelAttribute标注的方法,
        // 从而达到@ModelAttribute标注的方法能够在目标Handler调用之前调用的目的
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        // 获取当前的AsyncWebRequest,这里AsyncWebRequest的主要作用是用于判断目标
        // handler的返回值是否为WebAsyncTask或DefferredResult,如果是这两种中的一种,
        // 则说明当前请求的处理应该是异步的。所谓的异步,指的是当前请求会将Controller中
        // 封装的业务逻辑放到一个线程池中进行调用,待该调用有返回结果之后再返回到response中。
        // 这种处理的优点在于用于请求分发的线程能够解放出来,从而处理更多的请求,只有待目标任务
        // 完成之后才会回来将该异步任务的结果返回。
        AsyncWebRequest asyncWebRequest = WebAsyncUtils
            .createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        // 封装异步任务的线程池,request和interceptors到WebAsyncManager中
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

        // 这里就是用于判断当前请求是否有异步任务结果的,如果存在,则对异步任务结果进行封装
        if (asyncManager.hasConcurrentResult()) {
            Object result = asyncManager.getConcurrentResult();
            mavContainer = (ModelAndViewContainer) 
                asyncManager.getConcurrentResultContext()[0];
            asyncManager.clearConcurrentResult();
            if (logger.isDebugEnabled()) {
                logger.debug("Found concurrent result value [" + result + "]");
            }
            // 封装异步任务的处理结果,虽然封装的是一个HandlerMethod,但只是Spring简单的封装
            // 的一个Callable对象,该对象中直接将调用结果返回了。这样封装的目的在于能够统一的
            // 进行右面的ServletInvocableHandlerMethod.invokeAndHandle()方法的调用
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }

        // 对请求参数进行处理,调用目标HandlerMethod,并且将返回值封装为一个ModelAndView对象
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

        // 对封装的ModelAndView进行处理,主要是判断当前请求是否进行了重定向,如果进行了重定向,
        // 还会判断是否需要将FlashAttributes封装到新的请求中
        return getModelAndView(mavContainer, modelFactory, webRequest);
    } finally {
        // 调用request destruction callbacks和对SessionAttributes进行处理
        webRequest.requestCompleted();
    }
}

该方法干了些啥:

  • 获取当前容器中使用 @InitBinder 注解注册的属性转换器(扩展 @InitBinder
  • 获取当前容器中使用 @ModelAttribute 标注但没有使用 @RequestMapping 标注的方法,并且在调用目标方法之前调用这些方法(扩展 @ModelAttribute
  • 判断目标 handler 返回值是否使用了 WebAsyncTask 或 DefferredResult 封装,如果封装了,则按照异步任务的方式进行执行
  • 处理请求参数,调用目标方法和处理返回值,并且将返回值封装为一个ModelAndView对象返回出去

那么下面依次分析这几个主要步骤。

getDataBinderFactory

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDataBinderFactory

private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) 
    throws Exception {
    // 判断当前缓存中是否缓存了当前bean所需要装配的InitBinder方法,如果存在,则直接从缓存中取,
    // 如果不存在,则在当前bean中进行扫描获取
    Class<?> handlerType = handlerMethod.getBeanType();
    Set<Method> methods = this.initBinderCache.get(handlerType);
    if (methods == null) {
        // 在当前bean中查找所有标注了@InitBinder注解的方法,这里INIT_BINDER_METHODS就是一个
        // 选择器,表示只获取使用@InitBinder标注的方法
        methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
        this.initBinderCache.put(handlerType, methods);
    }

    // 这里initBinderAdviceCache是在RequestMappingHandlerAdapter初始化时同步初始化的,
    // 其内包含的方法有如下两个特点:①当前方法所在类使用@ControllerAdvice进行标注了;
    // ②当前方法使用@InitBinder进行了标注。也就是说其内保存的方法可以理解为是全局类型
    // 的参数绑定方法
    List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
    this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
        // 这里判断的是当前配置的全局类型的InitBinder是否能够应用于当前bean,
        // 判断的方式主要在@ControllerAdvice注解中进行了声明,包括通过包名,类所在的包,
        // 接口或者注解的形式限定的范围
        if (clazz.isApplicableToBeanType(handlerType)) {
            Object bean = clazz.resolveBean();
            for (Method method : methodSet) {
                initBinderMethods.add(createInitBinderMethod(bean, method));
            }
        }
    });

    // 这里是将当前HandlerMethod所在bean中的InitBinder添加到需要执行的initBinderMethods中。
    // 这里从添加的顺序可以看出,全局类型的InitBinder会在当前bean中的InitBinder之前执行
    for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        initBinderMethods.add(createInitBinderMethod(bean, method));
    }

    // 将需要执行的InitBinder封装到InitBinderDataBinderFactory中
    return createDataBinderFactory(initBinderMethods);
}

这里获取 InitBinder 的方式主要有两种:

  • 全局配置的 InitBinder :全局类型的 InitBinder 需要声明的类上使用 @ControllerAdvice 进行标注,并且声明方法上使用 @InitBinder 进行标注。
  • handler 所在类使用 @InitBinder 注解标注的方法。

这两种方式,全局配置的会优先于局部配置的 InitBinder 执行。另外,标注该注解的方法执行时机只会在参数绑定时执行。

getModelFactory

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelFactory

private ModelFactory getModelFactory(HandlerMethod handlerMethod, 
                                     WebDataBinderFactory binderFactory) {
    // 这里SessionAttributeHandler的作用是声明几个属性,使其能够在多个请求之间共享,
    // 并且其能够保证当前request返回的model中始终保有这些属性
    SessionAttributesHandler sessionAttrHandler = 
        getSessionAttributesHandler(handlerMethod);

    // 判断缓存中是否保存有当前handler执行之前所需要执行的标注了@ModelAttribute的方法
    Class<?> handlerType = handlerMethod.getBeanType();
    Set<Method> methods = this.modelAttributeCache.get(handlerType);
    if (methods == null) {
        // 如果缓存中没有相关属性,那么就在当前bean中查找所有使用@ModelAttribute标注,但是
        // 没有使用@RequestMapping标注的方法,并将这些方法缓存起来
        methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
        this.modelAttributeCache.put(handlerType, methods);
    }

    // 获取全局的使用@ModelAttribute标注,但是没有使用@RequestMapping标注的方法,
    // 这里全局类型的方法的声明方式需要注意的是,其所在的bean必须使用@ControllerAdvice进行标注
    List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
    this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {
        // 判断@ControllerAdvice中指定的作用的bean范围与当前bean是否匹配,匹配了才会对其应用
        if (clazz.isApplicableToBeanType(handlerType)) {
            Object bean = clazz.resolveBean();
            for (Method method : methodSet) {
                attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
            }
        }
    });

    // 将当前方法中使用@ModelAttribute标注的方法添加到需要执行的attrMethods中。从这里的添加顺序
    // 可以看出,全局类型的方法将会先于局部类型的方法执行
    for (Method method : methods) {
        Object bean = handlerMethod.getBean();
        attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
    }

    // 将需要执行的方法等数据封装为ModelFactory对象
    return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}

该方法获取 @ModelAttribute 的流程和 getDataBinderFactory 方法非常详细,大家可以类比去看看。

执行 @ModelAttribute 标注方法

上面 getModelFactory 获取到了 ModelAttribute 注解标注的方法,那么来看看这些标注的方法是如何执行的,源码如下:

org.springframework.web.method.annotation.ModelFactory#initModel

public void initModel(NativeWebRequest request, ModelAndViewContainer container,
                      HandlerMethod handlerMethod) throws Exception {

    // 在当前request中获取使用@SessionAttribute注解声明的参数
    Map<String, ?> sessionAttributes = 
        this.sessionAttributesHandler.retrieveAttributes(request);
    // 将@SessionAttribute声明的参数封装到ModelAndViewContainer中
    container.mergeAttributes(sessionAttributes);
    // 调用前面获取的使用@ModelAttribute标注的方法
    invokeModelAttributeMethods(request, container);

    // 这里首先获取目标handler执行所需的参数中与@SessionAttribute同名或同类型的参数,
    // 也就是handler想要直接从@SessionAttribute中声明的参数中获取的参数。然后对这些参数
    // 进行遍历,首先判断request中是否包含该属性,如果不包含,则从之前的SessionAttribute缓存
    // 中获取,如果两个都没有,则直接抛出异常
    for (String name : findSessionAttributeArguments(handlerMethod)) {
        if (!container.containsAttribute(name)) {
            Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
            if (value == null) {
                throw new HttpSessionRequiredException("Expected session attribute '" 
                                                       + name + "'", name);
            }
            container.addAttribute(name, value);
        }
    }
}

扩展 @SessionAttribute

该方法主要做了两件事情:

  1. 保证 @SessionAttribute 声明的参数的存在
  2. 调用使用 @ModelAttribute 标注的方法(invokeModelAttributeMethods )

下面来看看 invokeModelAttributeMethods 方法源码:

org.springframework.web.method.annotation.ModelFactory#invokeModelAttributeMethods

private void invokeModelAttributeMethods(NativeWebRequest request, 
                                         ModelAndViewContainer container) throws Exception {

    while (!this.modelMethods.isEmpty()) {
        // 这里getNextModelMethod()方法始终会获取modelMethods中的第0号为的方法,
        // 后续该方法执行完了之后则会将该方法从modelMethods移除掉,因而这里while
        // 循环只需要判断modelMethods是否为空即可
        InvocableHandlerMethod modelMethod = 
            getNextModelMethod(container).getHandlerMethod();
        // 获取当前方法中标注的ModelAttribute属性,然后判断当前request中是否有与该属性中name字段
        // 标注的值相同的属性,如果存在,并且当前ModelAttribute设置了不对该属性进行绑定,那么
        // 就直接略过当前方法的执行
        ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
        Assert.state(ann != null, "No ModelAttribute annotation");
        if (container.containsAttribute(ann.name())) {
            if (!ann.binding()) {
                container.setBindingDisabled(ann.name());
            }
            continue;
        }

        // 通过ArgumentResolver对方法参数进行处理,并且调用目标方法
        Object returnValue = modelMethod.invokeForRequest(request, container);

        // 如果当前方法的返回值不为空,则判断当前@ModelAttribute是否设置了需要绑定返回值,
        // 如果设置了,则将返回值绑定到请求中,后续handler可以直接使用该参数
        if (!modelMethod.isVoid()){
            String returnValueName = getNameForReturnValue(returnValue, 
                                                           modelMethod.getReturnType());
            if (!ann.binding()) {
                container.setBindingDisabled(returnValueName);
            }

            // 如果request中不包含该参数,则将该返回值添加到ModelAndViewContainer中,
            // 供handler使用
            if (!container.containsAttribute(returnValueName)) {
                container.addAttribute(returnValueName, returnValue);
            }
        }
    }
}

这里调用使用 @ModelAttribute 标注的方法的方式比较简单,主要需要注意的是,对于调用结果,如果当前 request 中没有同名的参数,则会将调用结果添加到 ModelAndViewContainer 中,以供给后续 handler 使用。

invokeAndHandle

在进行了相关前置方法调用和异步任务的判断之后,RequestMappingHandlerAdapter 就会开始调用目标 handler 了。调用过程在 ServletInvocableHandlerMethod.invokeAndHandle() 方法中,如下是该方法的源码:

org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle

public void invokeAndHandle(ServletWebRequest webRequest, 
                            ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 对目标handler的参数进行处理,并且调用目标handler
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // 设置相关的返回状态
    setResponseStatus(webRequest);

    // 如果请求处理完成,则设置requestHandled属性
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null 
            || mavContainer.isRequestHandled()) {
            mavContainer.setRequestHandled(true);
            return;
        }
    } else if (StringUtils.hasText(getResponseStatusReason())) {
        // 如果请求失败,但是有错误原因,那么也会设置requestHandled属性
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        // 遍历当前容器中所有ReturnValueHandler,判断哪种handler支持当前返回值的处理,
        // 如果支持,则使用该handler处理该返回值
        this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    } catch (Exception ex) {
        if (logger.isTraceEnabled()) {
            logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", 
                                                            returnValue), ex);
        }
        throw ex;
    }
}

private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
    // 获取HttpStatus
    HttpStatus status = getResponseStatus();
    // 未发现HttpStatus直接返回
    if (status == null) {
        return;
    }

    HttpServletResponse response = webRequest.getResponse();
    if (response != null) {
        String reason = getResponseStatusReason();
        if (StringUtils.hasText(reason)) {
            /**
             * 注意 注意 注意:这里是 sendError , 不是 setError
             * 使用指定的状态码并清空缓冲,发送一个错误响应至客户端。如果响应已经被提交,这个方法会抛出IllegalStateException。
             * 服务器默认会创建一个HTML格式的服务错误页面作为响应结果,其中包含参数msg指定的文本信息,
             * 这个HTML页面的内容类型为“text/html”,保留cookies和其他未修改的响应头信息。
             *
             * 如果一个对应于传入的错误码的错误页面已经在web.xml中声明,那么这个声明的错误页面将会优先于建议的msg参数服务于客户端。
             */
            response.sendError(status.value(), reason);
        }
        else {
            /**
             * 设置响应的状态码。
             * 这个方法被用于当响应结果正常时(例如,状态码为SC_OK或SC_MOVED_TEMPORARTLY)设置响应状态码。
             * 如果发生错误,而且来访者希望调用在web应用中定义的错误页面作为显示,那么应该使用sendError方法代替之。
             * 使用setStatus方法之后,容器会清空缓冲并设置Location响应头,保留cookies和其他响应头信息。
             */
            response.setStatus(status.value());
        }
    }

    // To be picked up by RedirectView
    webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
}

该方法主要分为三个步骤

  1. 对请求参数进行处理,将 request 中的参数封装为当前 handler 的参数的形式并通过反射调用当前 handler
  2. 设置响应状态
  3. 对方法的返回值进行处理,以将其封装为一个 ModleAndView 对象

下面来看看第一个步骤 invokeForRequest 方法源码

org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest

public Object invokeForRequest(NativeWebRequest request, @Nullable 
                               ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 将request中的参数转换为当前handler的参数形式
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
        logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), 
                                                                      getBeanType()) + "' with arguments " + Arrays.toString(args));
    }
    // 这里doInvoke()方法主要是结合处理后的参数,使用反射对目标方法进行调用
    Object returnValue = doInvoke(args);
    if (logger.isTraceEnabled()) {
        logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), 
                                                                    getBeanType()) + "] returned [" + returnValue + "]");
    }
    return returnValue;
}

org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues

// 本方法主要是通过当前容器中配置的ArgumentResolver对request中的参数进行转化,
// 将其处理为目标handler的参数的形式
private Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable 
                                         ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {

    // 获取当前handler所声明的所有参数,主要包括参数名,参数类型,参数位置,所标注的注解等等属性
    MethodParameter[] parameters = getMethodParameters();
    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        // providedArgs是调用方提供的参数,这里主要是判断这些参数中是否有当前类型
        // 或其子类型的参数,如果有,则直接使用调用方提供的参数,对于请求处理而言,默认情况下,
        // 调用方提供的参数都是长度为0的数组
        args[i] = resolveProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }

        // 如果在调用方提供的参数中不能找到当前类型的参数值,则遍历Spring容器中所有的
        // ArgumentResolver,判断哪种类型的Resolver支持对当前参数的解析,这里的判断
        // 方式比较简单,比如RequestParamMethodArgumentResolver就是判断当前参数
        // 是否使用@RequestParam注解进行了标注
        if (this.argumentResolvers.supportsParameter(parameter)) {
            try {
                // 如果能够找到对当前参数进行处理的ArgumentResolver,则调用其
                // resolveArgument()方法从request中获取对应的参数值,并且进行转换
                args[i] = this.argumentResolvers.resolveArgument(
                    parameter, mavContainer, request, this.dataBinderFactory);
                continue;
            } catch (Exception ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", 
                                                                   i), ex);
                }
                throw ex;
            }
        }

        // 如果进行了参数处理之后当前参数还是为空,则抛出异常
        if (args[i] == null) {
            throw new IllegalStateException("Could not resolve method parameter at index " 
                                            + parameter.getParameterIndex() + " in " 
                                            + parameter.getExecutable().toGenericString() 
                                            + ": " + getArgumentResolutionErrorMessage("No suitable resolver for",i));
        }
    }
    return args;
}

关于 handler 的调用,可以看到,这里的实现也是比较简单的,首先是遍历所有的参数,并且查找哪种 ArgumentResolver 能够处理当前参数,找到了则按照具体的 Resolver 定义的方式进行处理即可。在所有的参数处理完成之后,RequestMappingHandlerAdapter 就会使用反射调用目标 handler。

再来看看第二个步骤 setResponseStatus 方法,源码如下:

org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#setResponseStatus

private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
    // 获取HttpStatus
    HttpStatus status = getResponseStatus();
    // 未发现HttpStatus直接返回
    if (status == null) {
        return;
    }

    HttpServletResponse response = webRequest.getResponse();
    if (response != null) {
        String reason = getResponseStatusReason();
        if (StringUtils.hasText(reason)) {
            /**
             * 注意 注意 注意:这里是 sendError , 不是 setError
             * 使用指定的状态码并清空缓冲,发送一个错误响应至客户端。如果响应已经被提交,这个方法会抛出IllegalStateException。
             * 服务器默认会创建一个HTML格式的服务错误页面作为响应结果,其中包含参数msg指定的文本信息,
             * 这个HTML页面的内容类型为“text/html”,保留cookies和其他未修改的响应头信息。
             *
             * 如果一个对应于传入的错误码的错误页面已经在web.xml中声明,那么这个声明的错误页面将会优先于建议的msg参数服务于客户端。
                 */
            response.sendError(status.value(), reason);
        }
        else {
            /**
             * 设置响应的状态码。
             * 这个方法被用于当响应结果正常时(例如,状态码为SC_OK或SC_MOVED_TEMPORARTLY)设置响应状态码。
             * 如果发生错误,而且来访者希望调用在web应用中定义的错误页面作为显示,那么应该使用sendError方法代替之。
             * 使用setStatus方法之后,容器会清空缓冲并设置Location响应头,保留cookies和其他响应头信息。
             */
            response.setStatus(status.value());
        }
    }

    // To be picked up by RedirectView
    webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
}

最后来看第三个步骤 handleReturnValue 方法,源码如下:

org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    // 获取能够处理当前返回值的Handler,比如如果返回值是ModelAndView类型,那么这里的handler就是
    // ModelAndViewMethodReturnValueHandler
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " 
                                           + returnType.getParameterType().getName());
    }

    // 通过获取到的handler处理返回值,并将其封装到ModelAndViewContainer中
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

该方法也非常简单,主要就是两个步骤:

  1. 获取能够处理当前返回值的Handler
  2. 通过获取到的handler处理返回值

下面来看看如何获取处理返回值得 Handler 方法源码:

org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#selectHandler

// 本方法的主要作用是获取能够处理当前返回值的ReturnValueHandler
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, 
                                                      MethodParameter returnType) {
    // 判断返回值是否为异步类型的返回值,即WebAsyncTask或DefferredResult
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);

    // 对所有的ReturnValueHandler进行遍历,判断其是否支持当前返回值的处理。这里如果当前返回值
    // 是异步类型的返回值,还会判断当前ReturnValueHandler是否为
    // AsyncHandlerMethodReturnValueHandler类型,如果不是,则会继续查找
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }

        // 判断是否支持返回值处理的主要位置,比如ModelAndViewMethodReturnValueHandler就会
        // 判断返回值是否为ModelAndView类型,如果是,则表示其是当前ReturnValuleHandler所支持的类型
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

对于具体处理返回值的逻辑,每个 ReturnValueHandler 各不相同,这里不做分析。

getModelAndView

构建 ModelAndView 对象,通过 Model 和 View 。

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelAndView

private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
                                     ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    // 更新模型
    // 将列为@SessionAttributes的模型属性提升到会话
    modelFactory.updateModel(webRequest, mavContainer);
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    // 获取ModelMap并创建ModelAndView
    ModelMap model = mavContainer.getModel();
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    // 处理引用类型视图和转发类型视图
    // 真正的View 可见ModelMap/视图名称、状态HttpStatus最终都交给了Veiw去渲染
    if (!mavContainer.isViewReference()) {
        mav.setView((View) mavContainer.getView());
    }
    // 这个步骤:是Spring MVC对重定向的支持 
    // 重定向之间传值,使用的RedirectAttributes这种Model 
    if (model instanceof RedirectAttributes) {
        Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
    }
    return mav;
}

1.5 applyDefaultViewName

执行处理生成默认视图名,也就是添加前缀和后缀等。

org.springframework.web.servlet.DispatcherServlet#applyDefaultViewName

private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
    // ModelAndView 不为空并且 ModelAndView 中不包含视图
    if (mv != null && !mv.hasView()) {
        // 获取默认视图名称
        String defaultViewName = getDefaultViewName(request);
        if (defaultViewName != null) {
            // 给 ModelAndView 设置视图名称
            mv.setViewName(defaultViewName);
        }
    }
}

可以看到,这里的判断逻辑很简单,首先检查 mv 是否为 null(如果用户添加了 @ResponseBody 注解,mv 就为 null),然后去判断 mv 中是否包含视图,如果不包含视图,则调用 getDefaultViewName 方法去获取默认的视图名,并将获取到的默认视图名交给 mv。

org.springframework.web.servlet.DispatcherServlet#getDefaultViewName

protected String getDefaultViewName(HttpServletRequest request) throws Exception {
    // 如果默认视图解析器不为空则调用 getViewName 方法获取默认视图名称,反之返回 null
    return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
}

这里涉及到一个新的组件 viewNameTranslator,如果 viewNameTranslator 不为 null,则调用其 getViewName 方法获取默认的视图名。

org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#getViewName

public String getViewName(HttpServletRequest request) {
    // 获取 request 中的请求路径获取
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    // 拼接默认视图名称: 前缀 + transformPath 方法返回值 + 后缀
    return (this.prefix + transformPath(lookupPath) + this.suffix);
}

在 getViewName 方法中,首先提取出来当前请求路径,如果请求地址是 http://localhost:8080/test,那么这里提取出来的路径就是 /test,然后通过 transformPath 方法对路径进行处理,再分别加上前后缀后返回,默认的前后缀都是空字符串(如有需要,也可以自行配置)。

org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator#transformPath

protected String transformPath(String lookupPath) {
    String path = lookupPath;
    if (this.stripLeadingSlash && path.startsWith(SLASH)) {
        path = path.substring(1);
    }
    if (this.stripTrailingSlash && path.endsWith(SLASH)) {
        path = path.substring(0, path.length() - 1);
    }
    if (this.stripExtension) {
        path = StringUtils.stripFilenameExtension(path);
    }
    if (!SLASH.equals(this.separator)) {
        path = StringUtils.replace(path, SLASH, this.separator);
    }
    return path;
}

transformPath 则主要干了如下几件事:

  1. 去掉路径开始的 /
  2. 去掉路径结尾的 /
  3. 如果请求路径有扩展名,则去掉扩展名,例如请求路径是 /test.abc,经过这一步处理后,就变成了 /test
  4. 如果 separator 与 SLASH 不同,则替换原来的分隔符(默认是相同的)。

好了,经过这一波处理后,正常情况下,我们就拿到了一个新的视图名,这个新的视图名就是你的请求路径。

例如请求路径是 http://localhost:8080/test.abc,那么获取到的默认视图名就是 test

1.6 applyPostHandle

该方法是应用所有拦截器的 postHandle 方法。

org.springframework.web.servlet.HandlerExecutionChain#applyPostHandle

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
      throws Exception {
    // 获取容器中拦截器集合
   HandlerInterceptor[] interceptors = getInterceptors();
    // 不为空,则循环遍历拦截器并执行其 postHandle 方法
   if (!ObjectUtils.isEmpty(interceptors)) {
      for (int i = interceptors.length - 1; i >= 0; i--) {
         HandlerInterceptor interceptor = interceptors[i];
         interceptor.postHandle(request, response, this.handler, mv);
      }
   }
}

5.7 processDispatchResult

此方法是处理最终结果的,包括异常处理、渲染页面和发出完成通知触发拦截器的afterCompletion()方法执行等

org.springframework.web.servlet.DispatcherServlet#processDispatchResult

private void processDispatchResult(HttpServletRequest request, 
                                   HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, 
                                   @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {

    // 用于标记当前生成view是否是异常处理之后生成的view
    boolean errorView = false;
    if (exception != null) {
        // 如果当前的异常是ModelAndViewDefiningException类型,则说明是ModelAndView的定义
        // 异常,那么就会调用其getModelAndView()方法生成一个新的view
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        } else {
            // 如果生成的异常是其他类型的异常,就会在当前容器中查找能够处理当前异常的“拦截器”,
            // 找到之后调用这些拦截器,然后生成一个新的ModelAndView
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // 如果得到的ModelAndView对象(无论是否为异常处理之后生成的ModelAndView)不为空,并且没有被清理,
    // 那么就会对其进行渲染,渲染的主要逻辑在render()方法中
    if (mv != null && !mv.wasCleared()) {
        // 该方法主要有两个功能:根据view名称封装view视图对象(view对象的构建) 和 渲染数据(将modelMap中的数据暴露到request域中)
        render(mv, request, response);
        if (errorView) {
            // 如果当前是异常处理之后生成的视图,那么就请求当前request中与异常相关的属性
            WebUtils.clearErrorRequestAttributes(request);
        }
    } else {
        if (logger.isDebugEnabled()) {
            logger.debug("Null ModelAndView returned to DispatcherServlet with name '" 
                         + getServletName() + "': assuming HandlerAdapter completed request " 
                         + "handling");
        }
    }

    // 如果当前正在进行异步请求任务的调用,则直接释放当前线程,等异步任务处理完之后再进行处理
    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        return;
    }

    // 在视图渲染完成之后,依次调用当前容器中所有拦截器的afterCompletion()方法
    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

SpringMVC视图解析配置

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/"/>
    <property name="suffix" value=".jsp"/>
</bean>

1.8 请求流程图

最后画图小能手上线,SpringMVC请求流程图如下:

在这里插入图片描述

至此,我们的 SpringMVC 的源码分析到此就结束了。

好了,今天的内容到这里就结束了,我是 【J3】关注我,我们下期见


  • 由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。

  • 如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。

  • 感谢您的阅读,十分欢迎并感谢您的关注。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

J3code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值