1. Обзор
В этом уроке мы рассмотрим как создать приложение/проблему+json ответы с помощью веб-библиотеки Problem Spring . Эта библиотека помогает нам избежать повторяющихся задач, связанных с обработкой ошибок.
Интегрируя Problem Spring Web в наше приложение Spring Boot, мы можем упростить способ обработки исключений в рамках нашего проекта и генерировать соответствующие ответы .
2. Проблемная библиотека
Проблема -это небольшая библиотека, предназначенная для стандартизации того, как API Rest на основе Java выражают ошибки своим потребителям.
/| Проблема – это абстракция любой ошибки, о которой мы хотим сообщить. Он содержит удобную информацию об ошибке. Давайте посмотрим представление по умолчанию Проблемы ответа:
{ "title": "Not Found", "status": 404 }
В этом случае для описания ошибки достаточно кода состояния и заголовка. Однако мы также можем добавить его подробное описание:
{ "title": "Service Unavailable", "status": 503, "detail": "Database not reachable" }
Мы также можем создавать пользовательские Проблемные объекты, которые адаптируются к нашим потребностям:
Problem.builder() .withType(URI.create("https://example.org/out-of-stock")) .withTitle("Out of Stock") .withStatus(BAD_REQUEST) .withDetail("Item B00027Y5QG is no longer available") .with("product", "B00027Y5QG") .build();
В этом уроке мы сосредоточимся на реализации проблемной библиотеки для проектов Spring Boot.
3. Проблема Весенней Веб-настройки
Поскольку это проект на основе Maven, давайте добавим зависимость problem-spring-web в pom.xml :
org.zalando problem-spring-web 0.23.0 org.springframework.boot spring-boot-starter-web 2.4.0 org.springframework.boot spring-boot-starter-security 2.4.0
Нам также нужны |/spring-boot-starter-web и spring-boot-starter-security |/зависимости. Безопасность Spring требуется начиная с версии 0.23.0 problem-spring-web .
4. Базовая конфигурация
В качестве первого шага нам нужно отключить страницу ошибок с белой меткой, чтобы вместо этого мы могли видеть наше пользовательское представление ошибок:
@EnableAutoConfiguration(exclude = ErrorMvcAutoConfiguration.class)
Теперь давайте зарегистрируем некоторые из необходимых компонентов в ObjectMapper bean:
@Bean public ObjectMapper objectMapper() { return new ObjectMapper().registerModules( new ProblemModule(), new ConstraintViolationProblemModule()); }
После этого нам нужно добавить следующие свойства в файл application.properties :
spring.resources.add-mappings=false spring.mvc.throw-exception-if-no-handler-found=true spring.http.encoding.force=true
И, наконец, нам нужно реализовать интерфейс Обработки проблем :
@ControllerAdvice public class ExceptionHandler implements ProblemHandling {}
5. Расширенная конфигурация
В дополнение к базовой конфигурации мы также можем настроить наш проект для решения проблем, связанных с безопасностью. Первым шагом является создание класса конфигурации для включения интеграции библиотеки с Spring Security:
@Configuration @EnableWebSecurity @Import(SecurityProblemSupport.class) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private SecurityProblemSupport problemSupport; @Override protected void configure(HttpSecurity http) throws Exception { // Other security-related configuration http.exceptionHandling() .authenticationEntryPoint(problemSupport) .accessDeniedHandler(problemSupport); } }
И, наконец, нам нужно создать обработчик исключений для исключений, связанных с безопасностью:
@ControllerAdvice public class SecurityExceptionHandler implements SecurityAdviceTrait {}
6. Контроллер REST
После настройки нашего приложения мы готовы создать контроллер RESTful:
@RestController @RequestMapping("/tasks") public class ProblemDemoController { private static final MapMY_TASKS; static { MY_TASKS = new HashMap<>(); MY_TASKS.put(1L, new Task(1L, "My first task")); MY_TASKS.put(2L, new Task(2L, "My second task")); } @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) public List getTasks() { return new ArrayList<>(MY_TASKS.values()); } @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) public Task getTasks(@PathVariable("id") Long taskId) { if (MY_TASKS.containsKey(taskId)) { return MY_TASKS.get(taskId); } else { throw new TaskNotFoundProblem(taskId); } } @PutMapping("/{id}") public void updateTask(@PathVariable("id") Long id) { throw new UnsupportedOperationException(); } @DeleteMapping("/{id}") public void deleteTask(@PathVariable("id") Long id) { throw new AccessDeniedException("You can't delete this task"); } }
В этом контроллере мы намеренно создаем некоторые исключения. Эти исключения будут автоматически преобразованы в объекты Problem для получения ответа application/problem+json с подробной информацией об ошибке.
Теперь давайте поговорим о встроенных чертах советов, а также о том, как создать пользовательскую Проблему реализацию.
7. Встроенные функции советов
Совет-это небольшой обработчик исключений, который улавливает исключения и возвращает правильный объект проблемы.
Существуют встроенные рекомендации для общих исключений. Следовательно, мы можем использовать их, просто бросив исключение:
throw new UnsupportedOperationException();
В результате мы получим ответ:
{ "title": "Not Implemented", "status": 501 }
Поскольку мы также настроили интеграцию с Spring Security, мы можем создавать исключения, связанные с безопасностью:
throw new AccessDeniedException("You can't delete this task");
И получить надлежащий ответ:
{ "title": "Forbidden", "status": 403, "detail": "You can't delete this task" }
8. Создание пользовательской проблемы
Можно создать пользовательскую реализацию Проблемы . Нам просто нужно расширить Абстрактную выбрасываемую проблему класс:
public class TaskNotFoundProblem extends AbstractThrowableProblem { private static final URI TYPE = URI.create("https://example.org/not-found"); public TaskNotFoundProblem(Long taskId) { super( TYPE, "Not found", Status.NOT_FOUND, String.format("Task '%s' not found", taskId)); } }
И мы можем бросить нашу пользовательскую проблему следующим образом:
if (MY_TASKS.containsKey(taskId)) { return MY_TASKS.get(taskId); } else { throw new TaskNotFoundProblem(taskId); }
В результате выбрасывания Задачи Не найдена Проблема проблема, мы получим:
{ "type": "https://example.org/not-found", "title": "Not found", "status": 404, "detail": "Task '3' not found" }
9. Работа со следами стека
Если мы хотим включить трассировки стека в ответ, нам необходимо соответствующим образом настроить наш модуль Problem :
ObjectMapper mapper = new ObjectMapper() .registerModule(new ProblemModule().withStackTraces());
Причинно-следственная цепочка причин отключена по умолчанию, но мы можем легко включить ее, переопределив поведение:
@ControllerAdvice class ExceptionHandling implements ProblemHandling { @Override public boolean isCausalChainsEnabled() { return true; } }
После включения обеих функций мы получим ответ, похожий на этот:
{ "title": "Internal Server Error", "status": 500, "detail": "Illegal State", "stacktrace": [ "org.example.ExampleRestController .newIllegalState(ExampleRestController.java:96)", "org.example.ExampleRestController .nestedThrowable(ExampleRestController.java:91)" ], "cause": { "title": "Internal Server Error", "status": 500, "detail": "Illegal Argument", "stacktrace": [ "org.example.ExampleRestController .newIllegalArgument(ExampleRestController.java:100)", "org.example.ExampleRestController .nestedThrowable(ExampleRestController.java:88)" ], "cause": { // .... } } }
10. Заключение
В этой статье мы рассмотрели, как использовать веб-библиотеку Problem Spring для создания ответов с подробными сведениями об ошибках с помощью application/problem+json response. Мы также узнали, как настроить библиотеку в нашем приложении Spring Boot и создать пользовательскую реализацию объекта Problem .
Реализацию этого руководства можно найти в проекте GitHub – это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.