[인프런-스프링 핵심 원리] 스프링 컨테이너, 스프링 빈

JoonYoung Maeng·2021년 5월 23일
0
post-thumbnail
  • 일반적으로 ApplicationContext 인터페이스를 스프링 컨테이너라고 한다.

  • 스프링 컨테이너는 XML 기반 혹은 자바 어노테이션 기반 자바 클래스로 설정이 가능하다.

    → XML 기반 (과거) : ClassPathXmlApplicationContext

    → 자바 어노테이션 기반 (최근) : AnnotaionConfigApplicationContext

    ApplicationContext annotationCtx = new AnnotationConfigApplicationContext(AppConig.class);
    ApplicationContext xmlCtx = new ClassPathXmlApplicationContext("application.xml");

✔️ 스프링 컨테이너는 BeanFactoryApplicationContext로 구분되지만, 일반적으로 BeanFactory를 직접 사용하는 경우가 드물기 때문에 스프링 컨테이너 = ApplicationContext라 칭한다.


🛣️ 스프링 컨테이너 생성 과정

image

  1. 스프링 컨테이너 생성

    ApplicationContext ctx = 
    new AnnotationConfigApplicationContext(AppConfig.class);
  2. 파라미터로 들어간 AppConfig.class를 구성 정보로 지정.

  3. AppConfig.class 내부에 @Bean 어노테이션으로 지정된 메소드를 스프링 빈 저장소에 저장

    → 빈 이름은 일반적으로 @Bean 어노테이션 지정된 메소드 명이 들어감

    public MemberRepository memberRepository() {...} memberRepository가 빈 이름이 됨

    @Bean(name = "memberRepository")로 이름 지정 가능


✔️ 빈 이름은 반드시 고유(Unique) 해야한다. 고유하지 않을 시 기존 빈을 덮어버리거나 다른 빈이 무시되는 오류가 발생한다.

✔️ 스프링은 빈 생성 단계와 의존성 주입 단계가 구분된다.


☕ 스프링 컨테이너에 등록된 빈 조회하기

  1. 모든 빈을 조회하는 경우
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

@Test
@DisplayName("모든 빈 출력하기")
void findAllBean() {
		//스프링에 등록된 모든 빈이름 가져오기
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
				//빈 이름으로 빈 객체 가져오기
        Object bean = ac.getBean(beanDefinitionName);
        System.out.println("beanDefinitionName = " + beanDefinitionName + " object = "+ bean);
    }
}
  • getBeanDefinitionNames() : 스프링에 등록된 모든 빈을 조회할 수 있는 메소드
  • getBean(빈 이름) : 빈 이름으로 빈 객체(인스턴스)를 조회할 수 있는 메소드
  1. 애플리케이션에 등록된 빈을 조회하는 경우
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean() {
		String[] beanDefinitionNames = ac.getBeanDefinitionNames();
	  for (String beanDefinitionName : beanDefinitionNames) {
			  //beanDefinition : 빈에 대한 정보
			  BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

				//빈 역할
				if(beanDefinition.getRole() == BeanDefinition.ROLE_INFRASTRUCTURE) {
					  Object bean = ac.getBean(beanDefinitionName);
					  System.out.println("name = " + beanDefinitionName + " object  = "+bean);
			  }
		}
}    
  • BeanDefinition의 Role 구분
    1. BeanDefinition.ROLE_APPLCATION : 사용자가 정의한 빈 정보
    2. BeanDefinition.ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈 정보
  1.   빈 조회하는 방법(빈 이름, 빈 타입)
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

