Super Kawaii Cute Cat Kaoani
본문 바로가기
⚙️ Back-end/SpringBoot

[SpringBoot] 스프링 컨테이너와 스프링 빈

by wonee1 2025. 9. 10.
728x90

🖊️ 본 포스트는 인프런 김영한 스프링 기본편을 보고 정리한 내용입니다. 

 

 

 

스프링 핵심 원리 - 기본편| 김영한 - 인프런 강의

현재 평점 5.0점 수강생 48,207명인 강의를 만나보세요. 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다. 스프링 기본 기능, 스프

www.inflearn.com

 

 

 

 

 

 

스프링 컨테이너 생성


 

 //스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

 

 

ApplicationContext 

 

  • 스프링 컨테이너라고 하며 인터페이스다.
  • 스프링 컨테이너는 xml을 기반으로 만들 수 있고 어노테이션 기반의 자바 설정 클래스로 만들 수 있다.
  • 스프링 컨테이너는 @Component, @Configuration, @Bean 같은 애노테이션이 붙은 클래스를 읽어서, 필요한 객체들(빈, Bean)을 생성하고 관리하며, 객체들 간의 의존관계를 자동으로 연결해주는 역할을 한다. 

 

직전 실습에서 AppConfig.를 사용했던 방식이 어노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 만든 것 

 

 

 

 

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

 

1. 스프링 컨테이너 생성 

 

'

 

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

 

2. 스프링 빈 등록 

 

 

  • 스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용해서 스프링 빈을 등록한다 
  • @Bean 어노테이션이 붙은 메서드는 스프링 빈을 등록하는 메서드로 인식한다. 
  • 여기서 @x01, @x02는 단순히 객체의 메모리 위치(주소값)를 표현한 것

 

 

 

⚠️주의

 

빈 이름은 항상 다른 이름을 부여해야한다
동일한 이름의 빈이 등록되면 기존 빈을 덮어쓰거나(BeanDefinitionOverride), 충돌 예외가 발생할 수 있다.

 

 

 

 

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

 

 

 

 

  • memberService를 생성할 때 memberRepository() 필요
  • orderService를 생성할 때 memberRepository(), discountPolicy() 필요

 

 

 

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

 

 

 

 

 

  • 이제 스프링 컨테이너가 AppConfig 설정 정보를 이용해 실제 의존성을 주입(DI, Dependency Injection)한다
  • 각 서비스가 필요로 하는 객체를 스프링 컨테이너가 찾아서 자동으로 연결해 준다. 

 

 

 

 

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


 

 

 

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;
import static org.assertj.core.api.Assertions.assertThat;

class ApplicationContextInfoTest {
    AnnotationConfigApplicationContext ac = new
            AnnotationConfigApplicationContext(AppConfig.class); // 1. 컨테이너 생성 
    @Test
    @DisplayName("모든 빈 출력하기") 
    void findAllBean() {  //2. 모든 빈 출력하기
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println("name=" + beanDefinitionName + " object=" +
                    bean);
        }
    }
    @Test
    @DisplayName("애플리케이션 빈 출력하기")
    void findApplicationBean() { //3. 애플리케이션 빈 출력하기 
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition =
                    ac.getBeanDefinition(beanDefinitionName);

            //Role ROLE_APPLICATION: 직접 등록한 애플리케이션 빈
            //Role ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println("name=" + beanDefinitionName + " object=" + bean);



            }
        }
    }
}

 

 

모든 빈출력하기  

 

  • ac.getBeanDefinitionNames() → 현재 스프링 컨테이너에 등록된 모든 빈 이름을 가져온다. 
  • ac.getBean(beanDefinitionName) → 해당 빈 이름으로 실제 빈 객체를 가져온다.
  • 따라서 실행 결과는 내가 등록한 빈 + 스프링 내부에서 등록한 빈까지 모두 출력된다

 

 

애플리케이션 빈 출력하기

 

  • BeanDefinition 객체를 통해 빈의 역할(Role)을 확인한다.
  • 스프링 내부에서 사용하는 빈은 getRole() 값으로 구분할 수 있다 
    • ROLE_APPLICATION → 개발자가 직접 등록한 빈 (AppConfig에서 만든 것들)
    • ROLE_INFRASTRUCTURE → 스프링 내부 동작을 위해 등록된 빈 (예: 자동 프록시 생성기 등)
  • 따라서 실행결과는 내가 등록한 빈만 출력된다

 

 

실행결과 

