IoC, DI Container
- Spring Framework 에서 객체를 생성하고 관리하면서 의존 관계를 연결해 주는 컨테이너를 지원
- IoC Container 또는 DI Container라고 한다.
스프링 빈 등록
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImpl(MemberRepository()); // 회원 서비스(등록, 조회)
}
@Bean
public MemberRepository MemberRepository() {
return new MemoryMemberRepository(); // 회원 메모리 저장소
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(MemberRepository(), discountPolicy());
// 주문 서비스(회원 조회, 할인 적용)
}
@Bean
public DiscountPolicy discountPolicy(){
// return new FixDiscountPolicy(); // 정액 할인 정책
return new RateDiscountPolicy(); // 정률 할인 정책
}
}
- AppConfig에 설정을 구성한다는 뜻의
@Configuration을 붙여준다.
- 각 메서드에
@Bean 을 붙여서 스프링 빈으로 등록한다.
- DI 컨테이너에 객체를 빈으로 등록하고, 컨테이너에서 빈을 찾아서 사용하는 방식이다.
스프링 컨테이너 생성 과정
public class OrderApp {
public static void main(String[] args) {
// 어노테이션 기반 자바 설정 파일을 통해 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
// XML 기반 설정 파일을 통해 컨테이너 생성
ApplicationContext applicationContext = new GenericXmlApplicationContext("appConfig.xml");
// 스프링 빈 조회
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
// 조회한 빈을 사용하여 회원 생성
Long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
// 조회한 빈을 사용하여 주문 생성
Order order = orderService.createOrder(memberId, "itemA", 20000);
System.out.println("order = " + order);
System.out.println("order.calculatePrice = " + order.calculatePrice());
}
}
- 스프링 컨테이너 생성
ApplicationContext를 스프링 컨테이너라고 한다.
- XML 기반 혹은 어노테이션 기반의 자바 설정 클래스로 만들 수 있다.
AnnotationConfigApplicationContext는 인터페이스 ApplicationContext의 구현 체입니다.
- 스프링 빈 등록
- 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보(
AppConfig)를 사용해서 스프링 빈을 등록한다.
- 빈 이름의 디폴트 값은 메서드 이름이다. (
@Bean(name="orderService") 으로 직접 설정 가능)
- 스프링 빈 의존 관계 설정
- 설정 정보를 참고해서 의존 관계를 주입(DI)한다.
- 싱글톤 컨테이너로, 단순히 자바 코드를 호출하는 것과 차이가 있다.
스프링 빈 조회
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회")
public void findBeanByName() throws Exception {
MemberService memberService = ac.getBean("memberService", MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("이름 없이 타입으로만 조회")
public void findBeanByType() throws Exception {
MemberService memberService = ac.getBean(MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("구체 타입으로 조회(지양)")
public void findBeanByName2() throws Exception {
MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
@Test
@DisplayName("이름으로 조회한 빈이 없는 경우 오류 발생")
public void findBeanByNameX() throws Exception {
// MemberService xxxx = ac.getBean("xxxx", MemberService.class);
org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("xxxx", MemberService.class));
}
- 만약 스프링 컨테이너에 조회하는 빈이 존재하지 않으면
NoSuchBeanDefinitionException 발생
- 구체 타입으로 조회하면 변경 시 유연성이 떨어지므로, 역할(인터페이스)로 조회한다.
- 스프링 컨테이너에서 스프링 빈을 찾는 기본 조회 방법
- ac.getBean(빈 이름, 타입)
- ac.getBean(타입)