Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Обработчики исключений

Проверять «есть ли запись» в каждом методе ресурса утомительно, а ответ 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/99
HTTP/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.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.