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
@Beanmethod in a@Configurationclass, 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
@Beanmethods 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.