The labs on this site need JavaScript to run. The same content is written out below.
Learn › Spring › IoC container and DI
Bean creation and injection
In what order does Spring create objects and wire them together?
When an application starts, the Spring container creates the beans one by one and wires them together. The order in which the objects are created depends on the injection style, and when two beans need each other (a circular dependency), startup can fail.
Checked against real execution · All 12 combinations were started on Spring Boot 4.1.1 and checked against the results
Concepts in this lab
- Inversion of Control (IoC)
- This is a design principle in which a framework (the container), not the developer's code, takes charge of creating objects and connecting them to each other. In an ordinary program, your code creates and calls the objects it needs, but here the container creates the objects, puts them where they are needed, and calls your code at the right time. The name comes from the fact that the party in control is reversed.
- Dependency injection (DI)
- This is an approach in which an object does not create the other objects it uses (its dependencies) but receives them from outside. In Spring, the container supplies the dependencies through a constructor, a setter, or a field as it creates a bean. It becomes easier to change the implementing class or to put in a fake object in a test, and the coupling between classes becomes lower.
- Constructor injection, setter injection, and field injection
- These are three ways to supply a dependency. Constructor injection receives the dependencies when the object is created, so the fields can be final and an object is never created with a dependency missing; Spring recommends it. Setter injection is used for optional dependencies. Field injection puts @Autowired on a field; the code is short, but it is hard to create the object without Spring in order to test it.
- Spring bean
- This is an object that the Spring IoC container creates and manages. Unlike an object that a developer creates directly with new, the container injects its dependencies and manages its initialization and destruction, and additional features such as transactions and AOP can be applied to it through a proxy.
- Bean lifecycle
- This is the process from the creation of a bean to its end. It proceeds in this order: object creation, dependency injection, the initialization callback, use, and the destruction callback when the container shuts down. Put initialization work that must run after all dependencies are injected in @PostConstruct, and put cleanup work before shutdown in @PreDestroy.
- Circular dependency
- This is a state in which bean A needs B and B in turn needs A. With constructor injection, neither can be created first, so the application fails to start. Since Spring Boot 2.6, it is prohibited by default regardless of the injection style. The proper fix is to change the design, for example by moving the responsibility that the two beans share into a separate bean.
Try these
- Default settings — Constructor injection. A dependency must be fully initialized before the next object can be created.
- Switching to field injection — The object is created first and filled in later. For a short time, its fields are empty.
- Constructor injection + circular dependency — Neither bean can be created first, so startup fails.
- Field injection + circular dependency — With the default settings of Spring Boot, this also fails at startup.
- Allowing circular dependencies — Field injection gets through by handing over an object early, before it is fully initialized.
- Constructor injection fails even when allowed — This is because the arguments must exist before a constructor can be called.
Running with the default settings
- Component scanning: The container finds the classes annotated with @RestController, @Service, and @Repository and registers 3 bean definitions. No object has been created yet.
- OrderController starts to be created. The constructor requires OrderService, so the container must prepare that first before it can call the constructor.
- OrderService starts to be created. The constructor requires OrderRepository, so the container must prepare that first before it can call the constructor.
- OrderRepository starts to be created. The constructor takes no arguments.
- OrderRepository is created as an object by calling its constructor. All of its dependencies are in place from the moment it is created. Next, the initialization callback (@PostConstruct) is called if there is one, and the bean is fully initialized.
- OrderService is created as an object by calling its constructor. Passed as arguments: OrderRepository. All of its dependencies are in place from the moment it is created. Next, the initialization callback (@PostConstruct) is called if there is one, and the bean is fully initialized.
- OrderController is created as an object by calling its constructor. Passed as arguments: OrderService. All of its dependencies are in place from the moment it is created. Next, the initialization callback (@PostConstruct) is called if there is one, and the bean is fully initialized.
Result of this run
- With constructor injection, an object can be created only after its dependencies are fully initialized. So from the moment the object exists, all of its dependencies are in place, and the fields can be final.
Code
@RestController
class OrderController {
private final OrderService orderService;
OrderController(OrderService orderService) {
this.orderService = orderService;
}
}
@Service
class OrderService {
private final OrderRepository orderRepository;
OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
}
@Repository
class OrderRepository {
}
Assumptions of this model
This is a small application with only four beans, and the model assumes that the container starts creating the beans in the order in which they were scanned (the real order can vary with the classpath and the configuration, but the rule that a dependency must be ready first is the same). Prohibiting circular dependencies by default is the behavior of Spring Boot 2.6 and later. Another way to resolve a circular dependency (delaying the injection with @Lazy) is not covered. All 12 combinations were started on a real Spring Boot 4.1.1, and it was confirmed that whether startup succeeds, the order in which the objects are created (the order in which the constructors are called), and the order in which the beans are fully initialized (the order in which injection finishes) match the screen. Every startup failure was a BeanCurrentlyInCreationException.
Previous lab: The Spring container and beans · Next lab: Singleton beans and state