Spring boot 异常配置

Posted xiaodujava

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring boot 异常配置相关的知识,希望对你有一定的参考价值。

Spring boot 异常配置

上一节 Spring boot 参数校验

Spring boot 参数校验

源码

springboot学习指南


直接上代码。。。

1.通过ExceptionHandler 处理控制器异常

1. 局部异常处理
在controller中通过注解@ExceptionHandler 修饰控制器的方法,即可处理本控制器的异常信息;
属性value 指定处理那些异常

@RestController
@RequestMapping("ex")
@Api(tags = {"测试局部异常处理"})
public class TestPartExceptionContorller {

    @GetMapping("ex3")
    @ApiOperation(value = "测试局部异常处理", httpMethod = "GET")
    public void testPartException() throws Exception {
        int i = 1 / 0; // 模拟程序报错
    }

    /*局部异常处理,只处理当前controller的异常信息*/
    @ExceptionHandler(value = {Exception.class})
    public ResponseEntity<ResultBean<Void>> handlerPartException(HttpServletRequest request, HttpServletResponse response, Exception e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ResultBean.result(HttpStatus.INTERNAL_SERVER_ERROR));
    }

}

当 请求 访问到testPartException方法时报错后,我们在handlerPartException 指定了处理Exception的异常,所以报错Exception以及子类异常都会走异常处理方法。

通过@ExceptionHandler 和@RestControllerAdvice (或者@ControllerAdvice) 做全局异常处理

全局默认异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {
    /*默认异常处理*/
    @ExceptionHandler(value = {Exception.class})
    public ResultBean<Void> defExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e, HandlerMethod method) throws Exception {
        String message = e.getMessage();
        log.error("error -- message = " + message, e);
        return ResultBean.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
    }
    ...

通过@RestControllerAdvice 指定本类为异常处理类,另@RestControllerAdvice 的value 或者basePackages 属性可以指定具体那个包下控制器;

方法defExceptionHandler为默认异常处理(因为我们指定了Exception,异常类的基类),如果程序报异常会找到指定的ControllerAdvice中的 标注了@ExceptionHandler方法有哪些能够处理改异常的方法,若没有完全匹配的则找父类一级一级想上找;最终肯定就会找到 Exception 匹配的方法处理。

上一章节 Spring boot 参数校验; 我们进行了参数校验的学习;但是返回的信息不太友好;我们这章通过全局异常处理对校验后的异常进行处理封装,同时也熟悉了全局异常处理的使用方式。

参数校验异常处理

处理MethodArgumentNotValidException 和 ConstraintViolationException 异常信息
这两个异常会在参数校验出错的时候抛出;
MethodArgumentNotValidException: 是实体bean校验出错
ConstraintViolationException: 是基本参数类型(包括string) 校验出错

   /*valid 参数校验异常处理*/
    //MethodArgumentNotValidException  ConstraintViolationException
    @ExceptionHandler(value = {ConstraintViolationException.class, MethodArgumentNotValidException.class})
    public ResponseEntity<ResultBean<Void>> constraintValidatedExceptionHandler(HttpServletRequest request, Exception e, HandlerMethod method) {
        ResponseEntity.BodyBuilder responseEntityBuilder = ResponseEntity.badRequest();
        ResultBean<Void> resultBean = ResultBean.result(HttpStatus.BAD_REQUEST);
        StringJoiner stringJoiner = new StringJoiner(";", "异常信息{ ", " }");
        if (e instanceof ConstraintViolationException) {
            ConstraintViolationException constraintViolationException = (ConstraintViolationException) e;
            Optional.ofNullable(constraintViolationException.getConstraintViolations()).orElseGet(HashSet::new)
                    .forEach(v -> {
                        String message = v.getMessage();
                        PathImpl propertyPath = (PathImpl) v.getPropertyPath();
                        String s = propertyPath.getLeafNode().asString();
                        stringJoiner.add(s + ": " + message);
                    });
        }
        if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) e;
            List<FieldError> fieldErrors = methodArgumentNotValidException.getBindingResult().getFieldErrors();
            Optional.ofNullable(fieldErrors).orElseGet(ArrayList::new).forEach(v -> {
                String field = v.getField();
                String defaultMessage = v.getDefaultMessage();
                stringJoiner.add(field + ": " + defaultMessage);
            });
        }
        resultBean.setMsg(stringJoiner.toString());
        return responseEntityBuilder.body(resultBean);
    }

我们还是使用swagger 进行http调用请求;

请求获取商品详情; 指定了id 的长度

    @ApiOperation(value = "获取商品详情", httpMethod = "GET")
    @GetMapping("product")
    public Product getProduct(@Length(max = 32, min = 32) @RequestParam String id) throws Exception {
        Product product = new Product();
        product.setId(id);
        product.setName("xiaomi 11");
        product.setPrice(199900L);
        return product;
    }

通过swagger 请求;
指定id 为11 不满足参数校验; 不满足会 报异常ConstraintViolationException
在这里插入图片描述
返回信息
code 为400; msg 也是我们封装的信息
在这里插入图片描述
请求添加商品信息
对Product 进行了参数校验;

Product

@ApiModel(description = "商品详情")
public class Product {

   @ApiModelProperty(required = true, value = "商品名称")
   @NotBlank
   @Length(min = 1, max = 500)
   private String name;

