点击上方蓝色“方志朋”,选择“设为星标”

回复“666”获取独家整理的学习资料!

作者:Sicimike

blog.csdn.net/Baisitao_/article/details/107471719

前言

SpringMVC请求处理相信大家都很熟悉了,本篇主要是基于SpringMVC处理请求的流程来阅读并调试源码,以及解决几个仅靠流程图无法解释的问题。

本篇使用的Spring版本为5.2.2.RELEASE

九大组件

SpringMVC几乎所有的功能都由九大组件来完成,所以明白九大组件的作用,对于学习SpringMVC来说非常重要。

/** 文件上传解析器 */
private MultipartResolver multipartResolver;/** 区域解析器,用于国际化 */
private LocaleResolver localeResolver;/** 主题解析器 */
private ThemeResolver themeResolver;/** Handler映射信息 */
private List<HandlerMapping> handlerMappings;/** Handler适配器*/
private List<HandlerAdapter> handlerAdapters;/** Handler执行异常解析器 */
private List<HandlerExceptionResolver> handlerExceptionResolvers;/** 请求到视图的转换器 */
private RequestToViewNameTranslator viewNameTranslator;/** SpringMVC允许重定向时携带参数,存在session中,用完就销毁,所以叫FlashMap */
private FlashMapManager flashMapManager;/** 视图解析器 */
private List<ViewResolver> viewResolvers;
  • HandlerMappingHandler映射信息,根据请求携带的url信息查找处理器(Handler)。每个请求都需要对应的Handler处理。

  • HandlerAdapterHandler适配器,SpringMVC没有直接调用处理器(Handler),而是通过HandlerAdapter来调用,主要是为了统一Handler的调用方式

  • ViewResolver:视图解析器,用来将字符串类型的视图名称解析为View类型的视图。ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。

  • MultipartResolver:文件上传解析器,主要用来处理文件上传请求

  • HandlerExceptionResolver:Handler执行异常解析器,用来对异常进行统一处理

  • RequestToViewNameTranslator:请求到视图的转换器

  • LocaleResolver:区域解析器,用于支持国际化

  • FlashMapManagerSpringMVC允许重定向时携带参数,存在session中,用完就销毁,所以叫FlashMap

  • ThemeResolver:主题解析器,用于支持不同的主题

九大组件中最重的的前三个,HandlerMappingHandlerAdapterViewResolver,因为这是阅读源码时,避不开的三个组件。

调试准备

搭建一个基本的Spring web项目即可

Controller部分

@Controller
public class IndexController {@RequestMapping("/index/home")public String home(String id, Student student, @RequestParam("code") String code) {System.out.println(student.getName());return "index";}@ResponseBody@RequestMapping("/index/list")public String list() {return "success";}
}

Entity部分

public class Student {private String name;private Integer gender;// getter、setter
}

还是那句话,Spring源码非常庞大,不能只见树木不见森林,需要有针对性的阅读,所以本篇只需要关注主体流程即可。

搜索Java知音公众号,回复“后端面试”,送你一份Java面试题宝典.pdf

核心方法

我们都知道,SpringMVC有一个用来分发请求的前端控制器DispatcherServlet,其中用来处理请求的方法就是doService,该方法定义如下

doService

/*** Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}* for the actual dispatching.*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);// Keep a snapshot of the request attributes in case of an include,// to be able to restore the original attributes after the include.Map<String, Object> attributesSnapshot = null;if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap<>();Enumeration<?> attrNames = request.getAttributeNames();while (attrNames.hasMoreElements()) {String attrName = (String) attrNames.nextElement();if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {attributesSnapshot.put(attrName, request.getAttribute(attrName));}}}// Make framework objects available to handlers and view objects.request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());if (this.flashMapManager != null) {FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);if (inputFlashMap != null) {request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));}request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);}try {// 真正执行的方法doDispatch(request, response);}finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// Restore the original attribute snapshot, in case of an include.if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}}
}

doDispatch

doDispatchdoService中真正用来处理请求的方法

/*** 实际处理请求的方法*/
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 {// 校验是否是文件上传请求processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.// 为当前请求找到一个合适的处理器(Handler)// 返回值是一个HandlerExecutionChain,也就是处理器执行链mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.// 根据HandlerExecutionChain携带的Handler找到合适的HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.// 处理GET请求的缓存String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}// 执行拦截器的preHandle方法if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.// 利用HandlerAdapter来执行Handler里对应的处理方法mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}// 如果没有设置视图,则应用默认的视图名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);}// 根据ModelAndView对象解析视图processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);}catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));}finally {if (asyncManager.isConcurrentHandlingStarted()) {// Instead of postHandle and afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}
}

