[Spring-기본] 스프링 컨테이너와 스프링 빈 - 1

DANI·2023년 11월 23일

Spring[김영한T]

목록 보기
20/31
post-thumbnail

🔍 스프링 컨테이너 생성

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

  • ApplicationContext : 스프링 컨테이너(인터페이스)
  • new AnnotationConfigApplicationContext(AppConfig.class); : 구현체
    👉 스프링 컨테이너는 XML 기반으로 만들 수 있고, 애노테이션 기반의 자바 설정클래스로 만들 수도 있다.


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

📑 1. 스프링 컨테이너 생성

💾 AppConfig 파일

package hello.core;


import hello.core.Discount.DiscountPolicy;
import hello.core.Discount.RateDiscountPolicy;
import hello.core.Order.OrderService;
import hello.core.Order.OrderServiceImpl;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;


public class AppConfig {

    
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }

    
    public DiscountPolicy discountPolicy(){
        return new RateDiscountPolicy();
    }

    
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }
}
  • new AnnotationConfigApplicationContext(AppConfig.class)
    👉 스프링 빈 저장소 생성


📑 2. 스프링 빈 등록

💾 AppConfig 파일

package hello.core;


import hello.core.Discount.DiscountPolicy;
import hello.core.Discount.RateDiscountPolicy;
import hello.core.Order.OrderService;
import hello.core.Order.OrderServiceImpl;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 애너테이션 추가
@Configuration
public class AppConfig {

    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }

    @Bean
    public DiscountPolicy discountPolicy(){
        return new RateDiscountPolicy();
    }

    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }
}
  • 빈 이름은 메서드명을 사용한다
  • @Bean(name = "memberService")로 설정도 가능


📑 3. 스프링 빈 의존관계 설정





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

💾 ApplicationBeanInfoTest 테스트 파일 생성

package hello.core.beanfind;

import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

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

    @Test
    @DisplayName("모든 빈 출력하기")
    void findAll(){
        // 스프링에 등록된 모든 빈이름 조회
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for(String beans : beanDefinitionNames){
            Object bean = ac.getBean(beans);
            System.out.println("name : " + beans + "Object" + bean);
        }
    }
}

🔵 실행 결과


내가 등록한 빈과 스프링 내부에서 사용하는 빈이 모두 출력된다.



💻 애플리케이션 빈 조회

💾 ApplicationBeanInfoTest 테스트 파일 생성

package hello.core.beanfind;

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


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

   

    @Test
    @DisplayName("애플리케이션 빈 조회")
    void AppBeanFind(){
        // 모든 빈이름 조회
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beans : beanDefinitionNames){
            BeanDefinition beanDefinition = ac.getBeanDefinition(beans);
            
            // 스프링 내부에서 사용하는 빈은 .getRole() 로 구분할 수 있다.
            // .ROLE_APPLICATION : 사용자 정의 빈
            // .ROLE_INFRANSTRUCTURE : 스프링 내부에서 사용하는 빈
            if(beanDefinition.getRole()== BeanDefinition.ROLE_APPLICATION){
                Object bean = ac.getBean(beans);
                System.out.println("name : " + beans + "Object" + bean);
            }
        }
    }
}

🔵 실행 결과


내가 등록한 빈만 조회된다.




💻 스프링 빈 조회 - 기본

  1. 빈 이름으로 조회
  2. 빈 타입으로 조회
  3. 구체화로 조회
  4. 빈 이름으로 조회 안되는 경우

💾 ApplicationBasucFindTest 테스트 파일 생성

package hello.core.beanfind;

import hello.core.AppConfig;

import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.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;

public class ApplicationBasucFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

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

    @Test
    @DisplayName("빈 타입으로 조회")
    void findtype(){
        MemberService memberService1 = ac.getBean(MemberService.class);
        Assertions.assertThat(memberService1).isInstanceOf(MemberServiceImpl.class);
    }

    // 구체화로 조회하는 것은 좋지 않은 예제이다
    // 유연성이 떨어짐
    @Test
    @DisplayName("구체화로 조회")
    void findtype2(){
        MemberServiceImpl memberService1 = ac.getBean(MemberServiceImpl.class);
        Assertions.assertThat(memberService1).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName("빈 이름으로 조회 안되는 경우")
    void notfindname(){
        // MemberService xxxx = ac.getBean("XXXX", MemberService.class);
        org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class, ()->ac.getBean("XXXX", MemberService.class));
    }
}



