SpringMVC 请求流程源码分析

news2025/4/7 19:01:15
  • 三哥

内容来自【自学星球】

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

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

该文章的内容有视频讲解,地址👉:点我直达

一、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】关注我,我们下期见


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

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

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

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

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

相关文章

使用robot+selenium创建一个UI自动化测试用例

新建项目并安装robotframework pip install robotframework 在pycharm-插件&#xff0c;输入robot搜索插件 选择Robot Framework Language Server或者Hyper RobotFramework Support安装 新建test.robot文件编写测试用例test.robot robot用例文件通常包括4个头部分&#xf…

Substance Painter 的一些玩法笔记

Substance Painter 的一些玩法 1、模型边缘磨损效果&#xff1a; 参考连接&#xff1a;Substance Painter 后期添加法线贴图&#xff0c;处理边缘磨损&#xff01;_哔哩哔哩_bilibili 使用方法&#xff1a; 1、导入要处理的模型文件 2、烘焙 模型贴图(主要是法线) 3、创建一个…

接口api 之Swagger 一次实战探索

今天我们来说说什么是Swagger&#xff1f; 就是把相关的信息存储在它定义的描述文件里面&#xff08;yml或json格式&#xff09;&#xff0c;再通过维护这个描述文件可以去更新接口文档&#xff0c;以及生成各端代码。而Springfox-swagger,则可以通过扫描代码去生成这个描述文…

自学CFD:我在实习岗速成无人机设计和仿真的故事

一、写在前面 大家好&#xff0c;我叫Jack&#xff08;硕士在读&#xff09;&#xff0c;目前在企业实践学习。 作为一名门外汉&#xff0c;初识计算流体力学&#xff0c;经历了盲目无措、乱做一气&#xff0c;查资料找经验毫无进展&#xff0c;直到从B站遇到了Graychen老师。…

综合布线工程测试技术

一、测试的相关基础知识 综合布线工程测试内容主要包括三个方面:工作区到设备间的连通状况测试、主干线连通状况测试、跳线测试。 每项测试内容主要测试以下参数:信息传输速率、衰减、距离、接线图、近端串扰等。 1、接线图(Wire Map) 接线图是用来检验每根电缆末端…

24、四大函数式接口(有函数型接口和断定型接口(都是函数式接口))

四大函数式接口&#xff08;有函数型接口和断定型接口&#xff08;都是函数式接口&#xff09;&#xff09; 新时代程序员&#xff1a;lambda表达式&#xff0c;链式编程&#xff0c;函数式接口&#xff0c;Stream流式计算 函数式接口&#xff1a;只有一个抽象方法的接口&…

热门Java开发工具IDEA入门指南——从Eclipse迁移到IntelliJ IDEA(二)

IntelliJ IDEA&#xff0c;是java编程语言开发的集成环境。IntelliJ在业界被公认为最好的java开发工具&#xff0c;尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能是非常强大的。 本文…

HTML非遗文化网页设计题材【京剧文化】HTML+CSS(大美中国 14页 带bootstarp)

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 茶文化网站 | 中华传统文化题材 | 京剧文化水墨风书画 | 中国民间年画文化艺术网站 | 等网站的设计与制作 | HTML期末大学生网页设计作业&#xff0c;…

Publisher/Subscriber 订阅-发布模式原理解析

Publisher/Subscriber 订阅-发布模式原理解析 参考资料 What Is Pub/Sub? Publish/Subscribe Messaging Explained什么是serverless&#xff1f;Pub/Sub Examples: 5 Use Cases to Understand the Pattern and its BenefitsJavaScript 设计模式精讲Avro Schema格式 一、概念…

【视觉高级篇】21 # 如何添加相机,用透视原理对物体进行投影?

说明 【跟月影学可视化】学习笔记。 如何理解相机和视图矩阵&#xff1f; 用一个三维坐标&#xff08;Position&#xff09;和一个三维向量方向&#xff08;LookAt Target&#xff09;来表示 WebGL 的三维世界的一个相机。要绘制以相机为观察者的图形&#xff0c;需要用一个…

