Card 06/ 09
Dependency Inversion in the Wild: Constructor Injection in Spring
Spring hands a controller its service; the service only names an interface.
Spring, the most widely used Java application framework, does the wiring that the walkthrough did by hand. This example shows the same reminder design written for Spring, where the framework creates each object and passes its dependencies into its constructor, and what the framework does not do for you.
Spring's container is the part that creates objects and connects them. Classes marked @Service or @RestController are created by it. When a class has a single constructor, Spring reads its parameter types and passes in a matching object it has created. The listing needs the Spring libraries, which the offline container used for this subject does not have, so it is shown without being run.
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
class ReminderController {
private final ReminderService reminders;
ReminderController(ReminderService reminders) {
this.reminders = reminders;
}
@PostMapping("/users/{id}/reminders")
String remind(@PathVariable int id) {
return reminders.remind(id);
}
}
@Service
class ReminderService {
private final UserSource users;
ReminderService(UserSource users) {
this.users = users;
}
String remind(int userId) {
return "MAIL to " + users.emailFor(userId) + ": reset your password";
}
}
interface UserSource {
String emailFor(int userId);
}
@Service
class MySQLUserSource implements UserSource {
public String emailFor(int userId) {
return "[email protected]";
}
}Nothing in the listing calls new. Spring sees that ReminderService needs a UserSource, finds MySQLUserSource as the only class implementing it, and passes it in. In a test, you can call new ReminderService(fakeUsers) yourself, exactly as a hand-written Python test does, without starting Spring at all.
What a framework and a hand-written test share is the shape: the high-level class names an interface and receives an implementation. What differs is who connects them: a test, by hand, or a framework, at start-up.
The framework does not invert anything by itself. If ReminderService's constructor took a MySQLUserSource instead of a UserSource, Spring would inject it just as happily, and the service would still depend on MySQL. The container provides injection; whether the dependency is inverted depends on the type you write in the constructor.
← → move through sections and cards