-
Dependency Injection (DI)Framework/SPRING 2020. 3. 5. 19:24
1. Overview
for dependency injection, Spring can automatically wire up your objects together. So basically what'll happen is that Spring will look for a class that matches a given property. And it'll actually match by type, so the type could be either the class or the interface. Once Spring finds a match, then it'll automatically inject it. Hence it's called Autowired.
2. Dependency Injection with Annotations
3.1 Construction Injection
- Injecting FortuneService into a Coach Implementation
- Spring will scan @Components
- Anyone implements FortuneService interface. If so, injecting them. For example, HappyFortuneService
- Forcing that the object is created with the required dependency
3.1.1 What if there are multiple FortuneService implementations?
Spring has special support to handle this case. Use the @Qualifier annotation.
interface Coach { public String getDailyWorkout(); public String getDailyFortune(); } // helper. This is a dependency of Coach interface FortuneService { public String getFortune(); } @Component class HappyFortuneService implements FortuneService { @Override // Spring will find this bean that implements FortuneService based on Component annotation public String getFortune() { return "Today is your lucky day!"; } } @Component class TennisCoach implements Coach { private FortuneService fortuneService; @Autowired // auto injection will occur in this constructor based on autowired annotation public TennisCoach(FortuneService theFortuneService) { fortuneService = theFortuneService; } @Override public String getDailyWorkout() { return "Practice your backhand volley"; } @Override public String getDailyFortune() { return fortuneService.getFortune(); } } public class AnnotationDemoApp { public static void main(String[] args) { // read spring config file ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // get the bean from spring container Coach theCoach = context.getBean("tennisCoach", Coach.class); // call a method on the bean System.out.println(theCoach.getDailyWorkout()); // call method to get daily fortune System.out.println(theCoach.getDailyFortune()); // close the context context.close(); } }
<!-- add entry to enable component scanning --> <context:component-scan base-package="com.luv2code.springdemo" />
3.2 Method Injection (including Setter Injection)
interface Coach { public String getDailyWorkout(); public String getDailyFortune(); } interface FortuneService { public String getFortune(); } @Component class HappyFortuneService implements FortuneService { @Override public String getFortune() { return "Today is your lucky day!"; } } @Component class TennisCoach implements Coach { private FortuneService fortuneService; // define a default constructor public TennisCoach() { System.out.println(">> TennisCoach: inside default constructor"); } // define a setter method @Autowired public void setFortuneService(FortuneService theFortuneService) { System.out.println(">> TennisCoach: inside setFortuneService() method"); this.fortuneService = theFortuneService; } /* @Autowired public TennisCoach(FortuneService theFortuneService) { fortuneService = theFortuneService; } */ @Override public String getDailyWorkout() { return "Practice your backhand volley"; } @Override public String getDailyFortune() { return fortuneService.getFortune(); } } public class AnnotationDemoApp { public static void main(String[] args) { // read spring config file ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // get the bean from spring container Coach theCoach = context.getBean("tennisCoach", Coach.class); // call a method on the bean System.out.println(theCoach.getDailyWorkout()); // call method to get daily fortune System.out.println(theCoach.getDailyFortune()); // close the context context.close(); } }
3.3 Field Injection
Inject dependencies by setting field values on your class directly even private fields through using Java Reflection. It is applied directly to the field and no need for setter methods.
interface Coach { public String getDailyWorkout(); public String getDailyFortune(); } interface FortuneService { public String getFortune(); } @Component class HappyFortuneService implements FortuneService { @Override public String getFortune() { return "Today is your lucky day!"; } } @Component class TennisCoach implements Coach { @Autowired private FortuneService fortuneService; // define a default constructor public TennisCoach() { System.out.println(">> TennisCoach: inside default constructor"); } // define a setter method /* @Autowired public void setFortuneService(FortuneService theFortuneService) { System.out.println(">> TennisCoach: inside setFortuneService() method"); this.fortuneService = theFortuneService; } */ /* @Autowired public TennisCoach(FortuneService theFortuneService) { fortuneService = theFortuneService; } */ @Override public String getDailyWorkout() { return "Practice your backhand volley"; } @Override public String getDailyFortune() { return fortuneService.getFortune(); } } public class AnnotationDemoApp { public static void main(String[] args) { // read spring config file ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // get the bean from spring container Coach theCoach = context.getBean("tennisCoach", Coach.class); // call a method on the bean System.out.println(theCoach.getDailyWorkout()); // call method to get daily fortune System.out.println(theCoach.getDailyFortune()); // close the context context.close(); } }
3.4 Qualifiers
How will Spring know which one to pick? You have to kind of be very specific, and you have to qualify or tell Spring which specific bean you want them to use. So what you'll do is actually make use of this annotation called Qualifier.
The default name of bean id of a Class name is the first letter lower case. For example, HapptyFortuneService will make a bean id named happyFortuneService.
interface Coach { public String getDailyWorkout(); public String getDailyFortune(); } interface FortuneService { public String getFortune(); } @Component("dfs1") class DatabaseFortuneService implements FortuneService { @Override public String getFortune() { // TODO Auto-generated method stub return null; } } @Component("hfs5") class HappyFortuneService implements FortuneService { @Override public String getFortune() { return "Today is your lucky day!"; } } @Component("rfs") class RandomFortuneService implements FortuneService { // create an array of strings private String[] data = { "Beware of the wolf in sheep's clothing", "Diligence is the mother of good luck", "The journey is the reward" }; // create a random number generator private Random myRandom = new Random(); @Override public String getFortune() { // pick a random string from the array int index = myRandom.nextInt(data.length); String theFortune = data[index]; return theFortune; } } @Component("RESTFS") class RESTFortuneService implements FortuneService { @Override public String getFortune() { // TODO Auto-generated method stub return null; } } class SadFortuneService implements FortuneService { @Override public String getFortune() { return "Today is a sad day"; } } @Component("tc5") class TennisCoach implements Coach { @Autowired @Qualifier("hfs5") private FortuneService fortuneService; // define a default constructor public TennisCoach() { System.out.println(">> TennisCoach: inside default constructor"); } // define a setter method /* @Autowired public void setFortuneService(FortuneService theFortuneService) { System.out.println(">> TennisCoach: inside setFortuneService() method"); this.fortuneService = theFortuneService; } */ /* @Autowired public TennisCoach(FortuneService theFortuneService) { fortuneService = theFortuneService; } */ @Override public String getDailyWorkout() { return "Practice your backhand volley"; } @Override public String getDailyFortune() { return fortuneService.getFortune(); } } public class AnnotationDemoApp { public static void main(String[] args) { // read spring config file ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // get the bean from spring container Coach theCoach = context.getBean("tc5", Coach.class); // call a method on the bean System.out.println(theCoach.getDailyWorkout()); // call method to get daily fortune System.out.println(theCoach.getDailyFortune()); // close the context context.close(); } }
3.4.1 Qualifier on Constructor
public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(@Qualifier("main") MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ... }
3.5 Injection Guidelines
- For mandatory dependencies or when aiming for immutability, use constructor injection
- For optional or changeable dependencies, use setter injection
- Avoid field injection in most cases
3.5.1 Field Injection Drawbacks
- You cannot create immutable objects, as you can with constructor injection
- Your classes have tight coupling with your DI container and cannot be used outside of it
- Your classes cannot be instantiated (for example in unit tests) without reflection. You need the DI container to instantiate them, which makes your tests more like integration tests. Also, Reflection makes the application code run slower since it involves types that are dynamically resolved at runtime
- Your real dependencies are hidden from the outside and are not reflected in your interface (either constructors or methods)
- It is really easy to have like ten dependencies. If you were using constructor injection, you would have a constructor with ten arguments, which would signal that something is fishy. But you can add injected fields using field injection indefinitely. Having too many dependencies is a red flag that the class usually does more than one thing and that it may violate the Single Responsibility Principle.
3.5.2 Constructor Injection Pros
- Cannot Instantiation without clear dependency
- Prevent NullPointerException(NPE)
- Can be recognized at compile time
- Can declare fields as final
- Immutable
- Can be recognized cycle reference
- Easy for test code
4. Reference
https://yaboong.github.io/spring/2019/08/29/why-field-injection-is-bad/
'Framework > SPRING' 카테고리의 다른 글
Application Context (0) 2020.04.10 Portable Service Abstraction (PSA) (0) 2020.03.06 Spring Inversion of Control (IoC) (0) 2020.02.06 Spring Framework (0) 2020.02.06 Spring MVC and Request Life Cycle (0) 2020.02.03