SpringMVC运行流程分析之核心流程

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: SpringMVC运行流程分析之核心流程

关联博客:

SpringMVC源码分析之策略对象初始化

SpringMVC运行流程分析之前置流程

SpringMVC运行流程分析之核心流程

前面SpringMVC运行流程分析之前置流程



博文中我们分析了springMVC请求处理的前置流程,本文我们分析其核心流程也就是doDispatch(request, response);

这里先放一下时序图展示整体流程:

那么HandlerAdapter核心流程逻辑呢?


【1】整体结构

① 源码概览

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  HttpServletRequest processedRequest = request;
  // 处理器执行链对象
  HandlerExecutionChain mappedHandler = null;
  // 是否multipart请求
  boolean multipartRequestParsed = false;
// 异步处理管理器
  WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  try {
    //模型视图对象
    ModelAndView mv = null;
    // 异常对象
    Exception dispatchException = null;
    try {
    // 如果是multipart request 将会尝试使用multipartResolver解析
      processedRequest = checkMultipart(request);
      multipartRequestParsed = (processedRequest != request);
      // Determine handler for the current request.
      mappedHandler = getHandler(processedRequest);
      if (mappedHandler == null) {
        noHandlerFound(processedRequest, response);
        return;
      }
      // Determine handler adapter for the current request.
      // 获取处理器适配器
      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
      // Process last-modified header, if supported by the handler.
      String method = request.getMethod();
      boolean isGet = "GET".equals(method);
      // 如果是get且没有修改,直接返回
      if (isGet || "HEAD".equals(method)) {
        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
        if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
          return;
        }
      }
// 如果某个拦截器前置方法返回false,直接返回
      if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
      }
// 核心处理方法
      // Actually invoke the handler.
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 如果是异步请求处理,则这里就返回
      if (asyncManager.isConcurrentHandlingStarted()) {
        return;
      }
// 如果MV不为null,但是没有view,则尝试获取defaultViewName
      applyDefaultViewName(processedRequest, mv);
      // 处理执行链的后置处理--即拦截器的postHandle方法
      mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
      dispatchException = ex;
    }
    catch (Throwable err) {
      // As of 4.3, we're processing Errors thrown from handler methods as well,
      // making them available for @ExceptionHandler methods and other scenarios.
      dispatchException = new NestedServletException("Handler dispatch failed", err);
    }
// 处理最后结果,这里可能会返回到一个页面或者抛出异常,或者返回json等    
    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 {
  // 如果是异步请求 且 mappedHandler 不为null,
    if (asyncManager.isConcurrentHandlingStarted()) {
      // Instead of postHandle and afterCompletion
      if (mappedHandler != null) {
// 如果拦截器是asyncInterceptor类型,则调用afterConcurrentHandlingStarted方法
        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
      }
    }
    else {
      // Clean up any resources used by a multipart request.
      if (multipartRequestParsed) {
      // 如果是multipartRequest,则清理资源 fielItem哦
        cleanupMultipart(processedRequest);
      }
    }
  }
}


仅就这段方法来看,其流程如下所示:


这里有三个步骤十分关键,分别是第三步mappedHandler = getHandler(processedRequest);获取执行器链,第六步mv = ha.handle(processedRequest, response, mappedHandler.getHandler());获取MV以及最终的第十步processDispatchResult处理最终结果。


第三步是给请求尝试找到一个handler,比如某个controller某个方法,或者说默认的处理器DefaultServletHttpRequestHandler等。然后实例化HandlerExecutionChain 后添加合适的拦截器。


第六步则是核心的处理过程,会进行参数解析、目标方法的反射调用以及最终返回结果的处理。


第十步processDispatchResult处理最终结果通常比如渲染视图或者返回json。如果在前面处理过程中抛出了异常,那么这里也会对异常尝试处理。

② 核心对象HandlerExecutionChain


HandlerExecutionChain ,处理器执行链。其通过遍历HandlerMapping,根据其方法HandlerExecutionChain getHandler(HttpServletRequest request)获取得到。


