Beans and dependency injection

Spring Boot · Essentials · 12 min

Dependency injection is the core of Spring; every other feature is built on beans.

Spring's application context (the IoC container) creates and manages objects called beans. Instead of calling new for your services, you declare them and let the container wire them together.

Two ways to declare beans:

  • Annotate a class with a stereotype: @Component, or the more descriptive @Service, @Repository, @Controller/@RestController. Component scanning registers it.
  • Write a @Bean method in a @Configuration class, for objects from libraries you can't annotate or that need custom construction.

Dependencies are injected through the constructor: declare a final field and a constructor parameter, and Spring passes the matching bean. With a single constructor no @Autowired is needed. Constructor injection makes dependencies explicit, allows immutability and makes classes easy to test with plain new. Field injection (@Autowired on a field) still works but hides dependencies and is discouraged.

Depend on interfaces where it helps: inject PaymentGateway, not StripeGateway. If several beans implement the interface, pick one with @Primary or @Qualifier("name"), or inject List<PaymentGateway> to get them all.

Beans are singletons by default: one shared instance per context, so they must be stateless or thread-safe.

Example

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import java.time.Clock;
interface Notifier { void send(String to, String msg); }
@Service
class EmailNotifier implements Notifier {
    public void send(String to, String msg) { System.out.println("email to " + to + ": " + msg); }
}
@Service
class SignupService {
    private final Notifier notifier;   // injected via the constructor
    private final Clock clock;
    SignupService(Notifier notifier, Clock clock) {
        this.notifier = notifier;
        this.clock = clock;
    }
    void signup(String email) { notifier.send(email, "Welcome! " + clock.instant()); }
}
@Configuration
class AppConfig {
    @Bean Clock clock() { return Clock.systemUTC(); }   // library type as a bean
}

Remember

  • The container creates beans and injects their dependencies
  • Use stereotypes for your classes and @Bean methods for library objects
  • Prefer constructor injection with final fields
  • Beans are singletons by default, so keep them stateless

Watch out for

  • Storing per-request data in a field of a singleton @Service, which is shared across all requests.

Check yourself

  1. A @Service has one constructor taking a Notifier. What do you need for Spring to inject it?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html