Dependency Injection with Spring 5

As we learned in the previous chapter dependency injection is a very powerful technique. DI is provided by the Spring 5 Framework implementation of the Inversion of Control (IoC) principle. The actual beauty of Dependency comes when we code to an interface not to an implementation (concrete classes). This enables extension and testability.
Let’s create a simple application using the Spring 5 Framework where we have a class Computer which has a private field CPU showing the CPU this computer is using. Let’s imagine we have 2 different types of CPUs – Intel Core i9 – 9900K and Intel Pentium Gold G550 and our computer can have either of them. Each of these classes is going to have two private fields: cores (the number of cores the CPU has) and cache (the cache memory the CPU has). Each of our CPU classes will also have a public method getInfo() returning information about the specific CPU. Because each of the CPU models doesn’t change over time (the manufacturer has created them with concrete number of cores and cache), each instance of these classes will have identical values to its fields. Let’s first create each of our classes as follows:
public class IntelCorei9_9900K {

    private int cores;
    private int cache;

}
public class IntelPentiumGold_G550 {

    private int cores;
    private int cache;

}
As we can see, for now they are both identical. Both of them will have a method getInfo(), so now we will create an interface called ICPU containing the method getInfo() and both of our classes will implement this interface (code to an interface).
public interface ICPU {

    String getInfo();

}
The next step is to implement this interface in our CPU classes.
public class IntelCorei9_9900K implements ICPU {

    private int cores;
    private int cache;

    @Override
    public String getInfo() {
        return "Model: Intel Core i9-9900K" + ", cores: " + this.cores + ", cache: " + this.cache + "MB";
    }
}
public class IntelPentiumGold_G550 implements ICPU {

    private int cores;
    private int cache;

    @Override
    public String getInfo() {
        return "Model: Intel Pentium Gold G550" + ", cores: " + this.cores + ", cache: " + this.cache + "MB";
    }
}
We now have to set different values for our fields. We do that using the @Value annotation for each of our fields.
public class IntelCorei9_9900K implements ICPU {

    @Value("8")
    private int cores;

    @Value("16")
    private int cache;

    @Override
    public String getInfo() {
        return "Model: Intel Core i9-9900K" + ", cores: " + this.cores + ", cache: " + this.cache + "MB";
    }
}
public class IntelPentiumGold_G550 implements ICPU {

    @Value("2")
    private int cores;

    @Value("2")
    private int cache;

    @Override
    public String getInfo() {
        return "Model: Intel Pentium Gold G550" + ", cores: " + this.cores + ", cache: " + this.cache + "MB";
    }
}
The next step is to annotate each of our classes with the @Component annotation telling Spring that this class should be used as a Bean.
@Component
public class IntelCorei9_9900K implements ICPU {
@Component
public class IntelPentiumGold_G550 implements ICPU {
Now we create our Computer class which looks as follows:
@Component
public class Computer {

    private ICPU cpu;

    @Autowired //optional
    public Computer(ICPU cpu) {
        this.cpu = cpu;
    }
        
    public String getPCInfo() {
        return cpu.getInfo();
    }
}
Here we declare our CPU field as an interface. This way the computer class doesn’t have to worry for the concrete implementation – it can be either of our CPU classes. We use a constructor-based dependency injection. The @Autowired annotation for a constructor injection is optional (it can be omitted).
Another way of injecting our dependency is with a setter as shown bellow.
@Autowired
public void setCPU(ICPU cpu) {
    this.cpu = cpu;
}
The field – based dependency injection is considered as bad practice so it won’t be viewed in this tutorial.
@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {

		ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
		Computer computer = (Computer) context.getBean("computer");
		System.out.println(computer.getPCInfo());
	}
In our DemoApplication class we implement out main method as shown. We use the context variable to tell the framework to provide us a fully configured bean of class Computer. Then we print it’s info (the CPU info for this computer). Now it’s time to run our main class.
However we get the following error message:
Parameter 0 of constructor in com.example.demo.model.Computer required a single bean, but 2 were found:
	- intelCorei9_9900K: defined in file [E:\intelliJ\Examples for Knowledge Base\PC-DI\demo\target\classes\com\example\demo\model\IntelCorei9_9900K.class]
	- intelPentiumGold_G550: defined in file [E:\intelliJ\Examples for Knowledge Base\PC-DI\demo\target\classes\com\example\demo\model\IntelPentiumGold_G550.class]
It is caused because the autowiring in the Computer constructor is ambiguous. The framework doesn’t know which of the CPU classes implementing our ICPU interface to use. One way to specify the exact class that we want to use is to add the @Qualifier annotation to our classes and to the Computer constructor (or setter).
@Component
@Qualifier("i9")
public class IntelCorei9_9900K implements ICPU {
@Component
@Qualifier("pentium")
public class IntelPentiumGold_G550 implements ICPU {
Then in the Computer constructor we specify which one of the implementations we want to inject. In the example we choose that we want the Pentium implementation.
@Autowired //optional
public Computer(@Qualifier("pentium") ICPU cpu) {
    this.cpu = cpu;
}
In case we used the setter-based dependency injection we complete it adding the @Qualifier annotation as shown.
@Autowired
@Qualifier("pentium")
public void setCPU(ICPU cpu) {
    this.cpu = cpu;
}
If we run the main method again we get the following result.
2020-07-11 22:23:09.082  INFO 10516 --- [  restartedMain] com.example.demo.DemoApplication         : Starting DemoApplication on DESKTOP-9VUF3IS with PID 10516 (started by Dany in E:\intelliJ\Examples for Knowledge Base\PC-DI\demo)
2020-07-11 22:23:09.082  INFO 10516 --- [  restartedMain] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
2020-07-11 22:23:09.172  INFO 10516 --- [  restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2020-07-11 22:23:10.592  INFO 10516 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2020-07-11 22:23:10.672  INFO 10516 --- [  restartedMain] com.example.demo.DemoApplication         : Started DemoApplication in 2.12 seconds (JVM running for 3.174)
Model: Intel Pentium Gold G550, cores: 2, cache: 2MB

Process finished with exit code 0
And that’s it. We successfully used the Spring 5 Framework to manage our dependency injection.