#java #spring #spring-boot #rest #exception
#java #весна #весенняя загрузка #rest #исключение
Вопрос:
Я пытаюсь централизовать обработку ошибок в моем приложении 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 .
Может кто-нибудь объяснить мне, почему это происходит?
Комментарии:
1. Какой класс выдает
NoSuchElementException
?2. Матеус, класс обслуживания с orElseThrow() необязательного класса
Ответ №1:
Либо аннотируйте свой класс рекомендаций контроллера с помощью @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
в classpath заменяет 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
на this
@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 года) https://www.baeldung.com/exception-handling-for-rest-with-spring (31 декабря 2020 г.)
Комментарии:
1. Ссылочный код, не использующий ResponseEntity, совпадает с моим фактическим кодом; возвращает развернутый pojo. Я удовлетворен вашим ответом, но не полностью, я ожидаю более подробного объяснения того, как spring фиксирует эти исключения и как он знает, когда использовать дескриптор исключения по умолчанию или настроенный.
2. @EdgarHernandez: Я чувствую, что тема слишком обширна для ответа SO. Вы не возражаете, если я отправлю вам эту ссылку на хорошо написанную статью по этой теме? Статья была пересмотрена в 2018 году. spring.io/blog/2013/11/01/exception-handling-in-spring-mvc БР
3. @EdgarHernandez: Спасибо, что приняли мой ответ. Я добавил к своему ответу еще немного кода, который может оказаться для вас полезным. BR