该方法就是SpringMVC处理请求的整体流程,其中涉及到几个重要的方法。

getHandler

该方法定义如下

/*** Return the HandlerExecutionChain for this request.* 为这个request返回一个HandlerExecutionChain*/
@Nullable
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;
}

调试信息如下

根据调试信息可以看出,getHandler方法主要是从List<HandlerMapping> handlerMappings集合中遍历查找一个合适的处理器(Handler),返回的结果是一个HandlerExecutionChain。然后再根据HandlerExecutionChain里携带的Handler去获取HandlerAdapter

getHandlerAdapter

getHandlerAdapter方法定义如下

 /*** Return the HandlerAdapter for this handler object.* @param handler the handler object to find an adapter for* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.*/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");}

调试信息如下

同样getHandlerAdapter方法主要是从List<HandlerAdapter> handlerAdapters集合中遍历查找一个合适的处理器适配器(HandlerAdapter),返回的结果是一个HandlerAdapter

可以看到此处HandlerAdapter真正的实现类是RequestMappingHandlerAdapter

processDispatchResult

processDispatchResult方法主要根据方法执行完成后封装的ModelAndView,转发到对应页面,定义如下

/*** Handle the result of handler selection and handler invocation, which is* either a ModelAndView or an Exception to be resolved to a ModelAndView.*/
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);}}// Did the handler return a view to render?if (mv != null && !mv.wasCleared()) {// 主要调用该方法渲染视图render(mv, request, response);if (errorView) {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 forwardreturn;}if (mappedHandler != null) {// Exception (if any) is already handled..mappedHandler.triggerAfterCompletion(request, response, null);}
}

render

render方法定义如下

/*** Render the given ModelAndView.* <p>This is the last stage in handling a request. It may involve resolving the view by name.* @param mv the ModelAndView to render* @param request current HTTP servlet request* @param response current HTTP servlet response* @throws ServletException if view is missing or cannot be resolved* @throws Exception if there's a problem rendering the view*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// Determine locale for request and apply it to the response.Locale locale =(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());response.setLocale(locale);View view;String viewName = mv.getViewName();if (viewName != null) {// We need to resolve the view name.// 根据给定的视图名称,解析获取View对象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 {// No need to lookup: the ModelAndView object contains the actual View object.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 {if (mv.getStatus() != null) {response.setStatus(mv.getStatus().value());}view.render(mv.getModelInternal(), request, response);}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("Error rendering view [" + view + "]", ex);}throw ex;}
}

resolveViewName

resolveViewName方法定义如下

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

调试信息如下

根据调试信息可以看到真正解析视图的ViewResolver的是InternalResourceViewResolver类,也就是我们经常配置的一项类型

<!-- 定义视图文件解析 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"><property name="prefix" value="/WEB-INF/views/" /><property name="suffix" value=".html" />
</bean>

至此我们就得到了SpringMVC处理请求的完整逻辑

SpringMVC处理请求的整个流程已经梳理清楚了。

但是,有两个重要的问题没有解决,那就是:参数绑定和返回值处理。

> 因为在编写Controller里面的方法的时候,各种类型的参数都有,SpringMVC是怎么处理不同类型的参数的呢?
> SpringMVC处理请求完成后,一定会返回ModelAndView吗,如果加了@ResponseBody注解呢?

搜索Java知音公众号,回复“后端面试”,送你一份Java面试题宝典.pdf

参数绑定

在整个流程中,还有一个最重要的方法,那就是真正执行handler的方法,参数的绑定和返回值的处理都在这个方法里,也就是

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

handle

handle方法的作用是根据请求参数,执行真正的处理方法,并且返回合适的ModelAndView对象,也有可能返回null。该方法定义如下
AbstractHandlerMethodAdapter类中

/*** This implementation expects the handler to be an {@link HandlerMethod}.*/
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return handleInternal(request, response, (HandlerMethod) handler);
}

可以看到这个方法实现只有一行代码

handleInternal

继续深入handleInternal方法