@Test
@DisplayName("빈 이름으로 조회")
void findBeanByName() {
		// getBean(빈 이름, 타입)
    MemberService memberService = ac.getBean("memberService", MemberService.class);
		
		// getBean(빈 이름)
		MemberService memberService = ac.getBean("memberService");
		
		// getBean(타입)
		MemberService memberService = ac.getBean(MemberService.class);

		// 참고 : 구현에 의존한 코드, 구체 클래스를 접근하기 보단 추상화한 인터페이스를 접근하자!
		MemberSerivce memberService = ac.getBean(MemberServiceImpl.class);

    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
  1. 빈 조회에 실패하는 경우 : NoSuchBeanDefinitionException 에러 발생
@Test
@DisplayName("빈 이름으로 조회 x")
void findBeanByNameX() {
    //ac.getbean("xxxx",MemerService.class):
    MemberService xxxx = ac.getBean("xxxx", MemberService.class);
    assertThrows(NoSuchBeanDefinitionException.class,
            () -> ac.getBean("xxxx",MemberService.class));
}

// xxxx라는 이름으로 등록된 빈 객체가 없기 때문에 에러 발생
  1. AppConfig 하위에 동일한 타입의 Bean이 있는 경우 : NoUniqueBeanDefinition 에러 발생
AppConfig.class

@Bean
public MemberRepository membeRepository1() {...}
@Bean 
public MemberRepository memberRepository2() {...}

---- 

@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다.")
void findBeanByTypeDuplication() {
    //타입만 지정
    MemberRepository bean = ac.getBean(MemberRepository.class);
    assertThrows(NoUniqueBeanDefinitionException.class,
            () -> ac.getBean(MemberRepository.class));
}

→ 해결책 : 타입으로 조회하는 경우 같은 타입이 있다면, 이름을 지정해준다.

@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
void findBeanByTypeDuplication() {
    MemberRepository bean = ac.getBean("memberRepository1",MemberRepository.class);
}
  1. 특정 타입의 빈을 모두 조회하는 경우
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType() {
    Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
    for (String key : beansOfType.keySet()) {
        System.out.println("key = " + key + " value = "+ beansOfType.get(key));
    }
    System.out.println("beansOfType = " + beansOfType);
    assertThat(beansOfType.size()).isEqualTo(2);
}
  • getBeansOfType(빈 타입) : 빈 타입으로 지정된 모든 빈을 Map 형태로 반환해주는 메소드
  1. 부모 타입으로 빈을 조회하는 경우 : 자식 타입까지 모두 조회 된다.

Object 타입의 경우 최상위 객체 타입이기 때문에 스프링에서 설정한 모든 빈이 조회된다.

→ 부모 타입으로 조회 시 자식 타입이 둘 이상이면 NoUniqueBeanDefinitionException을 발생한다.

@Test
@DisplayName("부모 타입으로 모두 조회(Object 타입)")
void findBeanByObjectType() {
    Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
    for (String key : beansOfType.keySet()) {
        System.out.println("key = " + key+" value = "+beansOfType.get(key));
    }
}

@Test
@DisplayName("부모 타입으로 조회 시 자식이 둘 있으면, 중복 오류가 발생한다.")
void findBeanByParentTypeDuplicate() {
    Assertions.assertThrows(NoUniqueBeanDefinitionException.class,
            () -> ac.getBean(DiscountPolicy.class));
}

🤷 BeanFactory와 ApplicationContext의 차이점

image

  • BeanFactory는 최상위 스프링 컨테이너이며 빈 관리 및 조회를 하는 메소드는 모두 BeanFactory 인터페이스에 선언된 메소드이다. (getBean, getType 등)
  • ApplicationContext는 BeanFactory를 상속받는 하위 인터페이스이며, 애플리케이션 환경 설정 정보등 유용한 부가적인 기능을 가진 인터페이스를 상속 받는 다중 상속 인터페이스이다.
    1. MessageSource : 국가에 따른 언어를 다르게 제공해주는 국제화 기능 제공
    2. EnvironmentCapable : 로컬 환경, 개발 환경(테스트 서버), 운영 환경(실제 운영), 스테이징 환경(운영환경과 비슷하게 만든 환경) 구분
    3. ApplicationEventPublisher : 애플리케이션의 이벤트를 편리하게 사용하도록 구독,발행 기능 제공
    4. ResourceLoader : 파일, 클래스패스, 외부 등 여러 환경에서 리소스를 편리하게 조회하는 기능 제공 (Xml설정 시 ResourceLoader가 직접 클래스패스 탐색)

✔️ ApplicationContext = BeanFactory + 부가기능 , 그러므로 ApplicationContext를 많이 사용하고, 이를 일반적으로 스프링 컨테이너라 부른다.


🗒️ Reference

김영한님의 스프링 핵심 원리 기본편 : https://www.inflearn.com/course/스프링-핵심-원리-기본편/dashboard

profile
백엔드 개발자 지망생입니다!

0개의 댓글