Обработчики исключений
Проверять «есть ли запись» в каждом методе ресурса утомительно, а ответ 404 без тела мало что объясняет клиенту. Удобнее бросать своё исключение в глубине кода, а превращать его в HTTP-ответ в одном месте. Для этого в Jakarta REST есть ExceptionMapper — класс, который получает исключение и возвращает Response.
Своё исключение
Создайте файл src/main/java/com/example/blog/PostNotFoundException.java:
// src/main/java/com/example/blog/PostNotFoundException.java
package com.example.blog;
public class PostNotFoundException extends RuntimeException {
public PostNotFoundException(long id) {
super("Запись " + id + " не найдена");
}
}И запись для тела ответа с ошибкой:
// src/main/java/com/example/blog/ErrorMessage.java
package com.example.blog;
public record ErrorMessage(int status, String error) {
}Обработчик
// src/main/java/com/example/blog/PostNotFoundMapper.java
package com.example.blog;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
@Provider
public class PostNotFoundMapper implements ExceptionMapper<PostNotFoundException> {
@Override
public Response toResponse(PostNotFoundException e) {
return Response.status(Response.Status.NOT_FOUND)
.type(MediaType.APPLICATION_JSON)
.entity(new ErrorMessage(404, e.getMessage()))
.build();
}
}Аннотация @Provider регистрирует класс в Jakarta REST. Тип в угловых скобках ExceptionMapper<PostNotFoundException> говорит, какие исключения он обрабатывает. Когда метод ресурса выбросит такое исключение, сервер вызовет toResponse и отправит его результат.
Хранилище бросает исключение
Добавьте в PostStore метод get и измените delete:
// src/main/java/com/example/blog/PostStore.java (фрагмент)
public Post get(long id) {
return findById(id).orElseThrow(() -> new PostNotFoundException(id));
}
public void delete(long id) {
if (posts.remove(id) == null) {
throw new PostNotFoundException(id);
}
}Методы ресурса становятся короткими — проверок в них больше нет:
// src/main/java/com/example/blog/PostResource.java (фрагмент)
@GET
@Path("/{id}")
public Post show(@PathParam("id") long id) {
return store.get(id);
}
@DELETE
@Path("/{id}")
public void delete(@PathParam("id") long id) {
store.delete(id);
}Проверка
curl -i http://localhost:8080/blog/api/posts/99HTTP/1.1 404 Not Found
Content-Type: application/json
{"error":"Запись 99 не найдена","status":404}
Удаление несуществующей записи (curl -i -X DELETE http://localhost:8080/blog/api/posts/42) вернёт такой же ответ с номером 42.
Какой обработчик выберется
| Ситуация | Что произойдёт |
|---|---|
| Есть обработчик для этого класса исключения | Вызывается он |
Есть только обработчик для родителя, например RuntimeException | Вызывается обработчик родителя |
| Обработчиков нет | Ответ 500, исключение пишется в лог |
Обработчик для Exception ловит вообще всё — иногда его делают, чтобы не показывать клиенту внутренние сообщения. Но тогда он перехватит и NotFoundException из прошлого урока, так что внутри придётся отдельно обрабатывать WebApplicationException.
Частая ошибка
Забыть аннотацию @Provider. Класс компилируется, но сервер о нём не знает, и исключение уходит клиенту как ошибка 500 с текстом сообщения:
HTTP/1.1 500 Internal Server Error
Запись 99 не найдена
В логе сервера при этом появляется RESTEASY002375: Error processing request GET /blog/api/posts/99 и стек вызовов PostNotFoundException.