💻 스프링 빈 조회 - 중복된 타입이 있는 경우

  1. 중복된 타입으로 조회하기
  2. 특정 타입을 모두 조회하기

💾 ApplicationBeanDupTest 테스트 파일 생성

package hello.core.beanfind;

import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

public class ApplicationBeanDupTest {

    @Configuration
    static class AppConfigEX{

        @Bean
        public MemberRepository memberRepository1(){
            return new MemoryMemberRepository();
        }

        @Bean
        public MemberRepository memberRepository2(){
            return new MemoryMemberRepository();
        }
    }

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfigEX.class);

    @Test
    @DisplayName("중복된 타입으로 조회하기")
    void dupFindAll(){
        MemberRepository bean = ac.getBean(MemberRepository.class);

    }
}

🔵 실행 결과

예외가 발생한다! 코드를 수정해보자


@Test
@DisplayName("중복된 타입으로 조회하기")
void dupFindAll(){
   Assertions.assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(MemberRepository.class));
}

🔵 실행 결과




💻 스프링 빈 조회 - 중복된 타입이 있는 경우(특정 타입 모두 조회하기)

💾 ApplicationBeanDupTest 테스트 파일

package hello.core.beanfind;

import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

public class ApplicationBeanDupTest {

    @Configuration
    static class AppConfigEX{

        @Bean
        public MemberRepository memberRepository1(){
            return new MemoryMemberRepository();
        }

        @Bean
        public MemberRepository memberRepository2(){
            return new MemoryMemberRepository();
        }
    }

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfigEX.class);

    @Test
    @DisplayName("중복된 타입으로 조회하기")
    void dupFindAll(){
        Assertions.assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(MemberRepository.class));
    }

    @Test
    @DisplayName("특정 타입을 모두 조회하기")
    void typeAll(){
        // .getBeansOfType 특정 타입을 모두 조회
        Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
        for(String key : beansOfType.keySet()){
            System.out.println("key : " + key + " value : " + beansOfType.get(key) );
        }
        org.assertj.core.api.Assertions.assertThat(beansOfType.size()).isEqualTo(2);
    }
}

🔵 실행 결과




💻 스프링 빈 조회 - 상속 관계

  1. 부모 타입으로 조회 시, 자식이 둘 이상 있으면?
  2. 부모 타입으로 조회 시, 자식이 둘 이상 있으면 이름을 지정해주자!
  3. 특정 하위 타입으로 조회
  4. 부모 타입으로 모두 조회하기
  5. 부모(Object) 타입으로 모두 조회하기

💾 ApplicationExtendsFindTest 테스트 파일

package hello.core.beanfind;

import hello.core.AppConfig;
import hello.core.Discount.DiscountPolicy;
import hello.core.Discount.FixedDiscountPolicy;
import hello.core.Discount.RateDiscountPolicy;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

public class ApplicationExtendsFindTest {

    @Configuration
    static class AppConfigEx{

        @Bean
        public DiscountPolicy rateDiscountPolicy(){
            return new RateDiscountPolicy();
        }

        @Bean
        public DiscountPolicy fixDiscountPolicy(){
            return new FixedDiscountPolicy();
        }
    }

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfigEx.class);

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

    @Test
    @DisplayName("부모 타입으로 조회 시, 자식이 둘 이상 있으면 이름을 지정해주자!")
    void findParentType1(){
        DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
        DiscountPolicy fixDiscountPolicy = ac.getBean("fixDiscountPolicy", DiscountPolicy.class);
        org.assertj.core.api.Assertions.assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
        org.assertj.core.api.Assertions.assertThat(fixDiscountPolicy).isInstanceOf(FixedDiscountPolicy.class);
    }

    @Test
    @DisplayName("특정 하위 타입으로 조회")
    void findchildType(){
        RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
        org.assertj.core.api.Assertions.assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
    }

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

    @Test
    @DisplayName("부모(Object) 타입으로 모두 조회하기")
    void findParentAllobj(){
        Map<String, Object> beansOfType1 = ac.getBeansOfType(Object.class);
        for(String key : beansOfType1.keySet()){
            System.out.println("key : " + key + " value : " + beansOfType1.get(key));
        }
        // org.assertj.core.api.Assertions.assertThat(beansOfType1.size()).isEqualTo(16);
    }
}

🔵 실행 결과

Object 타입으로 조회 시 내장되어 있는 빈들 모두 조회된다.

0개의 댓글