Spring MVC : 400 错误是怎么发生的 ?

本文深入剖析SpringMVC中的异常处理流程,包括异常的产生、抛出、捕获及转换为HTTP状态码的过程,详细介绍了DispatcherServlet如何利用DefaultHandlerExceptionResolver处理标准异常。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 异常的产生和抛出

Spring MVC前置处理器DispatcherServlet处理一个请求时,会尝试捕获handler匹配过程,handler adapter匹配过程和handler执行过程中的异常,该异常记做dispatchException,如下代码所示 :

	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) 
		throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
            // 记录 handler 成功执行结果
			ModelAndView mv = null;
            // 记录请求处理过程中出现的异常 : handler匹配过程, handler adapter 匹配过程, handler执行过程
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					// 这种情况下,noHandlerFound方法会根据配置决定是抛出异常,
					// 还是直接 response.sendError(404)
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
                // handler adapter 匹配过程
                // 如果 handler adapter 匹配失败,则会抛出异常 ServletException
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				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;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler. 对 handler 的真正调用, 
				// 通过所匹配到的 handler adapter 完成
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
                // 发生的任何异常都记录为 dispatchException
				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
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
           // 上面的逻辑对请求进行了处理,该处理逻辑可以分为如下四个方面 : 
           // 1. 匹配目标 handler 
           // 2. 匹配目标 handler adapter
           // 3. 使用目标 handler adapter 执行对目标 handler 的调用 
           // 4. 捕获整个过程中可能发生的异常到 dispatchException
           
           // 上面的处理流程可能有两个结果 :
           // 1. 请求被标记为完成 (比如控制器方法上使用了@ResponseStatus注解并且控制器方法正常执行完成)
           // 2. 其他情况,请求未被标记为完成
           // 对于上面第1种情况,现在 mv 是 null, dispatchException 为 null
           // 对于上面第2种情况, 现在 mv 或者 dispatchException 至少有一个不是 null
           // 现在针对以上所有情况,综合使用 processDispatchResult 进行处理,决定如何渲染响应给浏览器端
			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 afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

2. 将异常转换成HTTP状态码

上面doDispatch方法对请求处理的主体流程可以简单地理解为两个步骤 :

  1. 寻找handler并执行以处理请求(request)
    • 整个细分为以下几个方面
      • 目标 handler 匹配过程,
      • 目标 handler adapter 匹配过程,
      • 目标 handler 执行过程
      • 成功处理结果记录到 ModelAndView mv
      • 异常捕获并记录到 Exception dispatchException
  2. 加工handler请求处理结果做出响应(response)

上面第二部分由processDispatchResult方法完成 。 该方法接收第一阶段的结果,无论是ModelAndView对象,还是请求处理过程中遇到的ExceptionprocessDispatchResult方法实现如下 :


	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) {
                // 这种异常中定义了将要渲染给浏览器端的 ModelAndView 对象, 直接使用它即可
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
				// 这种情况下需要使用 processHandlerException 将异常翻译成一个 ModelAndView 对象
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
                // 如果 mv 不为 null 则说明有异常解析器处理了该异常并且找到了相应的异常视图
                // 如果 mv 为 null 则说明有异常解析器处理了该异常但是没有相应的异常视图
				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 forward
			return;
		}

		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

processDispatchResult的处理流程中调用了processHandlerException来将异常转换成ModelAndView,该方法的实现如下 :

	/**
	 * Determine an error ModelAndView via the registered HandlerExceptionResolvers.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @param handler the executed handler, or null if none chosen at the time of the exception
	 * (for example, if multipart resolution failed)
	 * @param ex the exception that got thrown during handler execution
	 * @return a corresponding ModelAndView to forward to
	 * @throws Exception if no error ModelAndView found
	 */
	@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);

		// Check registered HandlerExceptionResolvers...
		ModelAndView exMv = null;
        // 使用每一个注册的 HandlerExceptionResolver 尝试解析该异常到一个 ModelAndView 对象,
        // 如果某个 HandlerExceptionResolver 能解析该异常到一个 ModelAndView 对象,则停止并使用
        // 该 ModelAndView 对象
		if (this.handlerExceptionResolvers != null) {
			for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {            
				exMv = resolver.resolveException(request, response, handler, ex);
                // 如果当前 resolver 能解析该异常,它会对响应做相应的处理并返回一个 ModelAndView
                // 对象。该 ModelAndView 对象 exMv 可能为空或者不为空。为空表明该 resolver
                // 处理了该异常。
                // 
                // 什么是一个空的 ModelAndView 对象 ?
                // 1. 不是 null                
                // 2. 对象的  view 属性为 null 并且 model 属性为空 (比如是 new ModelAndView() )
				if (exMv != null) {
					break;
				}
			}
		}
		if (exMv != null) {
           // 如果 exMv 不为 null, 但是为空 ModelAndView 对象,也就是其 view 为 null 并且 model 为空,
           // 则说明该异常已经被处理,返回 null ModelAndView 即可
			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);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Using resolved error view: " + exMv);
			}
			WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
            // 找到了针对该异常的异常视图
			return exMv;
		}

		// 如果针对指定异常没有异常解析器能够处理它,则继续抛出该异常
		throw ex;
	}

