Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Dependency injection and beans

Right now the articles are stored in the controller itself. But a controller should only accept requests; working with data belongs in a separate class, a service. How does the controller get the service? Not through new: the framework hands it over. This is called dependency injection (DI). The objects Micronaut creates and hands out are called beans.

The article service

Create a service class and mark it with @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 from the Jakarta Inject standard means: this is a bean, and there is one of it per application. The same class can be generated with mn create-bean com.example.ArticleService.

Constructor injection

The controller simply declares the service as a constructor parameter:

// 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()));
    }
}

When creating the controller, Micronaut sees a parameter of type ArticleService, finds that bean and passes it in. The API works as before: POST /articles creates an article, GET /articles shows it. But the data now lives in the service, and other classes can use it too. Constructor injection is the recommended style: the field can be final, and in tests you can easily pass your own object.

Scopes

AnnotationHow many instances
@SingletonOne per application, created on first use
@ContextOne, created right at startup
@PrototypeA new one for every injection
@RequestScopeA new one for every HTTP request

Controllers are singletons by default too.

A bean from a third-party class

You cannot annotate a class from a library or the JDK. For such a class you write a factory: a class marked @Factory whose methods create beans:

// 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();
    }
}

Now java.time.Clock can be received in any constructor, for example in TimeController(Clock clock). In tests such a bean is easy to replace with a clock frozen at a fixed time.

Common mistake

If you forget @Singleton on the service, the project builds, but the first request to the controller returns 500. The log shows:

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)

The Path Taken line shows where the chain broke. Mark the class with a scope annotation such as @Singleton.

Thanks for reading!

Donate to the site — we work hard for you.