Spring Introduction
What the core Spring Framework is, the problem of manually wiring dependencies, and how the IoC container solves it.
The problem: wiring dependencies by hand
Almost every non-trivial class depends on other classes to do its job. A OrderService needs an OrderRepository; that repository needs a database connection. Without a framework, something has to construct all of these objects in the right order and hand them to each other:
public class OrderService {
private final OrderRepository repository;
public OrderService() {
// OrderService is now responsible for knowing HOW to build a repository
this.repository = new JdbcOrderRepository(new DataSource("jdbc:mysql://localhost/orders"));
}
}
This looks harmless with one dependency. In a real application it doesn't stay that way:
OrderServiceis now tightly coupled toJdbcOrderRepository— swapping in an in-memory fake for a test, or a different implementation in production, means editingOrderServiceitself.- Every class that needs an
OrderRepositoryhas to repeat the same construction logic, including the connection details. - Object lifecycles (should this be one shared instance, or a new one each time?) are scattered across the codebase instead of being decided in one place.
What Spring is
The Spring Framework is a container that takes over the job of constructing objects ("beans") and wiring them together, based on configuration rather than code scattered through your classes. Two ideas make this possible:
- Inversion of Control (IoC) — instead of a class controlling how its dependencies get created (
new JdbcOrderRepository(...)), control is inverted: an external container creates the dependency and hands it to the class. The class just declares what it needs. - Dependency Injection (DI) — the specific technique IoC uses in Spring: dependencies are "injected" into a class, typically through its constructor, rather than the class instantiating them itself.
Rewriting the example above the Spring way:
@Service
public class OrderService {
private final OrderRepository repository;
// Spring supplies the repository — OrderService never calls `new` on it
public OrderService(OrderRepository repository) {
this.repository = repository;
}
}
@Repository
public class JdbcOrderRepository implements OrderRepository {
private final DataSource dataSource;
public JdbcOrderRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
}
Spring's IoC container (an ApplicationContext) scans for classes annotated as beans, works out the dependency graph between them, and constructs everything in the right order at startup. OrderService no longer knows or cares whether it's talking to a JDBC repository, an in-memory one, or a mock — it only depends on the OrderRepository interface, and the container decides which concrete implementation to hand it.
Why this matters in practice
- Testability — in a unit test, you construct
new OrderService(fakeRepository)directly, with a hand-rolled test double, no container required. - Swappable implementations — switch
JdbcOrderRepositoryforMongoOrderRepositoryin one place without touching every class that usesOrderRepository. - Centralized lifecycle management — the container decides whether a bean is a single shared instance or a fresh one per request, instead of that decision being buried inside constructors all over the codebase.
- Less boilerplate at scale — as an application grows to dozens or hundreds of classes, manual wiring becomes its own maintenance burden. The container does that bookkeeping.
Where Spring Boot fits in
The core Spring Framework — the IoC container, DI, and the surrounding ecosystem (Spring MVC, Spring Data, Spring Security, and so on) — is what this whole "Spring" family of tutorials is about. Spring Boot (covered in its own track) is built on top of the core Spring Framework — it doesn't replace any of these concepts, it just removes the manual setup (XML/Java config, embedded servers, dependency versions) needed to get a Spring application running quickly. Everything you learn about the container and DI here applies directly inside a Spring Boot application.
Common mistakes
- Thinking Spring Boot and Spring are two unrelated things — Spring Boot is the Spring Framework, with auto-configuration and opinionated defaults layered on top.
- Assuming the container is "magic" — it's just building a dependency graph from your classes and calling constructors in the right order, which you could (tediously) do by hand.
- Manually calling
newon a class annotated@Service/@Componentand then wondering why its own injected dependencies arenull— objects built withnewbypass the container entirely, so nothing gets injected into them.