[Spring] 스프링 핵심 원리 이해

객체지향설계와 스프링

  • 객체 지향 프로그래밍
    • 컴퓨터 프로그램을 “객체들의 모임”으로 파악하고자 하는 것
    • 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.
  • 스프링은 다형성을 극대화한 것
    • 역할(인터페이스)와 구현(인터페이스를 구현한 클래스)을 분리한다
    • 클라이언트는?
      • 대상의 역할(인터페이스)만 알면 되고, 구현 대상의 내부 구조를 몰라도 된다.
      • 구현 대상 자체나 구현 대상의 내부구조가 바뀌어도 영향 X
    • 즉, 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있어야 한다

좋은 객체 지향 설계의 5가지 원칙 (SOLID)

  1. SRP 단일 책임 원칙
    • 한 클래스는 하나의 책임만 가져야 한다.
  2. OCP 개방-폐쇄 원칙
    • 소프트웨어 요소는 확장에는 열려있으나, 변경에는 닫혀있어야 한다.
  3. LSP 리스코프 치환 원칙
    • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  4. ISP 인터페이스 분리 원칙
    • 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
    • 인터페이스가 명확해지고, 대체 가능성이 높아진다.
  5. DIP 의존관계 역전의 원칙
    • 구현 클래스에 의존하지 말고 인터페이스(역할)에 의존한다.
    • 유연하게 구현체를 변경할 수 있다.

스프링 컨테이너와 스프링 빈

  • ApplicationContext : 스프링 컨테이너
    • @Configuration이 붙은 AppConfig를 구성 정보로 사용한다.
    • AppConfig에서 @Bean이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다.
  • 스프링 빈 : 스프링 컨테이너에 등록된 객체
    • @Bean이 붙은 메서드의 명을 스프링 빈의 이름으로 사용한다. (빈 이름을 직접 부여도 가능)
    • 빈 이름은 항상 다른 이름을 부여해야 한다.
  • 스프링 컨테이너는 applicationContext.getBean() 메서드를 이용하여 스프링 빈을 찾을 수 있다.
  • 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용한다.
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
  • ApplicationContext : 스프링 컨테이너이자 인터페이스
  • 스프링 컨테이너는 XML 기반 or 애노테이션 기반의 자바 설정 클래스로 만들 수 있다.
  • new AnnotationConfigApplicationContext(AppConfig.class) : 인터페이스의 구현체
  • 스프링 컨테이너 생성 시 구성 정보를 지정해야 한다. (AppConfig.class : 구성 정보)
  • 스프링 컨테이너는 설정 정보를 참고하여 의존 관계을 주입한다.


스프링 빈 조회

  • 모든 빈 출력하기
    • ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
    • ac.getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회한다.
      @Test
      @DisplayName("모든 빈 출력하기") void findAllBean() {
            String[] beanDefinitionNames = ac.getBeanDefinitionNames();
            for (String beanDefinitionName : beanDefinitionNames) {
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println("name=" + beanDefinitionName + " object=" + bean);
      }
      }
      
  • 애플리케이션 빈 출력하기
    • getRole() : 스프링이 사용자가 정의한 빈인지, 내부에서 사용하는 빈인지를 구분한다.
    • ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
    • ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용한 빈
      @Test
      @DisplayName("애플리케이션 빈 출력하기")
      void findApplicationBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
      
            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println("name = " + beanDefinitionName + " object = " + bean);
            }
        }
      }
      


  • 스프링 빈 조회(기본)
    • ac.getBean(빈이름, 타입)
      @Test
      @DisplayName("빈 이름으로 조회")
      void findBeanByName() {
          MemberService memberService = ac.getBean("memberService", MemberService.class);
          assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
      }
      
    • ac.getBean(타입)
      @Test
      @DisplayName("이름 없이 타입으로만 조회")
      void findBeanByType() {
          MemberService memberService = ac.getBean(MemberService.class);
          assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
      }
      
    • 조회 대상 스프링 빈이 없으면 예외 발생
      • NoSuchBeanDefinitionException: No bean named 'xxxxx' available
      • ac.getBean("xxxxx", MemberService.class)를 실행하면 NoSuchBeanDefinitionException.class 예외가 실행되어야 한다.
        @Test
        @DisplayName("빈 이름으로 조회 X")
        void findBeanByNameX() {
        //  ac.getBean("xxxx", MemberService.class);
          assertThrows(NoSuchBeanDefinitionException.class,
                  () -> ac.getBean("xxxxx", MemberService.class));
        }
        


  • 스프링 빈 조회 - 동일한 타입이 둘 이상일 경우
    • 타입으로 조회 시 동일한 타입이 둘 이상일 경우 오류가 발생한다 (NoUniqueBeanDefinitionException)
    • 해결 방법 : 빈 이름을 저장한다
      @Test
      @DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 저장하면 된다")
      void findBeanByName() {
          MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
          assertThat(memberRepository).isInstanceOf(MemberRepository.class);
      }
      
    • ac.getBeansOfType() : 해당 타입의 빈을 모두 조회한다.
      @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); // 해당 타입이 2개 등록되어있으니 2개가 나와야함을 검증한다
      }
      


  • 스프링 빈 조회 - 상속 관계의 경우
    • 모든 자바의 최고 부모인 Object 타입으로 조회하면 모든 스프링 빈을 조회할 수 있다.
    • 부모 타입으로 조회 시 자식이 둘 이상이 있으면, NoUniqueBeanDefinitionException 오류가 발생한다.
    • 해결 방법 : 빈 이름을 지정한다.
      @Test
      @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면 빈 이름을 지정하면 된다")
      void findBeanByParentTypeBeanName() {
          DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
          assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
      }
      
    • 부모 타입으로 모두 조회
      • ac.getBeansOfType() 사용
      @Test
      @DisplayName("부모 타입으로 모두 조회하기")
      void findAllBeanByParentType() {
          Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
          assertThat(beansOfType.size()).isEqualTo(2); // 하위에 자식이 2개 있다. 여기서는 자식이 2개인지 검증한다.
          for (String key : beansOfType.keySet()) {
              System.out.println("key = " + key + " value = " + beansOfType.get(key));
          }
      }
      


BeanFactory vs. ApplicationContext (스프링 컨테이너)

  • BeanFactory
    • 스프링 컨테이너의 최상위 인터페이스
    • 스프링 빈을 관리하고 조회하는 역할
    • getBean()을 제공
  • ApplicationContext
    • BeanFactory 기능을 모두 상속받는다
    • 빈 관리 기능 + 편리한 부가 기능을 제공한다.
      • 메시지 소스를 활용한 국제화 기능
      • 환경 변수
      • 애플리케이션 이벤트
      • 편리한 리소스 조회
  • BeanFactory를 직접 사용하지 않고, 부가기능이 포함된 ApplicationContext를 사용한다.

BeanDefinition

  • BeanDefinition : 스프링 빈 설정 메타 정보
    • 스프링의 다양한 형태의 설정 정보를 BeanDefinition으로 추상화해서 사용한다.
    • @Bean, <bean> 당 각각 하나씩 메타 정보가 생성된다.
  • 스프링 컨테이너는 메타정보를 기반으로 스프링 빈을 생성한다.
image
  • AnnotationConfigApplicationContextAnnotatedBeanDefinitionReader를 사용해서 AppConfig.class를 읽고 BeanDefinition을 생성한다.




김영한의 스프링 핵심 원리 - 기본편을 참고하였습니다

© 2021. All rights reserved.

yaejinkong의 블로그