의존성 주입이 필요한 이유?

。한 Application에서 Web Layer, Business Layer, Data Layer가 각각 존재 시 각 Layer을 구성하는 Class들은 서로의 객체들을 필요로 한다.
▶ Spring Framework은 각 Layer의 클래스를 Spring Bean으로 등록 및 dependency를 정의하여 의존객체에 Spring Bean을 주입
▶ 실제 application은 수천개의 dependency가 존재한다고 할 수 있다.
의존성 주입 ( DI : Dependency Injection )
。객체가 의존하는 다른 객체를 직접 생성하지 않고 외부에서 주입받는 것
。Spring Context가 Bean Registry 내 저장된 Spring Bean을 필요로 하는 의존객체에 의존성을 정의하여 Auto-Wiring하여 Spring Bean을 주입
▶ 의존성주입을 구현하지 않으면 의존객체에서 NPE 발생.
。Spring Bean으로 등록한 Class에서 의존성 주입을 받을 수 있다.
▶ @Component, @Configuration , ... 등으로 선언된 Class
。Spring Bean의 제어권이 Spring에 있으므로 IoC 제공
의존성 주입의 장점
。낮은 결합도 : Service는 Repository 구현체를 몰라도 된다.
。유지보수 용이 : DB를 Memory, JPA로 교체하더라도 Service의 소스코드 수정은 없음.
。테스트용이 : Mock객체 주입이 용이
。확장성 : 구현체 교체 시 소스코드 수정이 거의없어 구현체 추가 등 확장이 용이
▶ OCP 준수
Auto-Wiring :
。Spring Framework에서 Spring Bean 의 의존성을 분석하여 올바른 의존객체를 자동으로 식별하여 주입하는 과정
@Autowired :
。 의존성 주입을 수행하는 어노테이션
▶ Spring Context에서 해당 의존객체의 Class Type에 해당하는 Spring Bean을 자동으로 선언된 Field, Setter의 주입 방식에 따라 의존성 주입을 수행
의존성 주입 방법
。생성자 주입, 필드 주입, 세터 주입이 존재
생성자 주입 ( Constructor-based DI )
。생성자를 사용하여 의존객체에 의존성을 정의하여 Spring Bean을 주입
。클래스 내 생성자가 단 하나만 존재해야 @Autowired를 생략가능
▶ 클래스 내 생성자가 많은 경우 @Autowired를 명시적으로 선언
。실무에서 가장 많이 사용
▶ 초기화 이후 재할당이 불가능한 불변객체에 Spring Bean의 의존성주입이 적합한 방식이므로.
private final Dependency1 dependency1;
public YourBusinessClass(Dependency1 dependency1) {
this.dependency1 = dependency1;
}
필드 주입 ( Field-based DI ) :
。의존객체인 Field에 @Autowired 선언하여 의존성을 정의하여 Spring Bean을 주입
。 Java Reflection을 통해 주입하므로 final을 선언한 불변객체에는 의존성 주입을 수행할 수 없다.
▶ 주로 테스트코드 적용 시 활용
Java Reflection
@Autowired
Dependency1 dependency1;
Setter 주입 ( Setter-based DI )
。의존객체의 setter method에 @Autowired를 선언하여 의존성을 정의하여 Spring Bean 주입
▶ Setter 자체가 비추천되는방식이므로 사용되진 않는다.
private Dependency1 dependency1;
@Autowired
public void setDependency1(Dependency1 dependency1) {
this.dependency1 = dependency1;
}
인터페이스 구현체나 클래스를 의존성 주입 시 Spring Context 내 이를 상속하거나 구현하는 복수의 Spring Bean이 존재 시 사용하는 방법
。의존객체에 의존성주입을 수행하기위해 클래스 또는 인터페이스 구현체에 해당하는 Spring Bean을 찾을때 Spring Context 내 해당 클래스 또는 인터페이스를 상속하거나 구현한 복수의 클래스의 Spring Bean이 등록되있는 경우 어떤 Spring Bean으로 가져와야 할지 설정이 필요
。@Primary, @Qualifier, 이름주입, PSA , 프로필의 방법이 존재
▶ PSA은 소스코드를 변경하지않고 Configuration file을 통해 특정 Spring Bean을 사용하도록 설정 PSA
▶ 프로필의 경우도 특정 프로필을 활성화하는 방법으로 특정 Spring Bean을 사용하도록 설정 프로필
@Primary :
。Spring Bean으로 등록되고 동일한 Class or Interface를 상속받은 클래스 중 우선적으로 의존성 주입을 수행할 클래스에 선언하여 우선권을 부여하는 어노테이션
▶ Candidate Bean 중 해당 Class의 Spring Bean을 우선적으로 의존성 주입
@Qualifier("스프링빈Class명") :
。 Spring Bean 주입 시 주입할 Spring Bean Class명을 선언하여 해당 명칭의 Spring Bean을 주입하도록하는 어노테이션
▶ @Qualifier은 @Primary보다 더 높은 우선순위를 지님.
。의존객체에 DI가 수행되는 생성자 매개변수에 @Qualifier("스프링빈Class명")을 선언
▶ @Qualifier를 통해 해당 명칭의 Class Type의 Spring Bean이 자동으로 주입
▶ Spring Bean을 정의하는 Class에도 추가적으로 Qualifier("사용자정의명칭") 선언 시 클래스명이 아닌 사용자정의명칭으로 식별하여 Spring Bean 주입
이름주입
。단순히 생성자 매개변수 이름을 SpringBean명으로 명시적으로 설정 시 해당 명칭의 Spring Bean을 Context의 Bean Registry에서 찾아서 자동으로 주입
▶ SpringBean명 : String Bean 클래스명의 카멜케이스
interface GamingConsole{
String up();
}
@Component
@Primary
class MarioGame implements GamingConsole{
public String up(){
return "Mario goes upside";
}
}
@Component
@Qualifier("wjdtn")
class SuperContraGame implements GamingConsole{
public String up(){
return "Character goes upside";
}
}
@Component
class Runner {
private final GamingConsole gc1;
private final GamingConsole gc2;
private final GamingConsole gc3;
public Runner(GamingConsole gc1 , @Qualifier("wjdtn") GamingConsole gc2 , GamingConsole marioGame){
this.gc1 = gc1;
this.gc2 = gc2;
this.gc3 = marioGame;
}
public void run(){
System.out.println(gc1.up());
System.out.println(gc2.up());
System.out.println(gc3.up());
}
}