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; } }
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。