通过如下核心属性与构造方法可知,其由handler和HandlerInterceptor组成。handler比如HandlerMethod(或其他handler类)即实际处理请求的目标类(方法)。HandlerInterceptor就是拦截器,在处理流程中分别会触发其preHandle、postHandle以及afterCompletion方法。HandlerExecutionChain 中的handler可能如下:


实现了org.springframework.web.servlet.function.HandlerFunction,比如ResourceHandlerFunction

实现了org.springframework.web.servlet.mvc.Controller,比如ParameterizableViewController

实现了org.springframework.web.HttpRequestHandler,比如DefaultServletHttpRequestHandler

实现了javax.servlet.Servlet,比如ViewStatusMessagesServlet

实现了org.springframework.web.method.HandlerMethod,比如ServletInvocableHandlerMethod,controller里面很多方法就被包装为了该类型。这个也是最常见的核心属性和构造方法:

private final Object handler;
private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
private int interceptorIndex = -1;
/**
 * Create a new HandlerExecutionChain.
 * @param handler the handler object to execute
 */
public HandlerExecutionChain(Object handler) {
  this(handler, (HandlerInterceptor[]) null);
}


③ 核心对象ModelAndView

模型视图对象,模型这里理解为一个存放数据的map,视图view通常需要一个ViewResolver解析处理。视图解析器会获取到model然后渲染view最后返回给前台一个解析后的页面。


我们下面看下其核心属性和构造方法。

// 视图实例或者视图名称
@Nullable
private Object view;
// 存放数据 map结构
@Nullable
private ModelMap model;
// 响应状态码 可为空
@Nullable
private HttpStatus status;
// 指示是否已经调用clear方法进行清理
private boolean cleared = false;
 // 空构造方法
public ModelAndView() {
}
 // 设置viewName
public ModelAndView(String viewName) {
  this.view = viewName;
}
 // 设置view
public ModelAndView(View view) {
  this.view = view;
}
 // 设置viewName和model
public ModelAndView(String viewName, @Nullable Map<String, ?> model) {
  this.view = viewName;
  if (model != null) {
    getModelMap().addAllAttributes(model);
  }
}
 // 设置view和model
public ModelAndView(View view, @Nullable Map<String, ?> model) {
  this.view = view;
  if (model != null) {
    getModelMap().addAllAttributes(model);
  }
}
 // 设置viewName status
public ModelAndView(String viewName, HttpStatus status) {
  this.view = viewName;
  this.status = status;
}
 // 完整实例
public ModelAndView(@Nullable String viewName, @Nullable Map<String, ?> model, @Nullable HttpStatus status) {
  this.view = viewName;
  if (model != null) {
    getModelMap().addAllAttributes(model);
  }
  this.status = status;
}
 // 设置viewName 然后向model中添加属性 modelName = modelObject
public ModelAndView(String viewName, String modelName, Object modelObject) {
  this.view = viewName;
  addObject(modelName, modelObject);
}
 // 设置view然后向model中添加属性 modelName = modelObject
public ModelAndView(View view, String modelName, Object modelObject) {
  this.view = view;
  addObject(modelName, modelObject);
}


另外,关于HandlerMapping组件、HandlerAdapter组件可以参考下面博文,这里不再赘述。

SpringMVC常见组件之HandlerMapping分析、SpringMVC常见组件之HandlerAdapter分析


【2】方法说明

① checkMultipart

源码如下所示,其核心是判断当前请求是否为multipart request并尝试使用multipartResolver进行解析。通常这一步应用于文件上传场景。

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.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
        }
      }
      else if (hasMultipartException(request)) {
        logger.debug("Multipart resolution previously failed for current request - " +
            "skipping re-resolution for undisturbed error rendering");
      }
      else {
        try {
          return this.multipartResolver.resolveMultipart(request);
        }
        catch (MultipartException ex) {
          if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
            logger.debug("Multipart resolution failed for error dispatch", ex);
            // Keep processing error dispatch with regular request handle below
          }
          else {
            throw ex;
          }
        }
      }
    }
    // If not returned before: return original request.
    return request;
  }


