Spring MVC系列(13)-实际开发中异常处理方案

Spring MVC中的异常

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

异常 说明
HttpRequestMethodNotSupportedException 请求处理程序不支持具体请求方法
HttpMediaTypeNotSupportedException MediaType不支持
HttpMediaTypeNotAcceptableException 当请求处理程序无法生成客户端可接受的MediaTyp时引发异常。
MissingPathVariableException 方法在从URL提取的URI变量中不存在
MissingServletRequestParameterException 指示缺少参数
ServletRequestBindingException 扩展ServletException
ConversionNotSupportedException 当找不到适用于bean属性的编辑器或转换器时引发异常。
TypeMismatchException 尝试设置bean属性时,类型不匹配引发异常。
HttpMessageNotReadableException 消息转换器read 时失败
HttpMessageNotWritableException 消息转换器write 时失败
MethodArgumentNotValidException 对带有{@code@Valid}注释的参数进行验证失败时引发的异常
MissingServletRequestPartException multipart/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信息,前端根据不同的状态码去跳转页面即可;

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