본문 바로가기
스프링

스프링 - 스프링 컨테이너와 스프링 빈(1)

by 왈레 2022. 4. 16.

1. 스프링 컨테이너 생성

 

스프링 컨테이너 생성

//스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
  • ApplicationContext 를 스프링 컨테이너라 한다.
  • ApplicationContext 는 인터페이스이다.
  • 스프링 컨테이너는 XML을 기반으로 만들 수 있고, 애노테이션 기반의 자바 설정 클래스로 만들 수 있다.
  • new AnnotationConfigApplicationContext(AppConfig.class); 이 클래스는 ApplicationContext 인터페이스의 구현체이다.
  • 스프링 컨테이너를 부를 때 BeanFactory , ApplicationContext 로 구분해서 이야기한다. BeanFactory를 직접 사용하는 경우는 거의 없으므로 일반적으로 ApplicationContext를 스프링 컨테이너라 한다.

 

스프링 컨테이너 생성과정

1. 스프링 컨테이너 생성

 

2. 스프링 빈 등록

  • 빈 이름을 직접 부여할 수 도 있다. ex) @Bean(name = "memberService2")
  • 빈 이름은 항상 다른 이름을 부여해야 한다. 같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생한다.
  • 참고로 AppConfig.class도 스프링 컨테이너에 올라간다.

 

3. 스프링 빈 의존관계 설정 - 준비

 

 

4. 스프링 빈 의존관계 설정 - 완료

  • 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.
  • 단순히 자바 코드를 호출하는 것 같지만, 차이가 있다. 이 차이는 뒤에 싱글톤 컨테이너에서 설명한다.
  • 스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있다. 그런데 이렇게 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한번에 처리된다. 여기서는 이해를 돕기 위해 개념적으로 나누어 설명했다. 자세한 내용은 의존관계 자동 주입에서 다시 설명
  • 실제로는 의존관계는 위처럼 단계가 나눠지고 한번에 설정되지 않는다. 스프링 라이플 사이클은 의존관계가 나눠져있다. 뒤에 내용참고( )

 

 


 

2. 컨테이너에 등록된 Bean 조회

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

@Test
@DisplayName("모든 Bean 출력하기")
void findAllBean() throws Exception {
    String[] beanDefinitionNames = ac.getBeanDefinitionNames(); //스프링에 등록된 모든 빈 이름을 조회

    for (String beanDefinitionName : beanDefinitionNames) {
        Object bean = ac.getBean(beanDefinitionName); //빈 이름으로 객체(인스턴스) 조회
        System.out.println("name = " + beanDefinitionName + "object = " + bean);
    }
}

@Test
@DisplayName("사용자정의 Bean 출력하기")
void findApplicationBean() throws Exception {
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();

    for (String beanDefinitionName : beanDefinitionNames) {
        BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName); //빈의 메타정보 조회

        //ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
        //ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
        if(beanDefinition.getRole() == BeanDefinition.ROLE_INFRASTRUCTURE){
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println("name = " + beanDefinitionName + "object = " + bean);
        }
    }

}

모든 Bean 출력하기

  • 실행하면 스프링에 등록된 모든 빈 정보를 출력할 수 있다. (스프링이 내부에서 사용하는 빈까지 포함)
  • ac.getBeanDefinitionNames() : 컨테이너에 등록된 모든 빈 이름을 조회한다.
  • ac.getBean() : 빈 객체(인스턴스)를 조회한다.

 

사용자 정의 Bean 출력하기

  • 스프링이 내부에서 사용하는 빈은 getRole() 로 구분할 수 있다.
  • ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
  • ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈

 

 


 

3. 스프링 빈 조회 - 기본

  • ac.getBean(빈 이름) : '빈 이름'을 갖는 빈을 조회한다.
  • ac.getBean(빈 타입) : 인테페이스, 구체타입 어떤 걸로든 조회 가능하다. 같은 타입의 빈이 둘 이상이면 오류가 발생한다. 이때는 빈 이름을 지정해주도록 한다.
  • ac.getBean(빈 이름, 빈 타입) : '빈 이름'을 갖고, '빈 타입'을 갖는 빈을 조회
  • 조회 대상 스프링 빈이 없으면 예외 발생 NoSuchBeanDefinitionException: No bean named 'xxxxx' available

 

 

 

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

@Test
@DisplayName("빈 이름으로 조회")
public void findBeanByName() throws Exception {
    MemberService memberService = ac.getBean("memberService", MemberService.class); // 빈 이름으로 조회
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}

@Test
@DisplayName("빈 이름 없이 타입으로만 조회")
public void findBeanByTypeInterface() throws Exception {
    MemberService memberService = ac.getBean(MemberService.class); // 빈 타입으로 조회
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}

@Test
@DisplayName("구체 타입으로 조회")
public void findBeanByTypeClass() throws Exception {
    MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class); //
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}

@Test
@DisplayName("빈 이름으로 조회가 안될경우, 실패테스트")
void findBeanByNameX() {
    //ac.getBean("xxxxx", MemberService.class);
    
    assertThrows(NoSuchBeanDefinitionException.class,
            () -> ac.getBean("xxxxx", MemberService.class)); //예외가 터져야 테스트 성공
}

참고: 구체 타입으로 조회하면 변경시 유연성이 떨어지고 별로 좋은 코드는 아니다. (인터페이스가 아닌 구현체에 의존...)

 

 


 

4.  스프링 빈 조회 - 동일한 타입이 둘 이상

  • 타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생한다. 이때는 빈 이름을 지정하자.
  • ac.getBeansOfType(빈 타입) 을 사용하면 해당 타입의 모든 빈을 조회할 수 있다. (반환값은 Map)
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);

@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다.")
void findBeanByTypeDuplicate() throws Exception {
    // MemberRepository bean = ac.getBean(MemberRepository.class);

    assertThrows(NoUniqueBeanDefinitionException.class, //예외가 터져야 테스트 성공
            () -> ac.getBean(MemberRepository.class));
}

@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상이 있으면, 빈 이름을 지정하면 된다.")
void findBeanByName() throws Exception {
    MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
    assertThat(memberRepository).isInstanceOf(MemberRepository.class);
}

@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType() throws Exception {
    Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class); //map으로 나온다.
    for (String key : beansOfType.keySet()) {
        System.out.println("key = " + key + "value = " + beansOfType.get(key));
    }

    System.out.println("beansOfType = " + beansOfType);
    assertThat(beansOfType.size()).isEqualTo(2);
}

@Configuration
static class SameBeanConfig{ //내부 static 클래스 = 내부 클래스를 감싸는 외부 클래스만 이 내부 클래스를 사용하겠다는 뜻
    @Bean
    public MemberRepository memberRepository1(){
        return new MemoryMemberRepository();
    }

    @Bean
    public MemberRepository memberRepository2(){
        return new MemoryMemberRepository();
    }
}

 

※출처 - 인프런 김영한 스프링

댓글