더보기
Starting Gradle Daemon...
Gradle Daemon started in 1 s 398 ms
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
name=appConfig object=hello.core.AppConfig$$SpringCGLIB$$0@70e659aa
name=memberService object=hello.core.member.MemberServiceImpl@2ef8a8c3
name=orderService object=hello.core.order.OrderServiceImpl@24f43aa3
name=memberRepository object=hello.core.member.MemoryMemberRepository@63fd4873
name=discountPolicy object=hello.core.discount.RateDiscountPolicy@1e11bc55
name=org.springframework.context.annotation.internalConfigurationAnnotationProcessor object=org.springframework.context.annotation.ConfigurationClassPostProcessor@77b21474
name=org.springframework.context.annotation.internalAutowiredAnnotationProcessor object=org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@52d10fb8
name=org.springframework.context.annotation.internalCommonAnnotationProcessor object=org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@41c07648
name=org.springframework.context.event.internalEventListenerProcessor object=org.springframework.context.event.EventListenerMethodProcessor@1fe8d51b
name=org.springframework.context.event.internalEventListenerFactory object=org.springframework.context.event.DefaultEventListenerFactory@781e7326
name=appConfig object=hello.core.AppConfig$$SpringCGLIB$$0@22680f52
name=memberService object=hello.core.member.MemberServiceImpl@60d84f61
name=orderService object=hello.core.order.OrderServiceImpl@39c11e6c
name=memberRepository object=hello.core.member.MemoryMemberRepository@324dcd31
name=discountPolicy object=hello.core.discount.RateDiscountPolicy@503d56b5
> Task :test
BUILD SUCCESSFUL in 10s

 

 

 

스프링 빈 조회 - 기본


 

스프링 빈 조회 기본 방법 

 

스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법

  • ac.getBean(빈이름, 타입)
  • ac.getBean(타입)

 

 

 

1. 빈 이름 + 타입으로 조회

MemberService memberService = ac.getBean("memberService", MemberService.class);

 

  • "memberService" → 스프링 빈 이름 (메서드 이름이 기본적으로 빈 이름이 됨)
  • MemberService.class → 빈의 타입
  • 장점: 동일 타입의 빈이 여러 개 있어도, 이름으로 구분 가능
  • 단점: 이름이 틀리면 NoSuchBeanDefinitionException 발생

 

 

 

2. 타입으로만조회  

 

MemberService memberService = ac.getBean(MemberService.class);

 

  • 빈 이름을 생략하고 타입만으로 조회 가능
  • 같은 타입의 빈이 하나만 있을 때 사용 가능
  • 같은 타입의 빈이 둘 이상 있으면 NoUniqueBeanDefinitionException 발생

 

 

 

3. 조회 대상 스프링 빈이 없으면? 

 

 

   다음과 같이 예외가 발생한다 

NoSuchBeanDefinitionException: No bean named 'xxxxx' available

 

 

 

 

 

 

 

실습 예제 코드 

package hello.core.beanfind;
import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.junit.jupiter.api.Assertions;
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.*;

class ApplicationContextBasicFindTest {

    AnnotationConfigApplicationContext ac=new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("빈 이름으로 조회")
    void findBeanByName(){
        MemberService memberService=ac.getBean("memberService",MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class); //memberService 객체가 MemberServiceImpl 클래스의 인스턴스인지 확인
    }



    @Test
    @DisplayName("이름 없이 타입만으로 조회")
    void findBeanByType(){
        MemberService memberService=ac.getBean("memberService",MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

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

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

}

 

 

 

ac.getBean(...) 

  • 스프링 컨테이너에서 빈을 꺼내오는 메서드

 

 

assertThat(...)

  • AssertJ라는 테스트 라이브러리에서 제공하는 메서드.
  • JUnit과 함께 자주 쓰임.

 

.isInstanceOf(MemberServiceImpl.class)

  • memberService 객체가 MemberServiceImpl 클래스의 인스턴스인지 확인.
  • (즉, memberService가 정말 구현체 MemberServiceImpl로 생성되었는지 검증하는 것)

 

테스트 메서드  조회 기준  반환 타입 조회 방식
findBeanByName "memberService" + MemberService.class MemberService 인터페이스 타입 조회
findBeanByType MemberService.class MemberService 인터페이스 타입 조회
findBeanByName2 "memberService" + MemberServiceImpl.class MemberServiceImpl 구체 타입 조회

 

 

참고

구체 타입으로 조회하면 변경시 유연성이 떨어진다.

 

 

 

 

 

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


 

타입으로 조회 시 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생 -> 이때 빈 이름을 지정하면된다 

 

 

ac.getBeansOfType()을 사용하면 해당 타입의 모든 빈을 조회할 수 있다 

 

 

 

public class ApplicationContextSameBeanFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);

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

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

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

    @Configuration
    static class SameBeanConfig {
        
        @Bean
        public MemberRepository memberRepository1() {
            return new MemoryMemberRepository();
        }
        
        @Bean
        public MemberRepository memberRepository2() {
            return new MemoryMemberRepository();   
        }


    }

}

 

 

Map<String, MemberRepository>

  • key → 빈 이름 ("memberRepository1", "memberRepository2" 등)
  • value → 빈 객체 (MemoryMemberRepository, JdbcMemberRepository 같은 구현체 인스턴스)

 

@Configuration  스프링 컨테이너에 빈을 등록하기 위한 설정 클래스임을 표시하는 어노테이션

 

 

 