② getHandler(processedRequest)

获取处理器执行链对象HandlerExecutionChain。这里本质是遍历容器中的HandlerMapping ,调用其getHandler方法获取HandlerExecutionChain 。

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;
}

关于mapping.getHandler(request)具体逻辑参考博文SpringMVC常见组件之HandlerMapping分析。


③ getHandlerAdapter(mappedHandler.getHandler())

同②一样,这里会遍历容器中的handlerAdapters,找到一个支持当前handler的适配器。如果找不到,将会抛出ServletException异常。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  if (this.handlerAdapters != null) {
    for (HandlerAdapter adapter : this.handlerAdapters) {
      if (adapter.supports(handler)) {
        return adapter;
      }
    }
  }
  throw new ServletException("No adapter for handler [" + handler +
      "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

④ 拦截器的方法

这里指的是HandlerExecutionChain.applyPreHandle、HandlerExecutionChain.applyPostHandle以及triggerAfterCompletion(有两个哦)


① 前置处理与后置处理

applyPreHandle与applyPostHandle本质只是遍历interceptorList,调用其preHandle方法。


需要注意的是在applyPreHandle中,如果某个拦截器的preHandle方法返回false,则会触发triggerAfterCompletion方法。applyPostHandle则不会触发triggerAfterCompletion方法。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
  for (int i = 0; i < this.interceptorList.size(); i++) {
    HandlerInterceptor interceptor = this.interceptorList.get(i);
    if (!interceptor.preHandle(request, response, this.handler)) {
      // 这里第三个参数为null
      triggerAfterCompletion(request, response, null);
      return false;
    }
    this.interceptorIndex = i;
  }
  return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
    throws Exception {
  for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
    HandlerInterceptor interceptor = this.interceptorList.get(i);
    interceptor.postHandle(request, response, this.handler, mv);
  }
}


②triggerAfterCompletion

首先是DispatcherServlettriggerAfterCompletion,其调用位置如下图所示(会将异常跑出去):


方法如下所示,其会调用mappedHandler(HandlerExecutionChain)triggerAfterCompletion方法,然后抛出异常。

private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
    @Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {
  if (mappedHandler != null) {
    mappedHandler.triggerAfterCompletion(request, response, ex);
  }
  throw ex;
}


HandlerExecutionChaintriggerAfterCompletion方法如下所示,本质是遍历interceptorList调用每一个拦截器的afterCompletion方法。

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
  for (int i = this.interceptorIndex; i >= 0; i--) {
    HandlerInterceptor interceptor = this.interceptorList.get(i);
    try {
      interceptor.afterCompletion(request, response, this.handler, ex);
    }
    catch (Throwable ex2) {
      logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
    }
  }
}


从这里也可以看出HandlerExecutionChain虽然维护了handlerinterceptorList,但是本身没有业务逻辑,具体业务逻辑处理都委派给了HandlerInterceptor

⑤ 默认视图名称处理

如果ha.handle(processedRequest, response, mappedHandler.getHandler())返回的MV不是null,但是却没有view。那么这里会尝试根据request获取一个默认viewName。

private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception {
  if (mv != null && !mv.hasView()) {
    String defaultViewName = getDefaultViewName(request);
    if (defaultViewName != null) {
      mv.setViewName(defaultViewName);
    }
  }
}
// 获取默认视图名称方法如下
@Nullable
protected String getDefaultViewName(HttpServletRequest request) throws Exception {
  return (this.viewNameTranslator != null ? this.viewNameTranslator.getViewName(request) : null);
}

这里根据请求获取默认视图名称步骤如下:



①获取请求路径 lookupPath

② 如果是以 / 开头,则去掉头部的 /;