processHandlerException对异常进行解析的过程中,使用到了属性handlerExceptionResolvers,这是一组在应用启动时初始化到DispatcherServletHandlerExceptionResolver。这组HandlerExceptionResolver bean的定义来自WebMvcConfigurationSupport,缺省情况下,它们是以下类型的3个HandlerExceptionResolver bean :

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. DefaultHandlerExceptionResolver

这3个bean中,DefaultHandlerExceptionResolver是对Spring MVC标准异常进行处理的,它会将能处理的某种异常根据异常语义转换成一个HTTP状态码(会通过调用response.sendError()),并返回一个空ModelAndView表明该异常已经被处理。下表是它能处理的异常和对应要转换成的HTTP状态码:

Spring MVC 标准异常HTTP状态码介绍
HttpRequestMethodNotSupportedException405 (SC_METHOD_NOT_ALLOWED)HTTP方法不支持
HttpMediaTypeNotSupportedException415 (SC_UNSUPPORTED_MEDIA_TYPE)MIME类型不支持
HttpMediaTypeNotAcceptableException406 (SC_NOT_ACCEPTABLE)MIME类型不被接受
MissingPathVariableException500 (SC_INTERNAL_SERVER_ERROR)路径参数缺失
MissingServletRequestParameterException400 (SC_BAD_REQUEST)Servlet请求参数缺失
ServletRequestBindingException400 (SC_BAD_REQUEST)Servlet请求处理过程中的绑定异常
ConversionNotSupportedException500 (SC_INTERNAL_SERVER_ERROR)不支持该类型转换
TypeMismatchException400 (SC_BAD_REQUEST)类型不匹配
HttpMessageNotReadableException400 (SC_BAD_REQUEST)HTTP消息不可读
使用者:HttpMessageConverter#read
HttpMessageNotWritableException500 (SC_INTERNAL_SERVER_ERROR)HTTP消息不可写
使用者:HttpMessageConverter#write
MethodArgumentNotValidException400 (SC_BAD_REQUEST)方法参数值验证失败
MissingServletRequestPartException400 (SC_BAD_REQUEST)multipart/form-data请求时,
part名称指定的part数据不存在
BindException400 (SC_BAD_REQUEST)Spring验证框架中的绑定异常
NoHandlerFoundException404 (SC_NOT_FOUND)没有找到能处理该请求的handler
AsyncRequestTimeoutException503 (SC_SERVICE_UNAVAILABLE)异步请求超时

3. 总结

基于以上分析,现在我们就不难看出Spring MVCHTTP 状态码 400错误(其实不仅仅是400错误,还有其他状态码错误)是怎么发生的了。可以简单总结如下 :

  1. Spring MVC应用启动过程中加载了一个DefaultHandlerExceptionResolver ;
    HandlerExceptionResolver能解析Spring MVC标准异常并将其转换成HTTP状态码;
  2. DispatcherServlet处理请求过程中会抛出各种异常;
  3. DefaultHandlerExceptionResolver会被调用用于解析某个异常并向响应设置相应的HTTP状态码。
    这里使用的异常到HTTP状态码的转换规则就来自上面提到的表格。

参考文章

Spring MVC DispatcherServlet 策略初始化 – initHandlerExceptionResolvers
Spring MVC : WebMvcConfigurationSupport 中定义的 HandlerExceptionResolver 组件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值