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 Map MY_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, поэтому его должно быть легко импортировать и запускать как есть.