③ 如果是以 / 结尾,则去掉尾部的 /;

④ 去掉文件拓展名e.g. "mypath/myfile.txt" -> "mypath/myfile".

⑤ 如果SLASH不等于separator,则进行替换path = StringUtils.replace(path, SLASH, this.separator);,默认情况下SLASH==separator==/

假设这里请求是http://localhost:8080/testNoView,那么获取的lookupPath为 /testNoView,得到的默认视图名称是testNoView。如果目标位置不存在这个文件,则会抛出如下异常:


8a96b67ceb0c45eebcc143d503971aa6.png


【3】结果处理

这里指的是processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);



① processDispatchResult

代码如下所示,分为这样几步:异常处理、视图处理、异步请求判断以及最后的triggerAfterCompletion。

private 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.debug("ModelAndViewDefiningException encountered", exception);
      mv = ((ModelAndViewDefiningException) exception).getModelAndView();
    }
    else {
      Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
      mv = processHandlerException(request, response, handler, exception);
      errorView = (mv != null);
    }
  }
// 视图处理
  if (mv != null && !mv.wasCleared()) {
  //核心步骤--视图渲染
    render(mv, request, response);
    if (errorView) { //如果是错误视图,清理request中error相关属性
      WebUtils.clearErrorRequestAttributes(request);
    }
  }
  else {
    if (logger.isTraceEnabled()) {
      logger.trace("No view rendering, null ModelAndView returned.");
    }
  }
// 异步请求判断
  if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    // Concurrent handling started during a forward
    return;
  }
// triggerAfterCompletion 处理 这里不再赘述
  if (mappedHandler != null) {
    // Exception (if any) is already handled..
    mappedHandler.triggerAfterCompletion(request, response, null);
  }
}


② 异常处理

如下所示是doDispatch的核心逻辑 。

//doDispatch的核心逻辑
DispatcherServlet#doDispatch
  - processedRequest = checkMultipart(request);
  - mappedHandler = getHandler(processedRequest);
  - HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  - mappedHandler.applyPreHandle(processedRequest, response)
  - mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  - applyDefaultViewName(processedRequest, mv);
  - mappedHandler.applyPostHandle(processedRequest, response, mv);
  - processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  - triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  - 


如果我们的业务抛出了异常,那么是在什么位置呢?

 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  - AbstractHandlerMethodAdapter#handle
  - RequestMappingHandlerAdapter#handleInternal
  - mav = invokeHandlerMethod(request, response, handlerMethod);
  - RequestMappingHandlerAdapter#invokeHandlerMethod
  // 假设这里抛出异常--通常就是我们的业务异常
  - invocableMethod.invokeAndHandle(webRequest, mavContainer);
  - return getModelAndView(mavContainer, modelFactory, webRequest);


如下图所示,当执行invokeAndHandle反射调用我们的目标方法时,假设业务抛出了异常,那么这里会一路抛出到DispatcherServlet.doDispatch中,交给processDispatchResult去处理。无论RequestMappingHandlerAdapter还是ServletInvocableHandlerMethod都没有对这里的异常进行拦截。

5036cf6e718b4965a61f9573cfb6eb2b.png

如下代码所示,如果异常是ModelAndViewDefiningException,则直接获取其ModelAndView属性。否则获取handler,然后根据processHandlerException(request, response, handler, exception)尝试获取MV。


processHandlerException放入如下所示,首先从request移除属性HandlerMapping.class.getName() + ".producibleMediaTypes";然后尝试用HandlerExceptionResolver 处理异常获取到exMV。获取到exMV后如果其不为null,那么将会是否有数据,是否有view;最后暴露错误属性信息给request。

