Spring REST如何统一错误处理呢?
我们日常开发中,经常看见一个后端系统返回的错误处理都具有统一的样式,那么这种统一错误处理是如何实现的呢?
下文笔者将一一道来,如下所示
它将抛出handleMethodArgumentNotValid
下文笔者将一一道来,如下所示
SpringBoot中 /error映射提供一个BasicErrorController控制器 用于处理所有错误 并提供getErrorAttributes可生成带有错误信息,HTTP状态码和异常消息的JSON响应块
BasicErrorController.java源码
package org.springframework.boot.autoconfigure.web.servlet.error; //... @Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController { //... @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); return new ResponseEntity<>(body, status); }
SpringBoot生成自定义异常的示例 使用@ControllerAdvice处理自定义异常
自定义异常示例
package com.java265.error; public class UserNotFoundException extends RuntimeException { public UserNotFoundException(Long id) { super("User id not found : " + id); } } 当未找到UserId时,可使用UserNotFoundException异常例
UserController.java
package com.java265; //... @RestController public class UserController { @Autowired private UserRepository repository; // Find @GetMapping("/Users/{id}") User findOne(@PathVariable Long id) { return repository.findById(id) .orElseThrow(() -> new UserNotFoundException(id)); } //... } //此代码会返回http代码500错误//当未找到userId,最合理的情况应该返回404错误而非500错误
package com.java265.error; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.servlet.http.HttpServletResponse; import javax.validation.ConstraintViolationException; import java.io.IOException; import java.util.Date; import java.util.LinkedHashMap; import java.util.list; import java.util.Map; import java.util.stream.Collectors; @ControllerAdvice public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler { // Let Spring BasicErrorController handle the exception, we just override the status code @ExceptionHandler(UserNotFoundException.class) public void springHandleNotFound(HttpServletResponse response) throws IOException { response.sendError(HttpStatus.NOT_FOUND.value()); } //... } //返回404 curl localhost:8080/Users/5 { "timestamp":"2022-02-5T21:11:18.360+0000", "status":404, "error":"Not Found", "message":"User id not found : 5", "path":"/Users/5" }
自定义json返回信息
package com.java265.error; import com.fasterxml.jackson.annotation.JsonFormat; import java.time.LocalDateTime; public class CustomErrorResponse { @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss") private LocalDateTime timestamp; private int status; private String error; //...getters setters }
package com.java265.error; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import java.time.LocalDateTime; @ControllerAdvice public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(UserNotFoundException.class) public ResponseEntity<CustomErrorResponse> customHandleNotFound(Exception ex, WebRequest request) { CustomErrorResponse errors = new CustomErrorResponse(); errors.setTimestamp(LocalDateTime.now()); errors.setError(ex.getMessage()); errors.setStatus(HttpStatus.NOT_FOUND.value()); return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND); } //... } Terminal curl localhost:8080/Users/5 { "timestamp":"2022-02-5 21:11:19", "status":404, "error":"User id not found : 5" }
JSR 303验证错误
Spring @valid验证错误它将抛出handleMethodArgumentNotValid
package com.java265.error; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.servlet.http.HttpServletResponse; import javax.validation.ConstraintViolationException; import java.io.IOException; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @ControllerAdvice public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler { //... // @Validate For Validating Path Variables and Request Parameters @ExceptionHandler(ConstraintViolationException.class) public void constraintViolationException(HttpServletResponse response) throws IOException { response.sendError(HttpStatus.BAD_REQUEST.value()); } // error handle for @Valid @Override protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { Map<String, Object> body = new LinkedHashMap<>(); body.put("timestamp", new Date()); body.put("status", status.value()); //Get all fields errors List<String> errors = ex.getBindingResult() .getFieldErrors() .stream() .map(x -> x.getDefaultMessage()) .collect(Collectors.toList()); body.put("errors", errors); return new ResponseEntity<>(body, headers, status); } }
ResponseEntityExceptionHandler
ResponseEntityExceptionHandler.java
package org.springframework.web.servlet.mvc.method.annotation; //... public abstract class ResponseEntityExceptionHandler { @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 { HttpHeaders headers = new HttpHeaders(); if (ex instanceof HttpRequestMethodNotSupportedException) { HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED; return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request); } else if (ex instanceof HttpMediaTypeNotSupportedException) { HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE; return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request); } //... } //... }
DefaultErrorAttributes
覆盖所有异常的默认JSON错误响应 创建一个bean并扩展DefaultErrorAttributes
package com.java265.error; import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; import org.springframework.stereotype.Component; import org.springframework.web.context.request.WebRequest; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; @Component public class CustomErrorAttributes extends DefaultErrorAttributes { private static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace); //timestamp时间格式化 Object timestamp = errorAttributes.get("timestamp"); if (timestamp == null) { errorAttributes.put("timestamp", dateFormat.format(new Date())); } else { errorAttributes.put("timestamp", dateFormat.format((Date) timestamp)); } //插入键 errorAttributes.put("version", "2.4"); return errorAttributes; } }
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。