In traditional procedural code, an object creates its own collaborators directly:
class OrderService {
private PaymentGateway gateway = new StripeGateway(); // OrderService controls its own dependency
}
Inversion of Control flips this around. The object just declares what it needs. Something external supplies it. Control over creating and wiring dependencies moves out of the object and into a container:
class OrderService {
private final PaymentGateway gateway;
OrderService(PaymentGateway gateway) { this.gateway = gateway; } // supplied from outside
}
Spring's IoC container (usually accessed via ApplicationContext) is what performs this inversion at runtime:
- It scans for bean definitions — via component scanning (
@Component,@Service, ...),@Beanmethods in@Configurationclasses, or (historically) XML. - It instantiates the beans. It resolves each one's declared dependencies (constructor parameters,
@Autowiredfields/setters) by looking them up among other managed beans. - It wires everything together and manages each bean's full lifecycle (initialization callbacks, scope, destruction).
@Service
class OrderService {
private final PaymentGateway gateway;
@Autowired // Spring resolves and injects this automatically
OrderService(PaymentGateway gateway) { this.gateway = gateway; }
}
The practical benefit: OrderService never mentions a concrete class like StripeGateway, only the PaymentGateway abstraction. The container decides which implementation to plug in. That's what makes it easy to swap implementations — for tests, different environments, or different providers — without touching OrderService itself.
Related Resources
BeanFactory is the most basic Spring IoC container interface. It can look up beans, resolve their dependencies, and manage bean scopes — that's essentially it. Its most distinguishing default behavior: it lazily instantiates singleton beans only when they're first requested via getBean().
ApplicationContext extends BeanFactory and adds a set of enterprise-oriented features on top:
- Eager singleton initialization by default — singleton beans are created at container startup, not on first use. This surfaces configuration errors immediately instead of at some arbitrary later point.
- Event publication —
ApplicationEventPublisher/@EventListenersupport for in-process, decoupled communication between beans. MessageSource— internationalization/localized message resolution.Environmentabstraction — unified access to properties from multiple sources (application.properties, environment variables, JVM system properties, profiles).- Easier AOP integration and automatic
BeanPostProcessor/BeanFactoryPostProcessorregistration.
ApplicationContext ctx = SpringApplication.run(MyApp.class, args);
MyService service = ctx.getBean(MyService.class);
In practice: almost every real Spring (and Spring Boot) application interacts with an ApplicationContext — specifically, Spring Boot uses an auto-configured AnnotationConfigServletWebServerApplicationContext or similar. BeanFactory mostly matters as the conceptual base interface, and in memory-constrained scenarios where its lighter weight and lazy-by-default behavior matter.
Spring supports three injection styles:
1. Constructor injection (recommended default):
@Service
class OrderService {
private final PaymentGateway gateway;
private final OrderRepository repository;
OrderService(PaymentGateway gateway, OrderRepository repository) { // @Autowired optional
this.gateway = gateway;
this.repository = repository;
}
}
2. Setter injection:
@Service
class OrderService {
private PaymentGateway gateway;
@Autowired
void setGateway(PaymentGateway gateway) { this.gateway = gateway; }
}
3. Field injection:
@Service
class OrderService {
@Autowired
private PaymentGateway gateway; // injected via reflection, bypassing the constructor entirely
}
Why constructor injection wins:
- Immutability: dependencies can be declared
final, guaranteeing they're set once and never reassigned. - Impossible to construct in an invalid state: you cannot create an
OrderServicewithout its required dependencies. The compiler enforces it. Field injection is different — a plainnew OrderService()compiles fine but produces an object withnulldependencies at runtime. - Fail-fast at startup: a missing or misconfigured dependency causes a clear
ApplicationContextstartup failure, instead of aNullPointerExceptiondeep in some unrelated code path later. - Trivial to unit test: you can construct the object directly with mocks in a plain JUnit test (
new OrderService(mockGateway, mockRepo)). No Spring container and no reflection-based injection needed. Field injection requires either a running context or reflection tricks (ReflectionTestUtils) to set the field for a test. - Surfaces excessive dependencies: a constructor with 8 parameters is an obvious, visible code smell suggesting the class does too much. Field injection hides that same problem.
Since Spring 4.3, @Autowired is even optional on a constructor if the class has exactly one constructor — Spring infers it automatically. That's part of why constructor injection has become the idiomatic default in modern Spring code.
All four are stereotype annotations. @Service, @Repository, and @Controller are each themselves annotated with @Component — so component scanning treats all four identically for the basic purpose of "register this class as a Spring bean":
@Component // generic — "this is a Spring-managed bean"
class DataFormatter { }
@Service // business/service-layer logic
class OrderService { }
@Repository // data-access layer
class OrderRepository { }
@Controller // web layer, returns view names (or @RestController for JSON/text bodies directly)
class OrderController { }
Why bother with more specific ones, if they're functionally interchangeable for bean registration?
- Readability/intent — seeing
@Repositoryon a class immediately communicates its architectural role, which plain@Componentdoesn't. @Repositoryhas a genuine behavioral difference: Spring registers aPersistenceExceptionTranslationPostProcessorfor beans annotated@Repository. It automatically translates low-level, technology-specific persistence exceptions (a JPAPersistenceException, a JDBCSQLException) into Spring's unified, uncheckedDataAccessExceptionhierarchy. Service-layer code can then catch one consistent, technology-agnostic exception type, regardless of which persistence technology the repository actually uses underneath.@Controller/@RestControlleradditionally participate in Spring MVC's request-handling machinery, being detected specifically as web-layer components that can hold@RequestMapping-annotated handler methods.
Rule of thumb: always prefer the most specific stereotype available for the layer a class belongs to. It costs nothing, documents architecture more clearly, and (for @Repository) unlocks a genuinely useful behavior.
Both register a Spring-managed bean, but via different mechanisms suited to different situations:
@Component (and its stereotype specializations) goes directly on a class, and gets picked up automatically by component scanning:
@Component
class EmailNotifier implements Notifier {
// Spring instantiates this via component scanning, no explicit wiring code needed
}
This only works for classes you own and can annotate. You can't add @Component to a class from a third-party JAR.
@Bean goes on a method inside an @Configuration class. The method's return value becomes the managed bean, and you write the actual construction logic yourself:
@Configuration
class AppConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.setConnectTimeout(Duration.ofSeconds(5)).build(); // full control over construction
}
}
When @Bean is necessary (not just a style choice):
- Wiring up a third-party class you can't put
@Componenton (RestTemplate, an external SDK client). - When bean creation needs custom logic or conditional configuration beyond what a constructor/field injection can express.
- When you need multiple beans of the same type with different configurations — for example, two differently-configured
RestTemplatebeans, disambiguated by method name or an explicit bean name.
Rule of thumb: use @Component (or a stereotype) for your own application classes. Use @Bean inside @Configuration classes for anything you don't own, or that needs custom construction logic.