@Override
protected ModelAndView handleInternal(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ModelAndView mav;// 校验指定的请求以获取受支持的方法类型(GET、POST等)和所需的sessioncheckRequest(request);// Execute invokeHandlerMethod in synchronized block if required.if (this.synchronizeOnSession) {HttpSession session = request.getSession(false);if (session != null) {Object mutex = WebUtils.getSessionMutex(session);synchronized (mutex) {mav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No HttpSession available -> no mutex necessarymav = invokeHandlerMethod(request, response, handlerMethod);}}else {// No synchronization on session demanded at all...// 真正执行handler的方法mav = invokeHandlerMethod(request, response, handlerMethod);}if (!response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);}else {prepareResponse(response);}}return mav;
}

invokeHandlerMethod

继续深入invokeHandlerMethod方法

/*** Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}* if view resolution is required.* 执行@RequestMapping标注的handler方法,如果需要解析视图就准备一个ModelAndView*/
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);// HandlerMethod接口封装执行方法的信息,提供对方法参数,方法返回值,方法注释等的便捷访问。ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);if (this.argumentResolvers != null) {invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}invocableMethod.setDataBinderFactory(binderFactory);invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);// ModelAndViewContainer可以看做ModelAndView的上下文容器,关联着Model和View的信息ModelAndViewContainer mavContainer = new ModelAndViewContainer();mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));modelFactory.initModel(webRequest, mavContainer, invocableMethod);mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);asyncWebRequest.setTimeout(this.asyncRequestTimeout);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();LogFormatUtils.traceDebug(logger, traceOn -> {String formatted = LogFormatUtils.formatValue(result, !traceOn);return "Resume with async result [" + formatted + "]";});invocableMethod = invocableMethod.wrapConcurrentResult(result);}// 真正执行Handler的方法invocableMethod.invokeAndHandle(webRequest, mavContainer);if (asyncManager.isConcurrentHandlingStarted()) {return null;}// 获取ModelAndeView对象return getModelAndView(mavContainer, modelFactory, webRequest);}finally {webRequest.requestCompleted();}
}

invokeAndHandle

invokeAndHandle方法的作用是执行并处理真正响应请求的方法,该方法定义如下

/*** Invoke the method and handle the return value through one of the* configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.* @param webRequest the current request* @param mavContainer the ModelAndViewContainer for this request* @param providedArgs "given" arguments matched by type (not resolved)*/
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 执行handler的方法Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);setResponseStatus(webRequest);if (returnValue == null) {if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}}else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}catch (Exception ex) {if (logger.isTraceEnabled()) {logger.trace(formatErrorForReturnValue(returnValue), ex);}throw ex;}
}

invokeForRequest

/*** Invoke the method after resolving its argument values in the context of the given request.* <p>Argument values are commonly resolved through* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.* The {@code providedArgs} parameter however may supply argument values to be used directly,* i.e. without argument resolution. Examples of provided argument values include a* {@link WebDataBinder}, a {@link SessionStatus}, or a thrown exception instance.* Provided argument values are checked before argument resolvers.* <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the* resolved arguments.* @param request the current request* @param mavContainer the ModelAndViewContainer for this request* @param providedArgs "given" arguments matched by type, not resolved* @return the raw value returned by the invoked method* @throws Exception raised if no suitable argument resolver can be found,* or if the method raised an exception* @see #getMethodArgumentValues* @see #doInvoke*/
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 获取参数Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}// 执行return doInvoke(args);
}

真正的执行无非就是通过反射invoke,所以更重要的是参数是如何绑定的,详情就在getMethodArgumentValues方法

getMethodArgumentValues

getMethodArgumentValues方法用于从request请求中获取真正的参数,返回的是Object数组,该方法定义如下

/*** Get the method argument values for the current request, checking the provided* argument values and falling back to the configured argument resolvers.* <p>The resulting array will be passed into {@link #doInvoke}.* @since 5.1.2*/
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 获取方法上所有的参数MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {// Leave stack trace for later, exception may actually be resolved and handled...if (logger.isDebugEnabled()) {String exMsg = ex.getMessage();if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {logger.debug(formatArgumentError(parameter, exMsg));}}throw ex;}}return args;
}


根据调试信息可以看到,用来处理请求参数的类是HandlerMethodArgumentResolver接口的实现类HandlerMethodArgumentResolverComposite,此时正在处理的参数是一个Student对象,并且已经把值注绑定了,也就是说真正执行绑定的是方法resolveArgument

resolveArgument

resolveArgument是真正执行绑定的的方法

