종속객체 주입 (DI, Dependency Injection)
아래와 같은 코드가 있다고 가정해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class DamselRescuingKnight implements Knight { private RescueDamselQuest quest; public DamselRescuingKnight () { this.quest = new RescueDamselQuest (); } public void embarkOnQuest () { quest.embark(); } } | cs |
이 코드에서는 DamselRescuingKnight 클래스를 생성했을 때 RescueDamselQuest 객체를 생성한다.
이 경우에는 새로운 RescueDamselQuest 객체의 값이 변경될 수 없다. DamselRescuingKnight 객체가 생성될 때, 생성자로써 RescueDamselQuest 객체를 만들어 private 전역변수에 저장해두기 때문이다.
이 코드와 같은 경우 DamselRescuingKnight 가 RescueDamselQuest 에 강하게 결합되어 있다고하며, DamselRescuingKnight 에 대한 단위테스트가 몹시 어렵다.
그렇다면 DI를 활용하면 이 코드를 어떻게 바꿀 수 있을까?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class BraveKnight implements Knight { private Quest quest; public BraveKnight (Quest quest) { this.quest = quest; } public void embarkQuest () { quest.embark(); } } | cs |
BraveKnight 클래스에서는 RescueDamselQuest 의 인터페이스인 Quest 타입으로 전역변수 quest를 생성한다. 그렇기 때문에 RescueDamselQuest 뿐만 아닌 Quest를 구현하는 어떤 클래스도 부여받을 수 있다.
그리고 BraveKnight는 생성자를 오버라이딩해 Quest 객체를 주입받는다. 그렇기 때문에 BraveKnight는 특정 구현체에 결합되지 않는다. 이것이 바로 DI의 주요 이점인 느슨한 결합도 이다.
좀 더 쉽게 비교해보자면, RescueDamselQuest 클래스의 내용이 '공주를 구해라!' 라고 가정을 했을 때, DamselRescuingKnight 는 어떤 짓을 해도 '공주를 구하는 것' 외에는 어떤 것도 하지 못한다.
반면, BraveKnight의 경우는 확연히 다르다. Quest의 구현 클래스의 내용이 '용이 있으면 용을 무찔러라!' 이면 BraveKnight는 용을 무찌른다.
또 다른 Quest의 구현 클래스의 내용이 '머리가 노란색인 공주를 구해라!' 라면 BraveKnight는 노란 머리의 공주를 구한다.
그렇다면, 이제 BraveKnight에게 부여할 임무를 만들자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class SlayDragonQuest implement Quest { private PrintStream stream; public SlayDragonQuest (PrintStream stream) { this.stream = stream; } public void embark () { stream.println ("Embarking on quest to slay the dragon!"); } } | cs |
이제 중대한 문제가 생겼다! 어떻게 BraveKnight에게 이 임무를 부여하고 임무에게 PrintStream 객체를 넘길 것인가?
애플리케이션 컴포넌트 간의 관계를 정하는 것을 와이어링 이라고 하는데, 스프링에서는 와이어링을 XML로 할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?xml version="1.0" encoding="UTF-8"?> <beans xmls="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="knight" class="com.test.logicalCode.knight.BraveKnight"> <constructor-arg ref="quest" /> ← quest라는 이름을 가진 Bean을 생성자에 넘김 </bean> <bean id="quest" class="com.test.logicalCode.quest.SlayDragonQuest"> <constructor-arg value="#{T(System).out}" /> ← SpEL을 이용해 System.out을 생성자에 넘김 </bean> </beans> | cs |
이렇게 와이어링까지 된 애플리케이션은 .ApplicationContext()로 XML을 로드해 스프링 컨텍스트를 로드한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class KnightMain { public static void main(String[] args) throws Exception { ClassPathXmlApplicationContext context = new new ClassPathXmlApplicationContext("META-INF/spring/knights.xml"); Knight knight = context.getBean(Knight.class); knight.embarkOnQuest(); context.close(); } } | cs |
|
이 포스트는 스프링 인 액션을 읽고 개인적으로 필요하다고 생각되는 내용을 정리한 포스트입니다.
일부 내용, 소스코드는 스프링 인 액션의 내용일 수 있습니다. |
'🌱 SPRING > 스프링 인 액션' 카테고리의 다른 글
조건부 빈 (0) | 2018.05.21 |
---|---|
XML로 빈 와이어링하기 (0) | 2018.05.07 |
자바로 빈 와이어링하기 (0) | 2018.05.07 |
빈 와이어링 - 자동 와이어링 (0) | 2018.05.07 |
스프링 컨테이너 (0) | 2018.05.06 |