Framework/SPRING

Dependency Injection (DI)

데먕 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://stackoverflow.com/questions/39890849/what-exactly-is-field-injection-and-how-to-avoid-it#:~:text=The%20reasons%20why%20field%20injection,in%20unit%20tests)%20without%20reflection.

https://yaboong.github.io/spring/2019/08/29/why-field-injection-is-bad/

http://www.luv2code.com/

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-autowired-annotation

https://docs.oracle.com/javase/8/docs/api/java/beans/Introspector.html#decapitalize(java.lang.String)

https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-autowired-annotation-qualifiers