/*** Iterate over registered* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}* and invoke the one that supports it.* @throws IllegalArgumentException if no suitable argument resolver is found*/
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 获取合适的参数解析器HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);if (resolver == null) {throw new IllegalArgumentException("Unsupported parameter type [" +parameter.getParameterType().getName() + "]. supportsParameter should be called first.");}// 执行参数绑定return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

getArgumentResolver

getArgumentResolver该方法用于执行参数的绑定,定义如下

/*** Find a registered {@link HandlerMethodArgumentResolver} that supports* the given method parameter.*/
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);if (result == null) {for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {if (resolver.supportsParameter(parameter)) {result = resolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;
}

该方法的逻辑就是先从argumentResolver缓存中找到能够执行参数绑定的HandlerMethodArgumentResolver,如果找不到就从HandlerMethodArgumentResolver找,SpringMVC支持的HandlerMethodArgumentResolver一共有26种,用来解析各种类型的参数

根据博主的调试可以知道

  • RequestParamMethodArgumentResolver:处理普通参数(基本类型、包装类型、String),不管加不加@RequestParam注解

  • ServletModelAttributeMethodProcessor:处理POJO类型的参数,比如自定义的Student对象

  • RequestResponseBodyMethodProcessor:处理@RequestBody注解类型的参数

有兴趣的同学可以试试更多不同形式的参数。更多内容:SpringBoot内容聚合

resolveArgument

由于不同类型的参数有不同的HandlerMethodArgumentResolver来处理,此处选取POJO类型参数的注入实现,对应的参数解析类是ModelAttributeMethodProcessor,其中resolveArgument方法用来解析(绑定)参数方法定义如下

/*** Resolve the argument from the model or if not found instantiate it with* its default if it is available. The model attribute is then populated* with request values via data binding and optionally validated* if {@code @java.validation.Valid} is present on the argument.* @throws BindException if data binding and validation result in an error* and the next method parameter is not of type {@link Errors}* @throws Exception if WebDataBinder initialization fails*/
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");// 获取参数名String name = ModelFactory.getNameForParameter(parameter);// 获取参数上的ModelAttribute注解ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);if (ann != null) {mavContainer.setBinding(name, ann.binding());}Object attribute = null;BindingResult bindingResult = null;if (mavContainer.containsAttribute(name)) {attribute = mavContainer.getModel().get(name);}else {// Create attribute instancetry {// 创建参数类型的实例(未注入值),底层就是通过反射调用构造方法attribute = createAttribute(name, parameter, binderFactory, webRequest);}catch (BindException ex) {if (isBindExceptionRequired(parameter)) {// No BindingResult parameter -> fail with BindExceptionthrow ex;}// Otherwise, expose null/empty value and associated BindingResultif (parameter.getParameterType() == Optional.class) {attribute = Optional.empty();}bindingResult = ex.getBindingResult();}}if (bindingResult == null) {// Bean property binding and validation;// skipped in case of binding failure on construction.WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);if (binder.getTarget() != null) {if (!mavContainer.isBindingDisabled(name)) {// 真正执行绑定(值注入)的方法bindRequestParameters(binder, webRequest);}validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {throw new BindException(binder.getBindingResult());}}// Value type adaptation, also covering java.util.Optionalif (!parameter.getParameterType().isInstance(attribute)) {attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);}bindingResult = binder.getBindingResult();}// Add resolved attribute and BindingResult at the end of the modelMap<String, Object> bindingResultModel = bindingResult.getModel();mavContainer.removeAttributes(bindingResultModel);mavContainer.addAllAttributes(bindingResultModel);return attribute;
}


根据调试信息也可以看到bindRequestParameters(binder, webRequest)执行完成之后,POJO类型的参数已经完成了绑定。

bindRequestParameters

/*** This implementation downcasts {@link WebDataBinder} to* {@link ServletRequestDataBinder} before binding.* @see ServletRequestDataBinderFactory*/
@Override
protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);Assert.state(servletRequest != null, "No ServletRequest");ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;// 执行绑定的方法servletBinder.bind(servletRequest);
}

bind

继续深入bind方法

public void bind(ServletRequest request) {// 获取所有参数的键值对MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);// 处理文件上传请求MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);if (multipartRequest != null) {bindMultipart(multipartRequest.getMultiFileMap(), mpvs);}// 把url中携带的参数也加入到MutablePropertyValuesaddBindValues(mpvs, request);// 执行绑定(注入值)doBind(mpvs);
}

由于调用层次过深,所以无法一步步列出下面的步骤,doBind方法的原理还是通过调用POJO对象里的setter方法设置值,可以查看最终的调试信息

