Контроллеры и apiBuilder
Когда все обработчики записаны лямбдами в main, метод быстро разрастается до сотни строк. Принято выносить обработчики в отдельный класс — контроллер. Каждый его метод принимает Context и подходит в качестве обработчика. Маршруты при этом остаются в Main, а для их группировки в Javalin есть apiBuilder — набор статических методов path, get, post, delete.
Класс-контроллер
Создайте файл контроллера постов. Хранилище он получает через конструктор:
// src/main/java/com/example/blog/PostController.java
package com.example.blog;
import io.javalin.http.Context;
import io.javalin.http.HttpStatus;
import io.javalin.http.NotFoundResponse;
public class PostController {
private final PostStore store;
public PostController(PostStore store) {
this.store = store;
}
public void getAll(Context ctx) {
ctx.json(store.findAll());
}
public void getOne(Context ctx) {
long id = ctx.pathParamAsClass("id", Long.class).get();
Post post = store.findById(id)
.orElseThrow(() -> new NotFoundResponse("Пост " + id + " не найден"));
ctx.json(post);
}
public void create(Context ctx) {
NewPost data = ctx.bodyValidator(NewPost.class)
.check(p -> p.title() != null && !p.title().isBlank(), "Нужен заголовок")
.check(p -> p.body() != null && p.body().length() >= 5, "Текст слишком короткий")
.get();
ctx.status(HttpStatus.CREATED).json(store.create(data));
}
public void delete(Context ctx) {
long id = ctx.pathParamAsClass("id", Long.class).get();
if (!store.delete(id)) {
throw new NotFoundResponse("Пост " + id + " не найден");
}
ctx.status(HttpStatus.NO_CONTENT);
}
}Код методов тот же, что был в лямбдах, только теперь у каждого есть имя.
Маршруты через apiBuilder
В Main.java создайте контроллер и замените маршруты постов блоком apiBuilder:
// src/main/java/com/example/blog/Main.java
package com.example.blog;
import io.javalin.Javalin;
import static io.javalin.apibuilder.ApiBuilder.delete;
import static io.javalin.apibuilder.ApiBuilder.get;
import static io.javalin.apibuilder.ApiBuilder.path;
import static io.javalin.apibuilder.ApiBuilder.post;
public class Main {
public static void main(String[] args) {
var posts = new PostController(new PostStore());
Javalin app = Javalin.create(config -> {
config.routes.apiBuilder(() -> {
path("/posts", () -> {
get(posts::getAll);
post(posts::create);
path("/{id}", () -> {
get(posts::getOne);
delete(posts::delete);
});
});
});
// обработчики before, after и exception из прошлых уроков остаются здесь
});
app.start(8080);
}
}path задаёт общую часть адреса, вложенные path её продолжают. Запись posts::getAll — ссылка на метод конкретного объекта posts. По структуре блока сразу видно все адреса API.
| Вызов внутри блока | Итоговый маршрут |
|---|---|
get(posts::getAll) в path("/posts") | GET /posts |
post(posts::create) в path("/posts") | POST /posts |
get(posts::getOne) в path("/{id}") | GET /posts/{id} |
delete(posts::delete) в path("/{id}") | DELETE /posts/{id} |
API работает как раньше:
curl -X POST http://localhost:8080/posts \
-d '{"title":"Контроллеры","body":"Код переехал в класс.","author":"Олег"}'{"id":3,"title":"Контроллеры","body":"Код переехал в класс.","author":"Олег"}
Частая ошибка
Статические методы get, post, path из ApiBuilder работают только внутри config.routes.apiBuilder(...). Если вызвать get("/", ...) прямо в блоке настроек, сервер падает при старте:
Exception in thread "main" java.lang.IllegalStateException: The static API can only be used within a routes() call.
Внутри Javalin.create пишите config.routes.get(...), а короткие get(...) — только внутри apiBuilder.