Spring之webMvc异常处理
异常处理可以前端处理,也可以后端处理。
从稳妥的角度出发,两边都应该进行处理。
本文专门阐述如何在服务端进行http请求异常处理。
一、常见的异常类型
当我们做http请求的时候,会有各种各样的可能错误,比较常见的例如:
1.服务类异常
2.接口异常,而接口异常有各种各样的情况
究极就是接口的异常。
异常可以发生在请求的各个步骤之中,这里主要阐述网络通讯正常的情况。
这些通讯异常在方法 org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver.doResolveException(HttpServletRequest, HttpServletResponse, Object, Exception)中
有详细的描述:
if (ex instanceof HttpRequestMethodNotSupportedException) { return handleHttpRequestMethodNotSupported( (HttpRequestMethodNotSupportedException) ex, request, response, handler); } else if (ex instanceof HttpMediaTypeNotSupportedException) { return handleHttpMediaTypeNotSupported( (HttpMediaTypeNotSupportedException) ex, request, response, handler); } else if (ex instanceof HttpMediaTypeNotAcceptableException) { return handleHttpMediaTypeNotAcceptable( (HttpMediaTypeNotAcceptableException) ex, request, response, handler); } else if (ex instanceof MissingPathVariableException) { return handleMissingPathVariable( (MissingPathVariableException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestParameterException) { return handleMissingServletRequestParameter( (MissingServletRequestParameterException) ex, request, response, handler); } else if (ex instanceof ServletRequestBindingException) { return handleServletRequestBindingException( (ServletRequestBindingException) ex, request, response, handler); } else if (ex instanceof ConversionNotSupportedException) { return handleConversionNotSupported( (ConversionNotSupportedException) ex, request, response, handler); } else if (ex instanceof TypeMismatchException) { return handleTypeMismatch( (TypeMismatchException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotReadableException) { return handleHttpMessageNotReadable( (HttpMessageNotReadableException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotWritableException) { return handleHttpMessageNotWritable( (HttpMessageNotWritableException) ex, request, response, handler); } else if (ex instanceof MethodArgumentNotValidException) { return handleMethodArgumentNotValidException( (MethodArgumentNotValidException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestPartException) { return handleMissingServletRequestPartException( (MissingServletRequestPartException) ex, request, response, handler); } else if (ex instanceof BindException) { return handleBindException((BindException) ex, request, response, handler); } else if (ex instanceof NoHandlerFoundException) { return handleNoHandlerFoundException( (NoHandlerFoundException) ex, request, response, handler); } else if (ex instanceof AsyncRequestTimeoutException) { return handleAsyncRequestTimeoutException( (AsyncRequestTimeoutException) ex, request, response, handler); }
我们整理为表格:
编码 | 名称 | 备注 |
handleHttpRequestMethodNotSupported | 请求方法不支持 | 例如RequestMapping只指定了post,但用get请求 |
HttpMediaTypeNotSupportedException | 媒体类型不支持 | 例如消息转换器可能只处理JSON,但请求头确设置为xml之类的 |
HttpMediaTypeNotAcceptableException | 媒体类型不可接受 | 请求处理器无法生成客户端可以支持的响应内容 |
MissingPathVariableException | 缺失路径变量 |
请求url缺乏路径变量。例如 @RequestMapping("/show/{name}") public Object show(@PathVariable String name){} 工程师可能少写了{name} |
MissingServletRequestParameterException | 缺失Servlet请求参数 | 例如使用了@RequstParam,但是url并没有提供对应的参数 |
ServletRequestBindingException | Servlet请求绑定异常 | 在执行绑定的时候发生的异常 |
ConversionNotSupportedException | 不支持的转换异常 | 无法为bean找到匹配的编辑器或者转换器 |
TypeMismatchException | 类型不匹配异常 | 给bean复制的时候,发现类型不匹配 |
HttpMessageNotReadableException | http消息无法读取 | 当HttpMessageConverter#read方法发生错误的时候 |
HttpMessageNotWritableException | http消息无法写异常 | 当HttpMessageConverter#write方法发生错误的时候 |
MethodArgumentNotValidException | 方法参数不可用异常 | 验证 @Valid过的参数时候所发生的异常 |
MissingServletRequestPartException | 缺失Servlet请求part异常 | 请求的媒体类型是文件类型(或者是附件),但内容没有 |
BindException | 绑定异常 | |
NoHandlerFoundException | 没有找到处理器 |
By default when the DispatcherServlet can't find a handler for a request it sends a 404 response. However if its property "throwExceptionIfNoHandlerFound" is set to 默认情况下,分发器处理器程序无法找到请求的处理器的时候(例如静态资源或者请求控制器方法的时候),会发送一个404响应。此外会设置属性throwExceptionIfNoHandlerFound=true,并引发没有处理器异常。用户可以考虑配置一个HandlerExceptionResolver来处理 |
AsyncRequestTimeoutException | 异步请求超时异常。即503 错误。 |
spring和springboot中和异常/错误有关的类远不止上表那么多。
出于代码的严谨性要求,几乎所有的函数/过程都会有异常处理(try catch throw),限于篇幅,不会讨论所有的代码,也没有必要。
二、WMS中的异常配置
关于wms的介绍,可以参考: wms配置简介
wms可以处理异常的地方有几个,但我们专门考虑exception字眼的部分。
@Bean public HandlerExceptionResolver handlerExceptionResolver( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) { List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>(); configureHandlerExceptionResolvers(exceptionResolvers); if (exceptionResolvers.isEmpty()) { addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager); } extendHandlerExceptionResolvers(exceptionResolvers); HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); composite.setOrder(0); composite.setExceptionResolvers(exceptionResolvers); return composite; } /** * Override this method to configure the list of * {@link HandlerExceptionResolver HandlerExceptionResolvers} to use. * <p>Adding resolvers to the list turns off the default resolvers that would otherwise * be registered by default. Also see {@link #addDefaultHandlerExceptionResolvers} * that can be used to add the default exception resolvers. * @param exceptionResolvers a list to add exception resolvers to (initially an empty list) */ protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) { } /** * A method available to subclasses for adding default * {@link HandlerExceptionResolver HandlerExceptionResolvers}. * <p>Adds the following exception resolvers: * <ul> * <li>{@link ExceptionHandlerExceptionResolver} for handling exceptions through * {@link org.springframework.web.bind.annotation.ExceptionHandler} methods. * <li>{@link ResponseStatusExceptionResolver} for exceptions annotated with * {@link org.springframework.web.bind.annotation.ResponseStatus}. * <li>{@link DefaultHandlerExceptionResolver} for resolving known Spring exception types * </ul> */ protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers, ContentNegotiationManager mvcContentNegotiationManager) { ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver(); exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager); exceptionHandlerResolver.setMessageConverters(getMessageConverters()); exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers()); exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers()); if (jackson2Present) { exceptionHandlerResolver.setResponseBodyAdvice( Collections.singletonList(new JsonViewResponseBodyAdvice())); } if (this.applicationContext != null) { exceptionHandlerResolver.setApplicationContext(this.applicationContext); } exceptionHandlerResolver.afterPropertiesSet(); exceptionResolvers.add(exceptionHandlerResolver); ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver(); responseStatusResolver.setMessageSource(this.applicationContext); exceptionResolvers.add(responseStatusResolver); exceptionResolvers.add(new DefaultHandlerExceptionResolver()); }
通过wms机制,可以覆盖bean(handlerExceptionResolver),或者覆盖方法 configureHandlerExceptionResolvers添加异常处理解析器。
不过一般情况下,我们都没有那个必要。
一般情况下,使用DefaultHandlerExceptionResolver进行处理即可,即前文介绍的内容。
三、springboot的http请求异常
如果使用springboot开发,那么springboot会通过自动配置引入一个 org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController
这类控制器类实现了接口 org.springframework.boot.web.servlet.error.ErrorController。
根据springboot的有关描述,我们可以实现它接口org.springframework.boot.web.servlet.error.ErrorController,以便覆盖默认的实现。
例如:
@Controller public class ErrController implements ErrorController { @RequestMapping("/error") public String handleError(HttpServletRequest request,HttpServletResponse response) { int statusCode=response.getStatus(); if (statusCode == 404) { return "/main/err.html"; } else if (statusCode == 444) { return "/main/login.html"; } return ""; } }
需要注意的是,ErrorController 的代码是在wms的异常处理之后执行的。
此外,也可以只用控制器advice注解处理, 例如:SpringBoot系列——自定义统一异常处理 - huanzi-qch - 博客园 (cnblogs.com)
四、小结
spring为了少让我们的代码出现问题,还引入了一个机制:验证。例如可以验证参数验证。不过验证仅仅只是解决了少数的异常情形,虽然参数不合法的发生的数量还是比较多的。
但这个验证一般情况下,也不需要,因为一般在客户端就先处理了!