Encodera

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

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

Оглавление

Внедрение зависимостей и бины

Сейчас статьи хранятся прямо в контроллере. Но контроллер должен только принимать запросы, а работу с данными лучше вынести в отдельный класс — сервис. Как контроллер получит сервис? Не через new: его передаст фреймворк. Это и называется внедрением зависимостей (dependency injection, DI). Объекты, которые создаёт и раздаёт Micronaut, называются бинами (beans).

Сервис статей

Создайте класс сервиса и отметьте его @Singleton:

// src/main/java/com/example/ArticleService.java
package com.example;
 
import jakarta.inject.Singleton;
 
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
 
@Singleton
public class ArticleService {
 
    private final Map<Long, Article> articles = new ConcurrentHashMap<>();
    private final AtomicLong nextId = new AtomicLong(1);
 
    public Collection<Article> findAll() {
        return articles.values();
    }
 
    public Optional<Article> findById(Long id) {
        return Optional.ofNullable(articles.get(id));
    }
 
    public Article create(ArticleForm form) {
        Article article = new Article(nextId.getAndIncrement(), form.title(), form.body());
        articles.put(article.id(), article);
        return article;
    }
}

@Singleton из стандарта Jakarta Inject означает: это бин, и на всё приложение он один. То же можно создать командой mn create-bean com.example.ArticleService.

Внедрение через конструктор

Контроллер просто объявляет сервис параметром конструктора:

// src/main/java/com/example/ArticleController.java
@Controller("/articles")
public class ArticleController {
 
    private final ArticleService articleService;
 
    public ArticleController(ArticleService articleService) {
        this.articleService = articleService;
    }
 
    @Get
    public Collection<Article> list() {
        return articleService.findAll();
    }
 
    @Get("/{id}")
    public Optional<Article> show(Long id) {
        return articleService.findById(id);
    }
 
    @Post
    public HttpResponse<Article> create(@Body ArticleForm form) {
        Article article = articleService.create(form);
        return HttpResponse.created(article, URI.create("/articles/" + article.id()));
    }
}

Создавая контроллер, Micronaut видит параметр типа ArticleService, находит этот бин и передаёт его. API работает как раньше: POST /articles создаёт статью, GET /articles её показывает. Зато данные теперь живут в сервисе, и им смогут пользоваться другие классы. Внедрение через конструктор — рекомендуемый способ: поле можно сделать final, а в тестах легко передать свой объект.

Области видимости

АннотацияСколько экземпляров
@SingletonОдин на приложение, создаётся при первом обращении
@ContextОдин, создаётся сразу при запуске
@PrototypeНовый при каждом внедрении
@RequestScopeНовый на каждый HTTP-запрос

Контроллеры по умолчанию тоже синглтоны.

Бин из чужого класса

Класс из библиотеки или JDK аннотацией не отметишь. Для него пишут фабрику — класс с @Factory, методы которого создают бины:

// src/main/java/com/example/ClockFactory.java
package com.example;
 
import io.micronaut.context.annotation.Factory;
import jakarta.inject.Singleton;
 
import java.time.Clock;
 
@Factory
public class ClockFactory {
 
    @Singleton
    Clock clock() {
        return Clock.systemDefaultZone();
    }
}

Теперь java.time.Clock можно получить в любом конструкторе, например в TimeController(Clock clock). В тестах такой бин легко заменить часами с фиксированным временем.

Частая ошибка

Если забыть @Singleton на сервисе, проект соберётся, а первый запрос к контроллеру вернёт 500. В журнале будет:

ERROR i.m.http.server.RouteExecutor - Unexpected error occurred: Failed to inject value for parameter [articleService] of class: com.example.ArticleController
 
Message: No bean of type [com.example.ArticleService] exists.
Path Taken:
new c.e.ArticleController(ArticleService articleService)

Строка Path Taken показывает, где оборвалась цепочка. Отметьте класс аннотацией области видимости, например @Singleton.

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

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