실행결과 

더보기
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
key = memberRepository1 value = hello.core.member.MemoryMemberRepository@41c07648
key = memberRepository2 value = hello.core.member.MemoryMemberRepository@2d35442b
beansOfType = {memberRepository1=hello.core.member.MemoryMemberRepository@41c07648, memberRepository2=hello.core.member.MemoryMemberRepository@2d35442b}
> Task :test
BUILD SUCCESSFUL in 3s
4 actionable tasks: 2 executed, 2 up-to-date
오후 8:54:12: Execution finished ':test --tests "hello.core.beanfind.ApplicationContextSameBeanFindTest"'.

 

 

 

스프링 빈 조회 - 상속 관계 


 

 

스프링 빈은 자바 객체니까, 상속 구조를 그대로 따른다. 따라서 부모 타입으로 조회하면, 그 부모를 상속하거나 구현한 자식 타입 빈들도 함께 조회된다.  모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다.

 

 

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

 

ac.getBeansOfType(DiscountPolicy.class)

  • DiscountPolicy는 인터페이스 (부모 타입).
  • 컨테이너에 등록된 DiscountPolicy를 구현한 모든 빈을 Map으로 반환.

 assertThat(beansOfType.size()).isEqualTo(2);

  • 컨테이너에 DiscountPolicy 구현체 빈이 2개 등록되어 있다고 가정하는 테스트.
  • 보통 AppConfig 같은 설정 클래스에 이렇게 등록해둔다. 

 

정리 

 

  • 부모 타입(DiscountPolicy)으로 조회하면, 자식 타입(RateDiscountPolicy, FixDiscountPolicy) 빈이 모두 조회된다.
  • ac.getBeansOfType(부모타입) → 반환: Map<String, 부모타입>
  • 이 방식으로 다형성을 활용한 빈 조회가 가능하다.

 

 

 

 

BeanFactory와 ApplicationContext


 

 

 

 

BeanFactory

 

1. 스프링 컨테이너의 최상위 인터페이스다.

2. 스프링 을 관리하고 조회하는 역할을 담당한다.

 

 

  • 빈 관리 (Bean Lifecycle 관리)
    • @Bean이나 @Component로 정의된 객체를 생성하고, 컨테이너에 담아 관리
    • 싱글톤 보장: 같은 빈 요청이 오면 기존 객체를 반환
    • 의존관계 주입(DI): 필요한 빈을 찾아서 다른 빈에 주입
  • 빈 조회 (Lookup)
    • getBean(String name)
    • getBean(Class<T> requiredType)
      → 이름 또는 타입으로 빈을 검색해 반환

 

 

3. IoC(제어의 역전) 컨테이너 기능을 제공하는 핵심 뼈대

4. 실무에서는 잘 안 씀. 보통 ApplicationContext를 쓴다, 

 

 

 

 

 

ApplicationContext 

 

 

1. 우리가 쓰는 ApplicationContext도 결국 BeanFactory를 상속받아 구현된 것이다. 

 

2. 애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고, 수 많은 부가기능이 필요한데 ApplicationContext가 이를 제공한다. 

 

 

 

 

ApplicatonContext가 제공하는 부가기능 


 

1. 메시지 소스 (MessageSource) → 국제화(i18n) 

 

  • 여러 언어 지원
  • 같은 코드라도 사용자의 Locale(국가/언어 설정)에 따라 다른 메시지 출력

 

String message = context.getMessage("hello", null, Locale.KOREA); 
// "안녕하세요" 출력

String message = context.getMessage("hello", null, Locale.ENGLISH); 
// "Hello" 출력

 

 

 

 

 

2. 환경 변수 (EnvironmentCapable)

 

  • 로컬 / 개발 / 운영 환경 구분
  • 운영 환경에 따라 다른 설정값을 주입 가능

Environment env = context.getEnvironment();
System.out.println(env.getProperty("spring.datasource.url"));

 

-> application-local.properties, application-dev.properties 같은 프로필 기반 설정도 가능

 

 

 

 

3. 애플리케이션 이벤트 (ApplicationEventPublisher)

 

  • 이벤트 발행 / 구독 모델 지원
  • 옵저버 패턴을 스프링이 직접 구현해줌

 

// 이벤트 발행
context.publishEvent(new MyCustomEvent(this, "이벤트 발생"));

// 이벤트 리스너
@Component
public class MyEventListener {
    @EventListener
    public void handle(MyCustomEvent event) {
        System.out.println("이벤트 수신: " + event.getMessage());
    }
}

 

 

 

 

4. 리소스 로딩 (ResourceLoader)

 

  • 다양한 리소스 접근을 일관되게 지원
  • 파일 시스템, 클래스패스, 외부 URL 등
Resource resource = context.getResource("classpath:application.properties");
System.out.println(resource.getFilename()); // application.properties

 

 

 

 

 

 

 

 

 

 

 

 

 

728x90