根据调试信息可以看到,最终执行的还是POJO对象的setter方法,具体执行的类是BeanWrapperImpl

了解了参数的绑定,再来看返回值的处理。

更多内容:SpringBoot内容聚合

返回值处理

invokeAndHandle

回到源码invokeAndHandle方法处(ServletInvocableHandlerMethod类中),该方法定义如下

/*** Invoke the method and handle the return value through one of the* configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.* @param webRequest the current request* @param mavContainer the ModelAndViewContainer for this request* @param providedArgs "given" arguments matched by type (not resolved)*/
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);setResponseStatus(webRequest);if (returnValue == null) {if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {disableContentCachingIfNecessary(webRequest);mavContainer.setRequestHandled(true);return;}}else if (StringUtils.hasText(getResponseStatusReason())) {mavContainer.setRequestHandled(true);return;}mavContainer.setRequestHandled(false);Assert.state(this.returnValueHandlers != null, "No return value handlers");try {// 真正处理不同类型返回值的方法this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);}catch (Exception ex) {if (logger.isTraceEnabled()) {logger.trace(formatErrorForReturnValue(returnValue), ex);}throw ex;}
}

真正处理不同类型的返回值的方法是handleReturnValue方法

handleReturnValue

/*** Iterate over registered {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers} and invoke the one that supports it.* @throws IllegalStateException if no suitable {@link HandlerMethodReturnValueHandler} is found.*/
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {// 根据返回值个返回值类型选取合适的HandlerMethodReturnValueHandlerHandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());}// 真正的处理返回值handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

selectHandler

@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {boolean isAsyncValue = isAsyncReturnValue(value, returnType);for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {continue;}if (handler.supportsReturnType(returnType)) {return handler;}}return null;
}


根据调试信息可以看到,SpringMVC为返回值提供了15个HandlerMethodReturnValueHandler的实现了来处理不同类型的返回值。

事实上,用来处理@ResponseBody类型的是RequestResponseBodyMethodProcessor

如果对前文参数绑定还有印象的话,会发现@RequestBody类型参数绑定也是用的这个类。

继续跟进RequestResponseBodyMethodProcessor类的handleReturnValue方法

handleReturnValue

RequestResponseBodyMethodProcessor类的handleReturnValue方法定义如下

