월루를 꿈꾸는 대학생

[핵심원리-기본] 스프링 컨테이너와 스프링빈 본문

Programing/Spring Boot

[핵심원리-기본] 스프링 컨테이너와 스프링빈

하즈시 2023. 7. 18. 21:05
728x90

스프링 컨테이너 생성

ApplicationContext applicationContext =
new
AnnotationConfigApplicationContext(AppConfig.class); // 해당 구문이 ApplicationContxt의 구현체  다형성 
  • ApplicationContext = 스프링 컨테이너 / 인터페이스 -> 다형성
  • 애노테이션 기반의 자바 설정 클래스로 만들어서 사용
  • AppConfig 방식이 애노테이션 기반 자바 설정 클래스로 컨테이너 만든것

일반적으로 ApplicationContext를 스프링 컨테이너라고 한다

스프링 컨테이너 생성 과정

  1. new AnnotationConfigApplicationContext(AppConfig.class)
  2. 스프링 컨테이너를 생성할 때는 구성 정보를 지정 키-값
  3. AppConfig.class 를 구성 정보로 지정
  • 구성정보로 지정한 클래스를 기준으로 스프링 빈 저장소에다가 객체 생성에서 집어넣음
  • 파라미터로 넘어온 클래스 정보를 사용해서 스프링 빈 정보를 등록

빈 이름

  • 빈 이름은 메서드 이름으로 사용 일반적
  • 빈 이름 직접 지정도 가능은 함
    @Bean(name="memberService2")

빈 이름은 항상 다른 이름 부여를 해야함

  • 설정 정보를 참고해서 의존관계를 주입 DI
  • 동적인 의존관계를 스프링이 알아서 만들어줌

컨테이너 등록된 모든 빈 조회

  • 제대로 빈이 등록되었는지 확인

테스트 코드 작성해서 확인

  • ac.getBeanDefinitionNames() -> 등록된 모든 빈 이름을 조회

  • ac.getBean() -> 빈의 이름으로 객체를 조회

    public class AppliocationContextTest {  
    
      AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);  
    
      @Test  
      @DisplayName("모든 빈을 출력하기")  
      void findAllBean(){  
          String[] beanDefinitionNames = ac.getBeanDefinitionNames();  
          for (String beanDefinitionName : beanDefinitionNames) {  
              Object bean = ac.getBean(beanDefinitionName);  
              System.out.println("name : " + beanDefinitionName + " object : " + bean);  
          }  
      }

출력결과

  • 스프링에서 기본으로 사용중인 빈들 + 내가 지정한 빈들 다 출력 됨
  • 이름은 빈의 이름 그리고 오브젝트는 컨테이너에 등록된 해당 객체들
@Test  
@DisplayName("애플리케이션 빈을 출력하기")  
void findApplicationBean(){  
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();  
    for (String beanDefinitionName : beanDefinitionNames) {  
        BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);  

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

내가 사용한 빈들만 나오도록 설정

출력결과


스프링 빈 조회 - 기본

  • ac.getBean(빈이름 , 타입)
  • ac.getBean(타입)
빈이름으로 조회
@Test  
@DisplayName("빈 이름으로 조회")  
void findBeanByName(){  
    MemberService memberService = ac.getBean("memberService", MemberService.class);  
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);  
    // 해당 객체는 빈에 등록된 객체이고 해당 객체의 인스턴스는 MemberServiceImpl 와 동일하다  
}
타입으로만 조회
  • 해당 타입의 조회는 인터페이스를 넣어서 getBean실행 -> 다형성

    @Test  
    @DisplayName("빈 이름없아 타입으로 조회")  
    void findBeanByType(){  
      MemberService memberService = ac.getBean( MemberService.class); // 인터페이스로 조화  
      assertThat(memberService).isInstanceOf(MemberServiceImpl.class);  
    }
  • 다음은 구현체로 조회 -> 유연성이 떨어짐

    @Test  
    @DisplayName("구체 타입으로 조회")  
    void findBeanByName2(){  
      MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class); //구현체로 조회  
      assertThat(memberService).isInstanceOf(MemberServiceImpl.class);  
    }
만약 이름이 존재하지 않는다면 ?
  • 없는 이름으로 빈 조회시 NoSuchBeanDefinitionException 에러 발생
  • 이런 테스트는 해당 에러를 나오도록 테스트해서 검증
  • org.junit.jupiter.api.Assertions.assertThrows 사용

위의 에러가 나오니까 이를 테스트

@Test  
@DisplayName("빈 이름으로 조회X")  
void findBeanByNameX(){  
    assertThrows(NoSuchBeanDefinitionException.class,  
            ()->ac.getBean("xxxx", MemberServiceImpl.class));  
}

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

  • 같은 타입이 여러개 있으면 뭘 줘야할지 판단 불가 - 오류 발생
  • 빈 이름으로 조회하자
  • ac.getBeanOfType() 사용시 해당 타입의 모든 빈 조회가능

예외가 터진다
NoUniqueBeanDefinitionException

일단 테스트용 구성파일을 클래스안에다가 구현

