Using interfaces we can make our applications loosely coupled.
Whenever we create a spring-boot-starter project, some necessary spring specific maven dependencies like spring core, spring context, spring beans, are by default added to the classpath of the project.
To make the best use of spring we have to tell spring below 3 things:
1. What are the beans ?
Classes having @Component annotations will be the beans.
2. What are the dependencies of a bean ?
Using @Autowired will help us in setting up the dependencies
3. Where to search for the beans ?
No need. Spring boot automatically search all the packages and the sub-packages wherever the main application resides. This is called component-scan.
All the beans are created and managed by ApplicationContext.
Whenever you create an spring boot application you will get below code by default:
@SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
You can assign the above run() call to an ApplicationContext like below:
ApplicationContext context = SpringApplication.run(MyApplication.class, args); MyImplementation obj=context.getBean(MyImplementation.class);//this is same as using @Component int result=obj.getSomeBusinessMethod(); //calling the methods on the bean.
In the above steps we had not directly wired together the classes rather we had used Spring to do the same and create the beans for us. So here Spring is doing dependency management, the dependency injection.
If you want to see what Spring framework does behind the scene, just put it in debug mode. Instead of putting the whole application into debug mode just put the spring framework into debug mode by below property in application.properties:
logging.level.org.springframework=debug
@Qualifier vs @Primary :
Lets create 2 beans implementation from a common interface as shown below:
public interface Interface1 { void getInfo(); }
@Component public class Implementation1 implements Interface1 { @Override public void getInfo() { System.out.println("------- User info from Implementation 1."); } }
@Component //@Primary public class Implementation2 implements Interface1 { @Override public void getInfo() { System.out.println("******* User info from Implementation 2."); } }
@Component public class Client1{ @Autowired //@Qualifier("implementation1") Interface1 interface1; }
From the main application:
@SpringBootApplication public class Springboot3Application { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(Springboot3Application.class, args); Client1 client1=context.getBean(Client1.class); Interface1 interface1= client1.interface1; interface1.getInfo(); } }
If you run this application you will get error, Spring will not be able to decide which one to pick:
Field interface1 in com.heapsteep.Client1 required a single bean, but 2 were found:
So you have to declare one as @Primary. After that you will see that bean will be picked up.
******* UserInfo from Implementation 2.
Now lets add @Qualifer and see the results.
------- UserInfo from Implementation 1.
@Qualifer has more priority than @Primary. Below is the order of precedence:
- @Qualifier(“some-name”)
- @Primary.
- Autowired by Name, instead of by Type.
Source code of the above : https://github.com/heapsteep/springboot3.git
Types of dependency injection:
There are 2 types of bean injection:
1. Constructor injection.
2. Setter injection.
Technically, there is also one more type of bean injection:
3. No Setter or Constructor injection.
1. Constructor Injection:
In the above example of Client1.java you can see the constructor injection.
@Component public class Client1 { @Autowired private Interface1 interface1; public Client1(Interface1 interface1) { super(); this.interface1 = interface1; } public int someBusinessMethod(int[] numbers, int numberToSearchFor) { int[] sortedNumbers = interface1.sort(numbers); System.out.println("---->"+interface1); return 3; } }
2. Setter Injection:
The same Client1.java can be modified to incorporate Setter injection like below:
@Component public class Client1 { @Autowired private Interface1 interface1; /*public Client1(Interface1 interface1) { super(); this.interface1 = interface1; }*/ public void setInterface1(Interface1 interface1) { this.interface1 = interface1; } public int someBusinessMethod(int[] numbers, int numberToSearchFor) { int[] sortedNumbers = interface1.sort(numbers); System.out.println("---->"+interface1); return 3; } }
In the above changing from the constructor injection to setter injection will not lead to any change in the result.
One interesting thing to note is that you won’t see the Autowired message in the log while doing Setter injection. You will see below message while doing a constructor injection.
Autowiring by type from bean name 'client1' via constructor to bean named 'implementation1'
3. No Setter or Constructor:
Not using any of the above 2 way. Technically this is similar to Setter injection.
@Component public class Client1 { @Autowired private Interface1 interface1; /*public Client1(Interface1 interface1) { super(); this.interface1 = interface1; }*/ /*public void setInterface1(Interface1 interface1) { this.interface1 = interface1; }*/ public int someBusinessMethod(int[] numbers, int numberToSearchFor) { int[] sortedNumbers = interface1.sort(numbers); System.out.println("---->"+interface1); return 3; } }
Which injection to use in which scenario ?
Well this used to be a favourite question of the interviewer. For the current version of spring it does not matter, but for older version of spring- if there is any mandatory dependency to be used, then go for Constructor injection. And if there is any optional dependency to be used, go for Setter injection.
Whenever we do an autowiring like below, it means autowiring by Type (here, by Interface1 type):
@Autowired private Interface1 interface1;
And if you have autowiring something like below, where you have mentioned the name of the reference as the name of some implemented class then it will be autowiring by Name (here, by implementation2 name). And obviously the methods in Implementation2 will be called.
@Autowired private Interface1 implementation2;