ubuntu安装和启动redis命令步骤及其配置文件redis.conf

1、步骤一 依次执行如下命令 1.下载&#xff1a;wget http://download.redis.io/releases/redis-6.0.1.tar.gz 2.解压&#xff1a;tar xzf redis-6.0.1.tar.gz 2.将解压文件夹移动到usr/local/redis目录下:sudo mv ./redis-6.0.1 /usr/local/redis/ 4.进入到redis目录&#x…

这或许是全网最全时间序列特征工程构造的文章了

数据和特征决定了机器学习的上限&#xff0c;而模型和算法只是逼近这个上限而已。由此可见&#xff0c;特征工程在机器学习中占有相当重要的地位。在实际应用当中&#xff0c;可以说特征工程是机器学习成功的关键。 那特征工程是什么&#xff1f; 特征工程是利用数据领域的相关…

饼图、柱形图、堆积柱、折线图、散点图,到底应该怎么选?

“随着数字经济的发展&#xff0c;各行业的数据都出现了爆炸式的增长&#xff0c;如何快速从海量数据中提取出有效信息&#xff0c;最大化地挖掘数据价值&#xff0c;是所有转型的企业都在面临的问题。” 想要快速直观地以易于理解、内容简单的方式了解相关数据&#xff0c;就需…

[附源码]java毕业设计家政管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

软件测试面试真题 | TCP为什么要进行三次握手和四次挥手呢?

TCP为什么要进行三次握手和四次挥手呢&#xff1f; 在这个三次握手的过程中对应的消息内容是怎样进行传递的呢&#xff1f; 在四次挥手的过程中&#xff0c;是怎样告知对方断开连接的呢&#xff1f; 三次握手 在说对应概念之前&#xff0c;我们先来了解一个场景&#xff1a…

计算机网络 4 - 网络层

第4章 网络层&#xff1a;数据层面(Network Layer: Data Plane)4.1 网络层概述4.2 IP: Internet Protocol分类 IP 地址子网划分无分类域间路由 CIDRIP 地址的特点4.3 地址解析协议 ARP4.4 IP 数据包格式路由转发示例路由器转发算法使用二叉树查找转发表4.5 IP分配技术DHCPNAT 网…

celery

一 介绍 官网&#xff1a;https://docs.celeryq.dev/en/latest/index.html celery是一个简单、灵活、可靠的分布式系统&#xff0c;用于 处理大量消息&#xff0c;同时为操作提供 维护此类系统所需的工具。 Celery架构 Celery的架构由三部分组成&#xff0c;消息中间件&…

纸牌游戏新版小猫钓鱼设计制作

新版纸牌游戏《小猫钓鱼》设计制作 此游戏设计是我新创制的简单的卡牌游戏。属于儿童益智类游戏&#xff0c;适用于儿童的认知教育。 游戏规则很简单&#xff1a;找配对的牌消去。 游戏设置2个玩家对玩&#xff0c;鱼池置牌21张&#xff0c;玩家每人5张牌&#xff0c;二人轮转…

從turtle海龜動畫 學習 Python - 高中彈性課程系列 6.1 內嵌正多邊形 類似禪繞圖

Goal: 藉由有趣的「海龜動畫繪圖」學會基礎的 Python 程式設計 本篇介紹基礎的 Python 海龜動畫繪圖, 確實可以只以簡單的指令畫出極為複雜有趣或美麗的圖案: 內嵌正多邊形之圖案, 禪繞圖等 “Talk is cheap. Show me the code.” ― Linus Torvalds 老子第41章 上德若谷 大白…

Redis--1.CentOS8安装redis服务器

一、登录root账号 设置root密码&#xff1a; sudo passwd root切换到root账号&#xff1a; su root二、下载解压安装包 切换到根目录: cd / 1、创建存放路径: mkdir -p /usr/local/redis cd /usr/local/redis2、下载redis安装包&#xff1a;去官网找到redis连接地址如&…