hoon's bLog

Spring gradle project 스프링 핵심 원리 기본편 | Spring Container Bean 스프링 컨테이너와 스프링 빈 조회 본문

IT/Spring

Spring gradle project 스프링 핵심 원리 기본편 | Spring Container Bean 스프링 컨테이너와 스프링 빈 조회

개발한기발자 2024. 4. 5. 09:11
반응형


본 포스팅은 인프런에 있는 인터넷 강좌인,

김영한 강사님의 스프링 핵심 원리 기본편을 공부하며,

개인적으로 공부하고, 정리하는 용도로 포스팅을 해보겠다.

 

[이전 포스팅 목록]

Spring gradle project 환경설정 및 회원 가입 서비스 예제 만들기
Spring gradle project 주문/할인 도메인 설계

Spring gradle project 객체 지향 원리 적용

Spring gradle project AppConfig 리팩토링 OCP 위반 해결 및 중복 제거

Spring gradle project 좋은 객체 지향 설계 5가지 원칙 적용 및 스프링 전환

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

저번 포스팅에서 스프링 컨테이너를 알아보았는데, 이번엔 생성되는 과정을 알아보자.

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

스프링 컨테이너의 생성 과정

  • 스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 한다.(여기서는 AppConfig.class를 구성 정보로 지정)

스프링 빈 등록

  • 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록한다.
  • 빈 이름은 메서드 이름을 사용하고, 직접 부여할 수 도 있다.(주로 camelCase로!!)
    ex). @Bean(name="memberService2")
  • 빈 이름은 항상 다른 이름을 부여해야 한다. 같은 이름을 부여 시, 다른 빈이 제외되거나, 기존 빈을 덮어 버리거나 설정에 따라 오류가 발생한다.

스프링 빈 의존관계 설정

  • 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.
  • 단순히 자바 코드를 호출하는 것 같지만, 차이가 있다. 이는 뒤에 싱글톤 컨테이너에서 설명한다.
  • 스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있다.
  • 그런데 이렇게 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한 번에 처리된다.

컨테이너에 등록된 모든 빈 조회 테스트 코드

package hello.core.beanfind;

import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class ApplicationContextInfoTest {
    AnnotationConfigApplicationContext ac =
            new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("모든 빈 출력하기")
    void findAllBean(){ //Junit 5부터는 public 선언 안해도 됨!
        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);
            }
        }
    }
}

 

  • AppConfig : 스프링 설정 클래스로, 스프링 빈에 대한 정보를 포함하고 있음
  • AnnotationConfigApplicationContext를 사용하여 AppConfig 클래스를 기반으로 한 Spring Application Context를 생성한다.
  • findAllBean() : getBeanDefinitionNames() 메서드를 사용하여 컨테이너에 등록된 모든 빈의 이름을 가져오고, 각 이름에 대해 getBean() 메소드를 호출하여 빈 객체를 검색한 다음 객체를 출력한다
  • findApplicationBean(): 동일하게 getBeanDefinitionNames()로 모든 빈 이름을 가져오고, 각 이름에 대해 getBeanDefinition()으로 빈 정의를 검색한다.
    getRole() 메서드로 빈의 역할을 확인하고, BeanDefinition.ROLE_APPLICATION에 해당하는 빈만 출력!
  • 이렇게 하면 애플리케이션의 주요 로직을 구성하는 Bean만 필터링할 수 있다.

주의할 점

로그가 출력되지 않는 이슈 발생!!!!

Springboot 버전 3.1 이상인 경우 아래와 같이 출력될 수 있다.

new member = memberA
find Member = memberA
  • Spring Container와 Spring Bean에 대한 정보 없이 그냥 Bean이름과 객체만 나오게 된다.
  • 때문에 아래 경로에 logback.xml 파일을 추가해 준다!

src/main/resources/logback.xml

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp%msg%n
            </pattern>
        </encoder>
    </appender>

    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

  • logback.xml 파일을 추가하고 다시 빌드하여 실행하면 그림과 같이 로그가 정상적으로 출력될 것이다!

스프링 빈 조회 - 기본

  • 스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법은 ac.getBean(빈이름, 타입) 또는 ac.getBean(타입)과 같이 getBean 메서드를 사용하는 것이다!
  • 조회 대상 스프링 빈이 없으면 예외 발생
package hello.core.beanfind;

import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ApplicationContextBasicFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

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

    @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);
    }

    @Test
    @DisplayName("빈 이름으로 조회되지 않을때")
    void findBeanByNameX(){
//        ac.getBean("xxxxx", MemberService.class);
        assertThrows(NoSuchBeanDefinitionException.class, () -> ac.getBean("xxxxx", MemberService.class));
    }

}
  • findBeanByName() : memberService라는 이름으로 등록된 빈을 찾아, 해당 빈이 MemberServiceImpl의 인스턴스인지 검증
  • findBeanByType() : 빈의 이름을 지정하지 않고, MemberService 타입으로 빈을 조회하는 방법을 테스트한다.
    그리고 조회된 빈이 MemberServiceImpl 클래스의 인스턴스인지 확인!!
  • findBeanByName2(): memberService라는 이름과 구체적인 클래스 타입(MemberServiceImpl.class)을 사용하여 빈을 조회하는 방법으로, 특정 구현 클래스로 직접 조회하는 것이며, 조회된 객체가 MemberServiceImpl 클래스의 인스턴스인지 확인한다.
  • findBeanByNameX(): 존재하지 않는 빈 이름으로 조회를 시도했을 때 예외가 발생하는지 검증한다.
    (assertThrows 메서드를 사용하여 NoSuchBeanDefinitionException 예외가 발생하는 것을 확인한다.)

이렇게 기초적인 Spring Bean 조회를 알아봤는데,

다음 포스팅에서는 이제 이 Bean 조회를 통해 중복 및 상속 관계에 대해 알아보겠다!

 

진짜 포스팅하면서 느끼지만,

뭔가 책으로만, 인강으로만 들었을 때 잘 이해되지 않았던 부분이,

다이어그램과 실제 코딩 결과를 통해 되새기니,

훨씬 더 개념이 잘 그려진다.

아직 Spring의 개념이 완벽히 잡힐 때까지 시간이 좀 걸리겠지만,

계속해서 꾸준하게 학습하고, 코딩해서 내 것으로 만들어야겠다.

 

끝.

728x90
반응형