Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Dependency injection

Application classes rely on each other: a controller uses a service, a service uses a data store. These links are called dependencies. With dependency injection a class does not create them itself but receives them ready-made from outside — in Spring the container passes them in.

A post store

We have no database yet, so posts will live in memory. First a record for a post:

// src/main/java/com/example/blog/Post.java
package com.example.blog;
 
import java.time.Instant;
 
public record Post(Long id, String title, String body, String author, Instant createdAt) {
}

Now the store. The @Repository annotation makes it a bean:

// src/main/java/com/example/blog/PostRepository.java
package com.example.blog;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
 
import org.springframework.stereotype.Repository;
 
@Repository
public class PostRepository {
 
    private final Map<Long, Post> posts = new ConcurrentHashMap<>();
    private final AtomicLong nextId = new AtomicLong(1);
 
    public List<Post> findAll() {
        return new ArrayList<>(posts.values());
    }
 
    public Optional<Post> findById(Long id) {
        return Optional.ofNullable(posts.get(id));
    }
 
    public Post save(Post post) {
        Long id = post.id() != null ? post.id() : nextId.getAndIncrement();
        Post saved = new Post(id, post.title(), post.body(), post.author(), post.createdAt());
        posts.put(id, saved);
        return saved;
    }
 
    public void deleteById(Long id) {
        posts.remove(id);
    }
}

ConcurrentHashMap and AtomicLong are safe when requests arrive at the same time from different threads. The data lives while the program runs; in chapter four a database takes its place.

Constructor injection

// src/main/java/com/example/blog/PostService.java
package com.example.blog;
 
import java.time.Clock;
import java.time.Instant;
import java.util.List;
 
import org.springframework.stereotype.Service;
 
@Service
public class PostService {
 
    private final PostRepository repository;
    private final Clock clock;
 
    public PostService(PostRepository repository, Clock clock) {
        this.repository = repository;
        this.clock = clock;
    }
 
    public List<Post> findAll() {
        return repository.findAll();
    }
 
    public Post create(String title, String body, String author) {
        return repository.save(new Post(null, title, body, author, Instant.now(clock)));
    }
}

When creating PostService, Spring looks at the constructor parameters, finds the PostRepository and Clock beans (the latter from AppConfig in the previous lesson) and passes them in. With a single constructor no @Autowired annotation is needed. This service replaces the practice PostService from the previous lesson, and TitleController can be deleted.

Why the constructor

  • Fields can be final — nobody can swap a dependency after creation.
  • You see right away what the class needs to work.
  • In a test the object is easy to create by hand: new PostService(repository, clock).

You will also see field injection — @Autowired private PostRepository repository;. It is shorter, but it hides dependencies and gets in the way of tests. Write a constructor.

When several beans match

When two beans fit, Spring does not know which one to choose. Mark the main one with @Primary:

// src/main/java/com/example/blog/AppConfig.java
@Bean
@Primary
public Clock utcClock() {
    return Clock.systemUTC();
}
 
@Bean
public Clock moscowClock() {
    return Clock.system(ZoneId.of("Europe/Moscow"));
}

Where you need the second one specifically, name it on the parameter: @Qualifier("moscowClock") Clock clock.

Common mistake

Without @Primary or @Qualifier, two Clock beans stop the startup:

Parameter 1 of constructor in com.example.blog.PostService required a single bean, but 2 were found:
	- utcClock: defined by method 'utcClock' in class path resource [com/example/blog/AppConfig.class]
	- moscowClock: defined by method 'moscowClock' in class path resource [com/example/blog/AppConfig.class]

Spring lists the candidates and suggests adding @Primary or @Qualifier.

Thanks for reading!

Donate to the site — we work hard for you.