@Configuration // 클래스 안에 static으로 클래스 정의한 건 스코프를 해당 클래스 내로 한정한다는 소리  
static class SameBeanConfig{  
    @Bean  
    public MemberRepository memberRepository(){  
        return new MemoryMemberRepository();  
    }  
    @Bean  
    public MemberRepository memberRepository2(){  
        return new MemoryMemberRepository();  
    }  
}
  • 타입이 둘 이상 있는 경우 위에 같은 에러가 나오는데 이를 핸들링한 테스트 코드

    @Test  
    @DisplayName("타입으로 조회시 같은 타입이 둘 잇아있으면 중복오류가 발생한다")  
    void findBeanByTypeDuplicate(){  
      // 타입만 지정함  
      //MemberRepository bean = ac.getBean(MemberRepository.class);  
      assertThrows(NoUniqueBeanDefinitionException.class, ()-> ac.getBean(MemberRepository.class));  
    }
  • 빈 이름을 통해서 조회한다면 에러 없이 조회 가능

    @Test  
    @DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면 빈 이름을 지정하면 된다 ")  
    void findBeanByName(){  
      MemberRepository bean = ac.getBean("memberRepository", MemberRepository.class);  
     assertThat(bean).isInstanceOf(MemberRepository.class);  
    }
  • 그냥 특정 타입들을 조회하는 경우

    • ac.getBeansOfType(MemberRepository.class) 를 사용하여 해당 인터페이스를 기준으로 빈 조회하기

스프링 빈 조회 - 상속관계

  • 부모타입으로 조회시 자식까지 같이 조회됨!!

  • Object로 조회하면 모든 스프링 빈 조회

  • 부모타입 조회할 때 자식이 2개 이상이면 또 오류남

  • NoUniqueBeanDefinitionException 에러

      @Test  
      @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다")  
      void findBeanByParentTypeDuplicate() {  
    //DiscountPolicy bean = ac.getBean(DiscountPolicy.class);  
          assertThrows(NoUniqueBeanDefinitionException.class, () ->  
                  ac.getBean(DiscountPolicy.class));  
      }
  • 그냥 조회할 때마다 빈이 2개 이상이면 에러가 나는 듯 그런 경우 이름으로 조회하면 오케

보통 빈을 조회하는 경우는 극히 드물다


BeanFactory와 ApplicationContext

BeanFactory

  • 스프링 컨테이너 최상위 인터페이스
  • 스프링 빈 관리 및 조회 getBean() 같은거
  • 대부분 썼던 기능은 BeanFactory꺼

ApplicaionCOntext

  • BeanFactory 상속받아서 제공

  • 그 외 부가 기능을 담당

  • 메시지소스를 활용한 국제화 기능

    • 예를 들어서 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력
  • 환경변수

    • 로컬, 개발, 운영등을 구분해서 처리
  • 애플리케이션 이벤트

    • 이벤트를 발행하고 구독하는 모델을 편리하게 지원
  • 편리한 리소스 조회

    • 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회

BeanFactory 잘 안 씀 ApplicationContext = 스프링 컨테이너


다양한 설정 형식 지원 - 자바 코드 , XML

  • 스프링 컨테이너는 다양한 형식의 설정정보를 지원!
    애노테이션 기반 자바 코드
  • new AnnotationConfigApplicationContext(AppConfig.class)
  • 지금까지 썼던 거
XML 설정 사용
  • 요즘 안 씀
  • xml 사용하면 컴파일 없이 빈 설정 정보 변경
  • GenericXmlApplicationContex를 써서 xml설정파일 넘겨주면 오케

스프링 빈 설정 메타 정보 - BeanDefinition

  • 스프링은 어떻게 xml, 자바 코드를 다 지우너할 수 있을까 ?? -> 빈정보 자체를 추상화해서 그런거임 BeanDefinition 로 추상화해서 가능
  • 즉 역할과 구현으로 개념적 나눈것
    • 자바 코드나 xml이나 설정 정보들을 BeanDefinition으로 만들고 넘겨주니까 스프링은 이게 xml이든 자바코드든 신경 안써도 되는 거임
  • BeanDefinition = 빈 설정 메타정보
    • ==@Bean== , ==< bean >==이 각각 하나의 메타 정보로 생성 됨

스프링 컨테이너는 메타정보를 가지고 스프링 빈을 생성한다


BeanDefination 이 인터페이스임
스프링 컨테이너는 추상화에만 의존하는 것

==AnnotationConfigApplicationContext== 는 ==AnnotatedBeanDefinitionReader== 를 사용해서
AppConfig.class 를 읽고 BeanDefinition 을 생성

즉 어떤거로 해동 BeanDefition을 만들어 버리니까 멀로 만들어도 괜찮

public class BeanDefinitionTest {  
// 여기서 추상화 안하고 AnnotationConfigApplicationContext 쓴 이유는 ac.getBeanDefinitionNames 이게 정의가 되어있지 않음
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);  

    @Test  
    @DisplayName("빈 설정 메타 정보 확인")  
    void findApplicationBean(){  
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();  
        for (String beanDefinitionName : beanDefinitionNames) {  
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);  
            if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){  
                System.out.println("beanDefinationName = " + beanDefinitionName +  
                        "beandefination : " + beanDefinition);  
            }  
        }  

    }  
}

다음과 같이 해당 AnnotationConfigApplicationContext을 가지고 온 다음에 어떤 BeanDefinition이 있는지 확인하는 테스트

출력 결과

여기 있는 출력결과에서 BeanDefinition의 메타정보를 확인 가능
이 메타정보를 가지고 인스턴스 생성

실무에서 BeanDefinition을 정의 사용하지는 않는다
다만 추상화해서 사용하는 것만 이해하는거로 오케

출처
https://inf.run/wFfL

728x90