   @ApiModelProperty(required = true, value = "商品id")
   @NotBlank
   @Length(max = 32, min = 32)
   private String id;

   @ApiModelProperty(required = true, value = "商品价格,以分为单位", dataType = "java.lang.Long", example = "1000")
   @Max(Integer.MAX_VALUE)
   @Min(0)
   private Long price;

   @ApiModelProperty(required =  true, value = "生产日期", dataType = "date")
   @NotNull
   private Date pdTime;

请求方法

    @ApiOperation(value = "添加商品信息", httpMethod = "POST")
    @PostMapping("product")
    public String addProduct(@Validated @RequestBody Product product) throws Exception {
        return product.getId();
    }

swagger 访问,还是让id的长度不够32; 对java bean的校验这里会报错 MethodArgumentNotValidException 异常
在这里插入图片描述
返回信息
在这里插入图片描述

处理HttpMessageNotReadableException 异常
也是属请求参数的异常信息;当参数类型不对或者解析出错时 会报错HttpMessageNotReadableException; 例如 String转 Integer 传入的参数不是数字,还有Date类型format出错;

  /*参数转换异常处理*/
    @ExceptionHandler(value = {HttpMessageNotReadableException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public HttpEntity<ResultBean<Void>> methodArgumentNotValidExceptionHandler(HttpServletRequest request, HttpMessageNotReadableException e, HandlerMethod method) {
        String localizedMessage = e.getLocalizedMessage();
        ResultBean<Void> resultBean = ResultBean.result(HttpStatus.BAD_REQUEST);
        HttpEntity<ResultBean<Void>> httpEntity = new HttpEntity(resultBean);
        if (localizedMessage.startsWith("JSON parse error")) {
            String through_reference_chain = localizedMessage.substring(localizedMessage.lastIndexOf("through reference chain"));
            String field = through_reference_chain.substring(through_reference_chain.lastIndexOf("[\\"") + 2, through_reference_chain.lastIndexOf("\\"]"));
            resultBean.setMsg("参数解析出错!请检测参数: " + field);
        }
        return httpEntity;
    }

如上我们还可以通过 @ResponseStatus 指定 http状态码;

swagger 访问我们把日期格式输入错误; pdTime 默认格式应该为 yyyy-MM-dd HH:mm:ss.SSS格式;我们使用yyyy/MM/dd格式传输;
在这里插入图片描述
返回信息
在这里插入图片描述

自定义异常处理

创建业务异常类BusinessException; 处理自定义异常BusinessException

    /*自定义异常处理*/
    @ExceptionHandler(value = {BusinessException.class})
    public void businessExceptionHandler(HttpServletRequest request, HttpServletResponse response, BusinessException e, HandlerMethod method) throws IOException {
        log.error("异常处理: businessException;\\n【 远端请求地址:{}; 请求URI:{};" +
                        "\\n 请求方法:{}; error message:{}】",
                request.getRemoteHost(), request.getRequestURI(),
                method.getMethod().getName(), e.getLocalizedMessage());
        response.getWriter().write(new ObjectMapper().writeValueAsString(ResultBean.error(e.getCode(), e.getMsg())));
    }

请求方法访问,抛出一个 BusinessException 异常

    @GetMapping("ex")
    @ApiOperation(value = "测试自定义异常处理", httpMethod = "GET")
    public Object testMyException(@RequestParam(required = false) @ApiParam(name = "str", value = "str") String str) throws Exception {
        throw new BusinessException("test businessException", 5001);
    }

swagger 访问;处理自定义异常; 略。。。
总结
上面的示例基本包含了常用的处理方式,@ExceptionHandler 修饰的方法 我们可以返回一个具体的ReusltBean; 或者一个HttpEntity 以及 ResponseEntity; 甚至一个void;


2.通过继承ResponseEntityExceptionHandler 进行全局异常处理

@RestControllerAdvice
public class GlobalExHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        ...
    }

    @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        ...
    }

    @Override
    protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
     ...
    }
    ...
}

ResponseEntityExceptionHandler中的handleException 处理大部分的异常进行;也是通过 @ExcepitonHandler

	@ExceptionHandler({
			HttpRequestMethodNotSupportedException.class,
			HttpMediaTypeNotSupportedException.class,
			HttpMediaTypeNotAcceptableException.class,
			MissingPathVariableException.class,
			MissingServletRequestParameterException.class,
			ServletRequestBindingException.class,
			ConversionNotSupportedException.class,
			TypeMismatchException.class,
			HttpMessageNotReadableException.class,
			HttpMessageNotWritableException.class,
			MethodArgumentNotValidException.class,
			MissingServletRequestPartException.class,
			BindException.class,
			NoHandlerFoundException.class,
			AsyncRequestTimeoutException.class
		})
	@Nullable
	public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
	...

所以继承该类 重新指定异常方法即可~


下一节 Spring boot lombok

Spring boot lombok

以上是关于Spring boot 异常配置的主要内容,如果未能解决你的问题,请参考以下文章

Spring boot 异常配置

解决Myeclipse启动Spring Boot项目报出莫名异常

Spring boot 异常处理配置

Spring boot:thymeleaf 没有正确渲染片段

spring boot的spring.factories配置的不同类/以及加载时机

在Spring Boot 上配置Redis Cluster出现的异常