13、SpringMVC进阶:实际开发中异常处理方案

本文深入解析Spring MVC中的常见异常类型,并提供三种核心异常处理方案:自定义4xx/5xx错误页面、使用@ControllerAdvice进行全局异常捕获以及重写BasicErrorController。包含完整代码示例与前后端分离/不分离场景下的最佳实践指南。

Spring MVC中的异常

首先了解下 Spring MVC中定义的一些异常。

异常说明
HttpRequestMethodNotSupportedException请求处理程序不支持具体请求方法
HttpMediaTypeNotSupportedExceptionMediaType不支持
HttpMediaTypeNotAcceptableException当请求处理程序无法生成客户端可接受的MediaTyp时引发异常。
MissingPathVariableException方法在从URL提取的URI变量中不存在
MissingServletRequestParameterException指示缺少参数
ServletRequestBindingException扩展ServletException
ConversionNotSupportedException当找不到适用于bean属性的编辑器或转换器时引发异常。
TypeMismatchException尝试设置bean属性时,类型不匹配引发异常。
HttpMessageNotReadableException消息转换器read 时失败
HttpMessageNotWritableException消息转换器write 时失败
MethodArgumentNotValidException对带有{@code@Valid}注释的参数进行验证失败时引发的异常
MissingServletRequestPartExceptionmultipart/form-data请求时,找不到对应名称
BindException绑定错误
NoHandlerFoundException当DispatcherServlet找不到请求的处理程序时,它会发送404
AsyncRequestTimeoutException异步请求超时异常

1. 指定4xx、5xx错误页面

思路

spring boot中的DefaultErrorViewResolver解析器负责对错误视图进行处理,不同的响应状态码,比如500异常,会解析为error/500的视图名。

然后使用模板处理器TemplateAvailabilityProvider,当存在对应模板引擎的视图页面时,会直接跳转到对应视图。

那么,我们只需要引入模板引擎,然后在对应的路径添加页面就可以实现跳转到指定的错误页面了。

案例

1、 添加模板引擎thymeleaf;

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

1、 在resources/templates/error目录下添加错误页面;
 
比如500错误页面,演示效果所以很简单。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>500</title>
</head>
<body>
<h1>服务器500异常</h1>
<ul>
    <li>异常发生的时间 [[${timestamp}]]</li>
    <li>异常路径 [[${path}]]</li>
    <li>错误信息 [[${error}]]</li>
</ul>
</body>
</html>

1、 直接访问异常接口,发现返回了我们指定的页面;
 

2. 全局异常捕获

注解介绍

全局异常捕获涉及到以下几个重要的注解。

@ControllerAdvice是spring-web-5.3.8.jar包提供的一个注解,在AOP中,Advice是增强的意思,所以@ControllerAdvice可以理解为一个增强@Controller注解,可用于控制器异常统一处理。

@ExceptionHandler也是spring-web-5.3.8.jar包提供的一个注解,意为异常处理器,可以标注在@Controller类的方法上,当这个@Controller类某个方法发生异常时,被@ExceptionHandler标注的方法会执行。这个注解只有一个属性value,配置项为异常的class名,当发生异常会先进行匹配,value有这个异常的@ExceptionHandler会执行,没有再往父类上找。

@ResponseStatus的作用就是为了改变HTTP响应的状态码,可以在@ExceptionHandler标注的方法上设置不同异常的响应状态码。

使用案例

定义一个@ControllerAdvice注解标识的类,配置异常处理器即可。

@Slf4j
@ControllerAdvice
public class ExceptionControllerAdvice {
   
     

    // 最后异常处理器,没被匹配到的Exception异常都由这里处理
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String,Object> errorHandler(Exception ex) {
   
     
        ex.printStackTrace();
        return getExceptionModel(500,ex.getMessage(),ex.getClass().getName(),Arrays.toString(ex.getStackTrace()));
    }

    // 处理NoHandlerFoundException Exception
    @ResponseBody
    @ExceptionHandler(value = NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Map<String,Object> errorHandler(NoHandlerFoundException ex) {
   
     
        ex.printStackTrace();
        return getExceptionModel(404,ex.getMessage(),ex.getClass().getName(),Arrays.toString(ex.getStackTrace()));
    }

    // 处理ArithmeticException 返回异常页面
    @ExceptionHandler(value = ArithmeticException.class)
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ModelAndView errorHandler(ArithmeticException ex) {
   
     
        ex.printStackTrace();
        return new ModelAndView("error/500");
    }

    // 获取异常中的信息封装为MAP
    private Map<String,Object> getExceptionModel(int code,String msg,String execName,String stackTrace){
   
     
        Map<String,Object> map = new HashMap<>();
        map.put("code", code);
        map.put("msg",msg);
        map.put("execName",execName);
        map.put("stackTrace", stackTrace);
        return map;
    }
}

注意事项

1、 可以使用@RestControllerAdvice,是@ControllerAdvice和@ResponseBody的组合注解,表示整个类所有方法都返回Json格式;
2、 404异常是不能被@ControllerAdvice捕获的,因为有很多静态资源的映射器一直存在,获取的时候也会获取到它们,只会在执行的时候设置response为404,想要抛出异常,需要特殊配置;

spring:
  mvc:
    servlet:
      load-on-startup: 1
    # 抛出找不到映射器异常
    throw-exception-if-no-handler-found: true
    # 设置静态资源映射目录
    static-path-pattern: /statics/**

1、 返回值可以返回Json,或者ModelView;
2、 并不是所有异常都能捕获,比如Security中的很多异常都是自己的异常过滤器处理掉了;
3、 某些框架会对异常进行层层包装,这时只会捕获到最外层的异常;

3. 重写BasicErrorController

可以在ErrorMvcAutoConfiguration自动配置类中,可以看到注入了BasicErrorController异常访问控制器,我们可以覆盖这个Bean,来进行自定义处理。

    @Bean
    @ConditionalOnMissingBean(
        value = {
   
     ErrorController.class},
        search = SearchStrategy.CURRENT
    )
    public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
   
     
        return new BasicErrorController(errorAttributes, this.serverProperties.getError(), (List)errorViewResolvers.orderedStream().collect(Collectors.toList()));
    }

我们可以自定义这两个方法的处理,比如自定义Json格式,还可以将request中的更多错误信息返回。具体怎么做,可以自己去实现,这里就给出思路了。。。
 

在实际开发怎么处理

在实际开发中,可以为几种情况。

1、 前后端不分离,这种页面和后台在一起的项目,可以重写BasicErrorController,定义自己风格的错误页面;
2、 前后分离,这种一般都是JSON交互数据,后端不可能还去搞一些页面资源,所以后端直接使用全局异常捕获都返回JSON信息,前端根据不同的状态码去跳转页面即可;

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: