我正在尝试将错误处理集中在我的 Spring Boot 应用程序中。目前我只处理一个潜在的异常 (NoSuchElementException),这是控制器建议:
import java.util.NoSuchElementException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(NoSuchElementException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public DispatchError dispatchNotFound(NoSuchElementException exception) {
System.out.println("asdasdasd");
return new DispatchError(exception.getMessage());
}
}
这里是抛出异常的服务:
import java.util.List;
import com.deliveryman.deliverymanapi.model.entities.Dispatch;
import com.deliveryman.deliverymanapi.model.repositories.DispatchRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DaoService {
@Autowired
DispatchRepository dispatchRepo;
public Dispatch findByShipmentNumber(long shipmentNumber) {
return dispatchRepo.findById(shipmentNumber).orElseThrow();
}
public List<Dispatch> findByUser(String user, String status) {
if(status == null) {
return dispatchRepo.findByOriginator(user).orElseThrow();
} else {
return dispatchRepo.findByOriginatorAndStatus(user, status).orElseThrow();
}
}
public Dispatch createDispatch(Dispatch dispatch) { //TODO parameter null check exception
return dispatchRepo.save(dispatch);
}
}
问题是,一旦我向不存在的资源发送请求,显示的 json 消息是 spring 的默认消息。应该是我自定义的json错误信息(DispatchError)。
现在,这是通过向异常处理程序方法添加@ResponseBody 来解决的,但问题是我使用的是我的旧代码作为参考,它在没有@ResponseBody 注释的情况下按预期工作。
谁能解释一下为什么会这样?
答案 0 :(得分:3)
使用 @ResponseBody
@ControllerAdvice
@ResponseBody
public class ExceptionController {
...
或将 @ControllerAdvice
替换为 @RestControllerAdvice
。
在我的计算机上使用您的控制器建议中的来源进行了测试和验证。
来自 @RestControllerAdvice
的来源
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
...
因此,@RestControllerAdvice
是
@ControllerAdvice
@ResponseBody
来自 @ResponseBody
的源文档
指示方法返回值的注解应该绑定到 网络响应体。支持带注释的处理程序方法。
仅使用 @ControllerAdvice
的替代方法:
@ControllerAdvice
public class ExceptionHandlerAdvice {
@ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<DispatchError> dispatchNotFound(NoSuchElementException exception) {
return new ResponseEntity<>(new DispatchError(exception.getMessage()), HttpStatus.NOT_FOUND);
}
}
对于您的旧应用中发生的事情,我确实有一个理论。根据您问题中的建议和下面的错误处理程序,我可以创建一个行为,其中 DispatchError
实例似乎是由建议返回的(执行了建议),但实际上是由错误控制器返回的。
package no.mycompany.myapp.error;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
@RestController
@RequiredArgsConstructor
public class ErrorHandler implements ErrorController {
private static final String ERROR_PATH = "/error";
private final ErrorAttributes errorAttributes;
@RequestMapping(ERROR_PATH)
DispatchError handleError(WebRequest webRequest) {
var attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE));
return new DispatchError((String) attrs.get("message"));
}
@Override
public String getErrorPath() {
return ERROR_PATH;
}
}
将 ErrorController
的实现放入类路径,替换 Spring 的 BasicErrorController
。
当强化 @RestControllerAdvice
时,错误控制器不再对 NoSuchElementException
有效。
在大多数情况下,处理所有错误的 ErrorController
实现结合用于更复杂异常的通知异常处理程序(如 MethodArgumentNotValidException
)应该就足够了。这将需要像这样的通用错误 DTO
package no.mycompany.myapp.error;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.Map;
@Data
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiError {
private long timestamp = new Date().getTime();
private int status;
private String message;
private String url;
private Map<String, String> validationErrors;
public ApiError(int status, String message, String url) {
this.status = status;
this.message = message;
this.url = url;
}
public ApiError(int status, String message, String url, Map<String, String> validationErrors) {
this(status, message, url);
this.validationErrors = validationErrors;
}
}
对于上面的ErrorHandler
,用这个替换handleError
@RequestMapping(ERROR_PATH)
ApiError handleError(WebRequest webRequest) {
var attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.of(ErrorAttributeOptions.Include.MESSAGE));
return new ApiError(
(Integer) attrs.get("status"),
(String) attrs.get("message"), // consider using predefined message(s) here
(String) attrs.get("path"));
}
关于验证异常处理的建议
package no.mycompany.myapp.error;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.stream.Collectors;
@RestControllerAdvice
public class ExceptionHandlerAdvice {
private static final String ERROR_MSG = "validation error";
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
ApiError handleValidationException(MethodArgumentNotValidException exception, HttpServletRequest request) {
return new ApiError(
HttpStatus.BAD_REQUEST.value(),
ERROR_MSG,
request.getServletPath(),
exception.getBindingResult().getFieldErrors().stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage,
// mergeFunction handling multiple errors for a field
(firstMessage, secondMessage) -> firstMessage)));
}
}
application.yml
中的相关配置server:
error:
include-message: always
include-binding-errors: always
使用 application.properties
时server.error.include-message=always
server.error.include-binding-errors=always
使用 Spring Data JPA 时,请考虑使用以下设置来关闭第二次验证。
spring:
jpa:
properties:
javax:
persistence:
validation:
mode: none
有关 Spring 中异常处理的更多信息:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc(2018 年 4 月修订) https://www.baeldung.com/exception-handling-for-rest-with-spring(2020 年 12 月 31 日)