@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
    @Nullable Object handler, Exception ex) throws Exception {
  // Success and error responses may use different content types
  request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
  // 遍历异常解析器处理异常,核心步骤
  ModelAndView exMv = null;
  if (this.handlerExceptionResolvers != null) {
    for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
      exMv = resolver.resolveException(request, response, handler, ex);
      if (exMv != null) {
        break;
      }
    }
  }
  if (exMv != null) {
    // 判断model是否为空
    if (exMv.isEmpty()) {
      request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
      return null;
    }
    // We might still need view name translation for a plain error model...
    if (!exMv.hasView()) {
    // 获取默认视图名称
      String defaultViewName = getDefaultViewName(request);
      if (defaultViewName != null) {
        exMv.setViewName(defaultViewName);
      }
    }
    if (logger.isTraceEnabled()) {
      logger.trace("Using resolved error view: " + exMv, ex);
    }
    else if (logger.isDebugEnabled()) {
      logger.debug("Using resolved error view: " + exMv);
    }
    // 暴露错误相关属性信息给request
    WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
    return exMv;
  }
  throw ex;
}

如果我们使用@ControllerAdvice和@ExceptionHandler(value = {Exception.class})来捕捉异常进行全局异常处理。那么上面exMv = resolver.resolveException(request, response, handler, ex);我们会调用异常解析器去处理异常,得到的exMV呢是一个空的ModelAndView。

7f1b14f200704b0da15df5c23aaa33c8.png

关于异常解析器处理异常逻辑参考博文SpringMVC常见组件之HandlerExceptionResolver分析。这里我们看下WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());方法。

public static void exposeErrorRequestAttributes(HttpServletRequest request, Throwable ex,
    @Nullable String servletName) {
  exposeRequestAttributeIfNotPresent(request, ERROR_STATUS_CODE_ATTRIBUTE, HttpServletResponse.SC_OK);
  exposeRequestAttributeIfNotPresent(request, ERROR_EXCEPTION_TYPE_ATTRIBUTE, ex.getClass());
  exposeRequestAttributeIfNotPresent(request, ERROR_MESSAGE_ATTRIBUTE, ex.getMessage());
  exposeRequestAttributeIfNotPresent(request, ERROR_EXCEPTION_ATTRIBUTE, ex);
  exposeRequestAttributeIfNotPresent(request, ERROR_REQUEST_URI_ATTRIBUTE, request.getRequestURI());
  if (servletName != null) {
    exposeRequestAttributeIfNotPresent(request, ERROR_SERVLET_NAME_ATTRIBUTE, servletName);
  }
}


错误属性key如下所示(都是public static final):

String EXCEPTION_ATTRIBUTE = DispatcherServlet.class.getName() + ".EXCEPTION";
String ERROR_STATUS_CODE_ATTRIBUTE = "javax.servlet.error.status_code";
String ERROR_EXCEPTION_TYPE_ATTRIBUTE = "javax.servlet.error.exception_type";
String ERROR_MESSAGE_ATTRIBUTE = "javax.servlet.error.message";
String ERROR_EXCEPTION_ATTRIBUTE = "javax.servlet.error.exception";
String ERROR_REQUEST_URI_ATTRIBUTE = "javax.servlet.error.request_uri";
String ERROR_SERVLET_NAME_ATTRIBUTE = "javax.servlet.error.servlet_name";

③视图处理

视图处理这里指的是render(mv, request, response);。ModelAndView里面包含了数据和视图信息,那么这一部分通常主要逻辑就是数据渲染,给浏览器一个解析好的页面。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
  // 尝试解析获取得到locale信息
  Locale locale =
      (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
  //为响应设置locale信息
  response.setLocale(locale);
  View view;
  // 获取视图名称
  String viewName = mv.getViewName();
  // 如果视图名称不为null,则解析得到View实例
  if (viewName != null) {
    //核心步骤哦,解析viewName,会遍历视图解析器处理
    view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    if (view == null) {
      throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
          "' in servlet with name '" + getServletName() + "'");
    }
  }
  else {
    // 如果不存在viewName则尝试获取view实例,也不存在,则抛出异常
    view = mv.getView();
    if (view == null) {
      throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
          "View object in servlet with name '" + getServletName() + "'");
    }
  }
  // Delegate to the View object for rendering.
  if (logger.isTraceEnabled()) {
    logger.trace("Rendering view [" + view + "] ");
  }
  try {
  // 如果MV中有http status ,则给response设置status状态码
    if (mv.getStatus() != null) {
      response.setStatus(mv.getStatus().value());
    }
    // 视图渲染,核心步骤,调用具体的View实例的render方法
    view.render(mv.getModelInternal(), request, response);
  }
  catch (Exception ex) {
    if (logger.isDebugEnabled()) {
      logger.debug("Error rendering view [" + view + "]", ex);
    }
    throw ex;
  }
}

