The labs on this site need JavaScript to run. The same content is written out below.
Fundamentals › Spring
What does dependency injection do for you?
Dependency injection means that a class does not create the objects it needs to do its work, but receives them from outside. Here OrderService needs a PayService object to make a payment, and this is called "OrderService depends on PayService". In Spring, all you do is write PayService as a parameter of the OrderService constructor, and the @Component above a class is a mark that tells Spring to create an object (a bean) of that class and keep it. When Spring creates the OrderService object, it puts the PayService bean it was keeping into that constructor. In this lab you change the constructor to new PayService() or remove it, remove @Component above PayService, and run the same main, to see where the PayService that OrderService uses came from and how many PayService objects are created.
Checked against real execution · Compiled 6 combinations with real component scanning on Spring Framework 7.0.9 and ran them, measured even the objects in the fields by reflection, and checked against the results
@Component
public class PayService {
public String pay() {
return "Payment complete";
}
}
@Component
public class OrderService {
private final PayService payService;
// Injection: if you write the object you need as a constructor parameter, Spring puts it in
public OrderService(PayService payService) {
this.payService = payService;
}
public String order() {
return payService.pay();
}
}
public class Main {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext("app");
OrderService orderService = context.getBean(OrderService.class);
System.out.println(orderService.order());
}
}
These are the results of running Spring Framework 7.0.9 on JDK 21.0.12.1. Spring Boot was not used, and only the component scanning of AnnotationConfigApplicationContext("app"), which the main on the screen uses as it is, was used. All 6 combinations were checked by compiling a source with the same code as on the screen (PayService, OrderService, and Main in the package app) with javac (-parameters) and running it as a child Java process. Components were not registered by hand, and Spring was made to scan the classes and find them. Only in the source used for checking, a Probe class and four lines were added. One line each was added to the two classes, an instance initializer block that reads from the stack which side called its constructor (Spring, or which constructor of OrderService) when an object is created and records it, and to main, one line each that measures after the context is created and after getBean; the checking script removes these lines when it compares with the code on the screen. What was checked is the result of the run (the output, the kind and cause of the exception when startup failed, and the exception that stopped it and where), the PayService that OrderService uses, and the number of PayService objects created. "The PayService that OrderService uses" was measured, not guessed. During the run, the payService field of the OrderService object was read by reflection, and it was compared with the PayService bean in the context to see whether it is the same object (==) and whether it is empty. The numbers of objects were also counted by the number of times the constructors were called. The facts that the step sentences rely on were also measured separately: the classes that component scanning found; the order in which the objects were created (with constructor injection PayService first, and with direct creation PayService is created while OrderService is being created); that all objects are created at the line that creates the context and no new object is created at the getBean line; the dependencies of OrderService that Spring recorded (payService only when it is received in the constructor); that when there is no PayService bean, OrderService is not created and main stops at its first line; that the place where the program stops using an empty field is the order() method of OrderService; and that our code has no line new OrderService(...). The actual exception in the combination where startup failed is UnsatisfiedDependencyException with the cause NoSuchBeanDefinitionException (no bean of type PayService), and the original text was kept in the Probe output. What the documentation specifies is the following. The Dependency Injection section of the Core part of the Spring Framework documentation (dependency injection is a process in which objects define the other objects they depend on only through constructor arguments and so on, and the container injects them when it creates the bean; and constructor-based injection is the container calling a constructor with arguments that each represent a dependency), the Using @Autowired section (if the target bean has only one constructor, @Autowired does not have to be put on that constructor), the Classpath Scanning and Managed Components section, and the API documentation of @Component and AnnotationConfigApplicationContext (a class with @Component is a candidate for auto-detection, and the specified package is scanned and bean definitions are registered). The following behaviors seen in the same run are not defined by the documentation but were observed on this version. In the order in which the class files are scanned, OrderService came before PayService, and because of that, with constructor injection, PayService was created first while OrderService was being created; with direct creation by new, Spring creates OrderService first and the PayService bean after it (this depends on the scan order, so it could change if the class names change); and Spring does not report an empty field as an error. The code on the screen leaves out the package and import lines. This lab covers only constructor injection. Setter injection, field injection (@Autowired), the case where there are several beans of the same type, circular dependencies, the scope and lifecycle of beans, and designs that receive an interface are not covered, and they are covered in the in-depth lab "Bean creation and injection".
Previous lab: Beans · Next lab: AOP and proxies
Struck-through text was removed from the first version of the code; highlighted text was added.