1. IoC란?
- IoC(Inversion of Control, 제어의 역전)는 프로그램의 제어 흐름을 개발자가 아닌 외부 프레임워크가 관리하는 소프트웨어 디자인 원칙입니다.
- 전통적인 프로그래밍에서는 개발자가 필요한 객체를 직접 생성하고, 의존성을 연결하며, 메서드를 호출하는 등 제어의 주체가 되었습니다.
- 반면 IoC 패러다임에서는 이러한 제어 흐름이 "역전"되어, 프레임워크가 객체의 생성과 생명주기를 관리하고 애플리케이션 코드를 필요한 시점에 호출합니다.
- 스프링 프레임워크에서 IoC는 주로 의존관계 주입(Dependency Injection, DI)을 통해 구현됩니다.
- 객체 지향 설계의 5가지 원칙(SOLID) 중 의존관계 역전 원칙(DIP)을 효과적으로 지원합니다.
- DIP: 프로그래머는 추상화에 의존해야 하며, 구체화에 의존하면 안 된다.
- SOLID 참고
1.1 실제 의미
public class UserService {
private UserRepository userRepository = new UserRepositoryImpl(); // 직접 생성
// 또는
private UserRepository userRepository = ServiceLocator.getUserRepository(); // 서비스 로케이터 사용
}
- 위는 객체 생성과 의존성 설정을 직접 수행하는 전통적인 방식입니다.
- 이 방식은 객체 간의 결합도가 높아져 유연성이 떨어지며, 테스트가 어려워집니다.
- IoC 컨테이너는 이러한 문제를 해결하기 위해 객체 생성과 의존성 설정을 외부로 분리합니다.
public class UserService {
private final UserRepository userRepository;
// 의존성이 외부에서 주입됨
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
- 반면 IoC 컨테이너에서는 객체가 필요한 다른 객체를 외부에서 주입받아 사용합니다.
- UserService가 구체적인 UserRepositoryImpl 클래스에 의존하지 않고, 인터페이스에만 의존합니다.
- 테스트 시 진짜 DB에 접근하는 UserRepositoryImpl 대신 가짜(mock) 구현체를 주입할 수 있습니다.
- 필요에 따라 다른 구현체로 쉽게 교체할 수 있어 유연성이 증가합니다.
- 이렇게 객체 간의 의존관계를 외부에서 주입받아 사용하는 것을 DI(Dependency Injection)라고 합니다.
2. ApplicationContext
- ApplicationContext는 스프링의 핵심 IoC 컨테이너입니다.
- 빈(Bean)을 초기화하고, 구성하고, 의존성을 조립하는 역할을 수행합니다.
- 컨테이너는 XML, Java Annotation 등의 configuration metadata를 읽어 어떤 빈을 초기화하고 의존성을 어떻게 조립할지 결정합니다.
- 기존에는 개발자가 직접 자바코드로 모든 것을 했다면 이제부터는 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용할 수 있습니다.
2.1 ApplicationContext의 부가 기능
- ApplicationContext는 단순한 빈 관리 외에도 여러 부가 기능을 제공합니다:
- BeanFactory: 빈 관리 및 조회 기능
- ApplicationEventPublisher: 이벤트 발행과 구독 모델 지원
- EnvironmentCapable: 환경변수 관리(로컬, 개발, 운영 환경 구분)
- ResourceLoader: 파일, 클래스패스 등의 리소스 조회 기능
2.2 BeanFactory
- BeanFactory는 스프링 컨테이너의 최상위 인터페이스입니다.
- ApplicationContext는 BeanFactory를 상속받아 빈 관리 기능을 제공합니다.
- 실제로 빈을 관리하는 기능은 BeanFactory에 있지만, 보통은 ApplicationContext를 통해 간접적으로 사용합니다.
applicationContext.getBean()메서드는 BeanFactory로부터 상속받은 메서드입니다.- BeanFactory를 직접 사용하는 경우는 거의 없으므로 일반적으로 ApplicationContext를 스프링 컨테이너라 합니다.
2.3 Configuration Metadata 설정
- 스프링 컨테이너는 configuration metadata에 명시한 대로 빈을 초기화하고 의존관계를 조립합니다.
- 스프링 컨테이너는 다양한 형식의 설정 정보를 받아 드릴 수 있게 유연하게 설계되어 있습니다.
- XML, Java Annotation, Java 코드 등 다양한 방식을 지원합니다.
- 최근에는 스프링 부트를 많이 사용하면서 XML기반의 설정은 잘 사용하지 않습니다.
2.3.1 Java 기반 설정
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(
memberRepository(),
discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
@Configuration애노테이션이 붙은 클래스는 스프링 설정 정보로 사용됩니다.@Bean애노테이션이 붙은 메서드의 반환 객체가 스프링 컨테이너에 빈으로 등록됩니다.
2.4 컨테이너 생성
Java 기반 설정을 사용한 컨테이너 생성 방법:
// ApplicationContext 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
// ApplicationContext에서 빈 조회
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
AnnotationConfigApplicationContext는 ApplicationContext 인터페이스의 구현체로, 애노테이션 기반의 설정 클래스를 읽어 빈을 초기화하고 의존성을 조립합니다.
3. 스프링 컨테이너 다루기
3.1 스프링 컨테이너 생성하기
스프링 컨테이너 생성은 다음 단계로 이루어집니다:
- 스프링 컨테이너 생성:
- 구성 정보(Configuration)를 지정하여 컨테이너를 생성합니다.
- 예:
new AnnotationConfigApplicationContext(AppConfig.class);
- 스프링 빈 등록:
- 구성 정보를 사용해 스프링 빈을 등록합니다.
- 빈의 이름은 기본적으로 메서드 이름이 됩니다.
@Bean(name="customName")으로 이름을 직접 지정할 수도 있습니다.- 빈 이름은 중복 되면 안 됩니다.
- 의존관계 주입:
- 빈 사이의 의존관계가 있다면 의존성을 주입합니다.
3.1 스프링 컨테이너에서 빈 조회
- 스프링 컨테이너에서 빈을 조회하는 방법은 다음과 같습니다:
// 빈 조회(빈이름, 타입)
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
// 타입으로 조회
MemberService memberService = applicationContext.getBean(MemberService.class);
getBean()메서드는 빈 이름과 타입을 지정해 빈을 조회합니다.- 빈이 없으면
NoSuchBeanDefinitionException이 발생합니다.
3.1.1 같은 타입의 빈이 둘 이상인 경우
- 만약 타입으로 조회 시 같은 타입의 빈이 둘 이상이면
NoUniqueBeanDefinitionException이 발생합니다. - 이러한 경우 이름을 지정해 빈을 조회해야 합니다.
3.1.2 상속 관계
- 부모 타입으로 조회하면 자식 타입도 함께 조회됩니다.
- 부모 타입으로 조회시 자식 타입이 둘 이상이면
NoUniqueBeanDefinitionException이 발생합니다.- 이 경우 빈 이름을 지정해야 합니다.
- getBeansOfType() 메서드를 사용하면 해당 타입의 모든 빈을 조회할 수 있습니다.