这里设置了一个非常重要的属性requestHandled,这个属性关系到是否需要返回ModelAndView对象

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {// 设置该请求是否已在处理程序中完全处理,例如@ResponseBody方法不需要视图解析器,此处就可以设置为true。// 当控制器方法声明类型为ServletResponse或OutputStream的参数时,也可以设置此标志为true。 // 这个属性设置成true之后,上层getModelAndView获取ModelAndView时会返回Null,因为不需要视图。// 默认值为falsemavContainer.setRequestHandled(true);ServletServerHttpRequest inputMessage = createInputMessage(webRequest);ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);// Try even with null return value. ResponseBodyAdvice could get involved.// 底层就是利用java.io.OutputStreamWriter类把返回值写到网络IOwriteWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

继续深入writeWithMessageConverters方法,一步步调试到最后,底层就是利用java.io.OutputStreamWriter类把返回值写到网络IO

由于handleReturnValuerequestHandled设置成了true,上层在调用getModelAndView方法时会返回null,表示该请求不需要视图。感兴趣的同学自己调试一下便知。

总结

本文主要从源码的阅读和调试的角度,整体的讲解了SpringMVC处理请求的整个流程,并且讲解了参数的绑定以及返回值的处理。相信大家看完后,结合自己的调试信息,会对SpringMVC的请求处理过程有一个更深入的理解。

热门内容:
  • 今天终于搞懂了:为什么 Java 的 main 方法必须是 public static void?

  • 七个开源的 SpringBoot 前后端分离项目,Star过千,快去收藏夹吃灰吧!

  • 道友自诉:入职中软一个月(外包华为)就离职了!

  • 腾讯推出高性能 RPC 开发框架

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
明天见(。・ω・。)

面试官:小伙汁,你画的SpringMVC请求处理过程是从网上抄的吧?相关推荐

  1. 漫画:狼厂校招面试题(面试官说要和我画圈圈...)

    今天是小浩算法"365刷题计划"第78天.这次小浩又出去面试了,面试官说想和我画圈圈(原题为狼厂校招面试题),想起来还有点羞羞的. 01 PART 画圈圈 !@#¥%--& ...

  2. ssm中怎么使tomcat一起动就执行一个controller_【200期】面试官:你能简单说说 SpringMVC 的执行原理吗?...

    点击上方"Java面试题精选",关注公众号 面试刷图,查缺补漏 >>号外:往期面试题,10篇为一个单位归置到本公众号菜单栏->面试题,有需要的欢迎翻阅 阶段汇总集 ...

  3. 面试官:请你谈谈Java的类加载过程

    刚刚走出校门的应届毕业生,如果在去寻求一份Java开发的工作时,你的面试官很有可能一边看着你的简历,一边漫不经心地问你:了解过Java类的加载过程吗? 这个时候你一定要注意了,虽然这是一个老生常谈的问 ...

  4. 面试官:CSS如何画一个三角形?原理是什么?

    一.前言 在前端开发的时候,我们有时候会需要用到一个三角形的形状,比如地址选择或者播放器里面播放按钮 通常情况下,我们会使用图片或者svg去完成三角形效果图,但如果单纯使用css如何完成一个三角形呢? ...

  5. 美团面试官:为什么不建议把数据库部署在Docker容器内?

    点击上方蓝色字体,选择"标星公众号" 优质文章,第一时间送达 关注公众号后台回复pay或mall获取实战项目资料+视频 年初送15.6英寸笔记本电脑,吃鸡超级爽,不爱可折现!! 作 ...

  6. 知乎高赞:如果你是一个 Java 面试官,你会问哪些问题....

    注:本文内容选自公众号<Java面试题精选>,内容比较丰富,帮助大家做面试前的准备,可以省不少时间.欢迎收藏点赞,也欢迎去围观原号主! 不断收集整理,汇总网上面试知识点,方便面试前刷题,希 ...

  7. 面试官本拿求素数搞我,但被我优雅的“回击“了(素数筛)

    原创公众号(希望能支持一下):bigsai 转载请联系bigsai 文章收录在github 求star 前言 现在的面试官,是无数开发者的梦魇,能够吊打面试官的属实不多,因为大部分面试官真的有那么那几 ...

  8. 只导表前10条数据_【205期】面试官:数据量很大的情况下,对于分页查询你有什么优化方案吗?...

    点击上方"Java面试题精选",关注公众号 面试刷图,查缺补漏 >>号外:公众号改版后文章顺序不固定,欢迎大家把我们面试题精选这个公众号设置为星标,感谢大家一年的支持! ...

  9. 面试官:你知道怎么求素数吗?

    摘要:面试官:你知道怎么求素数吗?我:求素数? 本文分享自华为云社区<很多人不知道的求素数的正确方法>,原文作者:bigsai . 前言 现在的面试官,是无数开发者的梦魇,能够吊打面试官的 ...

最新文章

  1. matlab 分段式规范作图
  2. bmob php支付,基于Bmob在小程序端实现一键支付
  3. linux上卸载kafka,kafka安装在linux上的安装
  4. php5.3连接sqlserver2005
  5. OpenCV防止数据溢出saturate_cast
  6. C++基础::string
  7. vue使用国密(sm2)
  8. 堆排序(C语言实现)
  9. 目标跟踪 — MOSSE
  10. gliffy 绘图软件
  11. 谷歌离线地图WMS/WMTS服务
  12. 校招行测笔试-图形推理
  13. java编写个人所得税_个人所得税JAVA算法
  14. [UE4C++程序]GameModule与Plugin
  15. 神经网络训练不起来,怎么办?
  16. 【wordpress】Elementor插件图标显示错误:显示为空方格
  17. win10 无法删除 注册表 蓝牙_Win10蓝牙无法删除设备蓝牙鼠标无法连接解决方法...
  18. STM32之vl53l0x读取距离
  19. 阿里云code结合git管理代码,运用webHook同步部署服务器代码(php)
  20. 原创:用python把链接指向的网页直接生成图片的http服务及网站(含源码及思想)...

热门文章

  1. 算法总结---最常用的五大算法(算法题思路)
  2. Maven-环境配置
  3. Python 函数初识 (1)
  4. javascript禁止修改对象
  5. 敏捷开发日常跟进系列之二:燃尽图(中)
  6. 破一个行业ERP的感想
  7. 上传图片并生成缩略图
  8. 【直播】闫强:文本分类上分利器 -- Bert微调技巧大全
  9. Matlab数据的可视化 -- 三维表面图
  10. 【ACM】奇怪的回文数