这里有两步需要注意:resolveViewName(viewName, mv.getModelInternal(), locale, request);解析视图名称得到具体的View以及view.render(mv.getModelInternal(), request, response);调用具体的View实例的render方法进行视图渲染。


如下所示resolveViewName方法解析视图名称得到实际的View实例,核心是遍历容器中的viewResolvers调用其resolveViewName方法得到View实例。

@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
    Locale locale, HttpServletRequest request) throws Exception {
  if (this.viewResolvers != null) {
    for (ViewResolver viewResolver : this.viewResolvers) {
      View view = viewResolver.resolveViewName(viewName, locale);
      if (view != null) {
        return view;
      }
    }
  }
  return null;
}


目录
相关文章
|
6月前
|
Java 应用服务中间件 Spring
SpringMVC快速入门加登录流程分析
SpringMVC快速入门加登录流程分析
66 0
|
3月前
|
前端开发 Java Spring
SpringMVC种通过追踪源码查看是哪种类型的视图渲染器(一般流程方法)
这篇文章通过示例代码展示了如何在Spring MVC中编写和注册拦截器,以及如何在拦截器的不同阶段添加业务逻辑。
SpringMVC种通过追踪源码查看是哪种类型的视图渲染器(一般流程方法)
|
6月前
|
JSON 前端开发 Java
SpringMVC概述、SpringMVC的工作流程、创建SpringMVC的项目
SpringMVC概述、SpringMVC的工作流程、创建SpringMVC的项目
37 2
|
6月前
|
前端开发 Java 定位技术
生活小事件(SpringMVC主要的组件及作用和执行流程)
Spring MVC 的主要组件包括 DispatcherServlet(核心,请求调度)、HandlerMapping(URL 映射到处理器)、HandlerAdapter(统一执行处理器)、Handler(处理业务逻辑,通常为 @Controller 类)、ViewResolver(视图解析)和 View(渲染输出)。通过这些组件的协作,Spring MVC 实现了从接收请求到返回响应的流程,类似于警察处理交通违规的协调过程。
|
6月前
|
前端开发 Java 应用服务中间件
SpringMvc拦截器和手写模拟SpringMvc工作流程源码详解
MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分。 M: Model,模型层,指工程中的JavaBean,作用是处理数据。 JavaBean分为两类: 1.实体类Bean:专门存储业务数据的,如Student User等 2.业务处理Bean:指Service或Dao对象,专门用于处理业务逻辑和数据访问。
|
6月前
|
JSON 前端开发 Java
SpringMVC概述、入门案例及工作流程
SpringMVC概述、入门案例及工作流程
33 0
|
11月前
|
开发框架 前端开发 Java
SpringMVC之入门:springmcx工作流程,springmvc的入门,静态资源处理器
SpringMVC之入门:springmcx工作流程,springmvc的入门,静态资源处理器
47 0
|
11月前
|
内存技术
SpringMVC运行流程分析之前置流程
SpringMVC运行流程分析之前置流程
45 0
|
JSON 前端开发 Java
【SpringMVC】工作流程&入门案例的使用
【SpringMVC】工作流程&入门案例的使用
40 0
|
6月前
|
设计模式 前端开发 JavaScript
Spring MVC(一)【什么是Spring MVC】
Spring MVC(一)【什么是Spring MVC】