스프링2 - 스프링 필수개념 DI, IOC
의존성 주입(DI)
의존성 주입(Dependency Injection)이란 외부에서 실제 참조형 구현 객체를 생성하여 클라이언트 객체에 전달함으로써 의존관계가 생기는 것을 뜻한다. 의존성 주입을 잘 활용하면 클래스의 내부에서 의존관계를 변경하지 않고 외부에서 동적으로 객체 인스턴스 의존관계를 쉽게 변경할 수 있다. 외부에서 객체를 생성하고 관리하면서 의존관계를 연결해 주는 컨트롤 객체를 DI컨테이너
라고 한다.
다음 코드를 통해 좀 더 자세히 알아보자.
1
2
3
4
5
6
7
8
9
10
11
//DI가 적용되지 않은 객체
public class CharacterService {
//참조객체를 클래스 내부에서 직접 호출하여 선언
private final Character character = new CharacterState(100, 50, 50);
private int attack;
public CharacterService(Character character) {
this.attack = character.getAttack();
}
}
위 코드를 보면 Character
인터페이스의 구현체인 CharacterState
클래스를 참조객체로 선언하고 있다. 이 것은 클라이언트 객체가 참조객체를 직접 의존하고 있는 강한 결합 상태라고 볼 수 있다. 즉, 지정된 참조객체로만 사용하게 되기 때문에 해당 구현체를 참조하는 클래스가 많아질 수록 구현체 변경 시 코드 수정량이 많아진다. 이는 유연하거나 확장 가능한 구조가 아니다. 스프링이 제공하는 DI 의존성 주입은 이러한 단점을 해결해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
//DI가 적용된 객체
@Service
public class CharacterService {
//참조 객체를 외부에서 선언(의존성 주입)
private Character character;
private int attack;
public CharacterService(Character character) {
this.character = character;
this.attack = character.getAttack();
}
}
위의 코드는 생성자에 파라미터로 Character
인터페이스만 선언되어 있다. 스프링 DI컨테이너
에서 자동으로 해당 인터페이스 구현체를 의존성 주입해주기 때문에, 어떤 구현체를 선언했는지 클라이언트 객체는 알 필요가 없다. 인터페이스의 구현체가 CharacterState
클래스 하나라면 자동으로 해당 구현체가 주입될 것이고, 여러 개라면 특정 구현체를 지정하여 주입시킬 수 있다.
의존성을 주입하여 사용하게 되면 Character
인터페이스의 구현체 변경이 필요하더라도 불필요한 코드 변경 없이 구현체만 지정하여 사용할 수 있게 된다. 즉, 직접 참조하지 않고 의존성을 주입받아 사용하는 느슨한 결합 상태이며, 코드의 가독성, 유연성 및 확장성도 좋아진다.
그렇다면 DI컨테이너
에서 의존성 주입을 위해 어떤 정보들을 담고 있고, 어떻게 구성되어 있는지 제어의 역전(IOC)을 통해서 알아보자
제어의 역전(IOC)
제어의 역전(Inversion of Control)이란 프로그램에 대한 제어 흐름을 내부에서 관리하는 것이 아니라 외부(프레임워크)에서 직접 제어하고 대신 실행하는 것을 뜻한다.
기존의 클라이언트 객체가 생성, 연결, 실행을 담당하던 제어 흐름을 외부 컨테이너에서 담당하게 된다. 따라서 클라이언트 객체는 실행에 필요한 로직만 남게된다.
1
2
3
4
5
6
7
8
9
@Configuration
public class AppConfig {
// 빈에 참조객체 의존성을 주입한 클라이언트 객체를 저장
@Bean
public Character character() {
return new ChracterStatus(100, 50, 50);
}
}
위 코드 처럼 @Configuration
어노테이션이 선언된 클래스는 DI컨테이너
에서 관리될 Bean
정보를 생성하고 설정할 수 있다. 스프링 Bean
을 활용하여 클라이언트 객체에 참조객체를 선언하여 저장한다. 즉, Character
인터페이스 생성 빈을 통해서 ChracterStatus
구현체가 선언된 빈을 설정하였고, CharacterService
클래스에서 Character
인터페이스 의존성 주입 시에 DI컨테이너
에서 character 빈을 호출하여 사용하게 되는 것이다.
1
2
3
4
5
6
7
8
public class CharacterApp {
public static void main(String[] args) {
//스프링 컨테이너 호출
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//컨테이너에 저장된 빈 호출
Character character = ac.getBean("character", Character.class);
}
스프링 컨테이너
를 통해서 AppConifg
클래스에서 생성했던 character 빈을 호출하여 의존성 주입이 잘 되었는지 확인해볼 수 있다.