ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring]스프링 핵심원리_스프링 컨테이너와 빈
    Backend/Spring 2022. 2. 17. 15:18

    스프링 컨테이너 생성

    스프링 컨테이너가 생성되는 과정을 알아봅시다.

    ApplicationContext applicationContext=new AnnotationConfigApplicationContext(AppConfig.class);
    • ApplicationContext를 스프링 컨테이너라 한다. 
    • ApplicationContext는 인터페이스이다.
    • 스프링 컨테이너는 XML을 기반으로 만들 수 있고, 애노테이션 기반의 자바 설정 클래스로 만들 수 있다. 
    • 직전에 AppConfig를 사용했던 방식이 애노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 만든 것이다.
    • 자바 설정 클래스를 기반으로 스프링 컨테이너(ApplicationContext)를 만들어보자
      • new AnnotationConfigApplicationContext(AppConfig.class);
      • 이클래스는 ApplicationContext 인터페이스의 구현체이다.

     참고: 더 정확히는 스프링 컨테이너를 부를 때 BeanFactory, ApplicationContext로 구분해서 이야기한다. 이 부분은 뒤에서 설명하겠다. BeanFactory를 직접 사용하는 경우는 거의 없으므로 일반적으로 ApplicationContext를 스프링 컨테이너라 한다.

     

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

     

    스프링 컨테이너 생성

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

    스프링 빈 등록

    스프링 컨테이너는 파라미터로 넘어온 설정 클래스 정보를 사용하여 스프링 빈을 등록한다

     

    빈 이름

    • 빈 이름은 메서드 이름을 사용한다
    • 빈 이름을 직접 부여할 수 도 있다.
      • @Bean(name="memberService2")

     

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

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

    • 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)한다.
    • 단순히 자바 코드를 호출하는 것 같지만, 차이가 있다. 이 차이는 뒤에 싱글톤 컨테이너에서 설명한다. 

    참고
    스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져 있다. 그런데 이렇게 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한 번에 처리된다. 여기서는 이해를 돕기 위해 개념적으로 나누어 설명했다. 자세한 내용은 의존관계 자동 주입에서 다시 설명하겠다.

     

     

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

    스프링 컨테이너에 실제 스프링 빈들이 잘 등록되어있는지 확인해보는 과정을 진행해보겠습니다.

    테스트 코드로 작성.

    코드

    package hello.core.beanfind;
    
    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 hello.core.AppConfig;
    
    public class ApplicationContextInfoTest {
    
    	AnnotationConfigApplicationContext ac =new AnnotationConfigApplicationContext(AppConfig.class);
    	
    	@Test
    	@DisplayName("모든 빈 출력하기")
    	void findAllBean() {
    		System.out.println("===모든 빈 출력하기===");
    		String[] beanDefinitionNames= ac.getBeanDefinitionNames();
    		for(String beanDefinitionName : beanDefinitionNames ) {
    			Object bean =ac.getBean(beanDefinitionName);
    			System.out.println("name = "+ beanDefinitionName +" object = "+ bean);
    		}
    	}
    	
    	@Test
    	@DisplayName("애플리케이션 빈 출력하기")
    	void findApplicationBean() {
    		System.out.println("=====애플리케이션 빈 출력하기====");
    		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(): 빈 이름으로 빈객체(인스턴스)를 조회한다.

     

    애플리케이션 빈 출력하기

    • 스프링이 내부에서 사용하는 빈은 제외하고, 내가 등록한 빈만 출력해 보자. 
    • 스프링이 내부에서 사용하는 빈은 getRole()로 구분할수 있다.
      • ROLE_APPLICATION: 일반적으로 사용자 가정의 한빈
      • ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈

    스프링 빈 조회 - 기본

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

    • ac.getBean(빈이름, 타입)
    • ac.getBean(타입)
    • 조회 대상 스프링 빈이 없으면 예외 발생
      • NoSuchBeanDefinitionException: No bean named 'xxxxx' available
    package hello.core.beanfind;
    
    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;
    
    import hello.core.AppConfig;
    import hello.core.member.MemberService;
    import hello.core.member.MemberServiceImpl;
    
    public class ApplicationContextBasicFindTest {
    
    	AnnotationConfigApplicationContext ac =new AnnotationConfigApplicationContext(AppConfig.class);
    	
    	@Test
    	@DisplayName("빈 이름으로 조회")
    	void findBeanByName() {
    		MemberService memberService=ac.getBean("memberService",MemberService.class);
    		Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    	}
    	@Test
    	@DisplayName("이름 없이 타입으로만 조회")
    	void findBeanByType() {
    		MemberService memberService=ac.getBean(MemberService.class);
    		Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    	}
    	
    	@Test
    	@DisplayName("구체 타입으로 조회")
    	void findBeanByName2() {
    		MemberServiceImpl memberService=ac.getBean("memberService",MemberServiceImpl.class);
    		Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    	}
    	
    	
    	@Test
    	@DisplayName("빈 이름으로 조회X 에러 발생")
    	void findBeanByNameX() {
    		//두번째 파라미터의 함수를 실행시켰을 때 첫번째의 오류가 무조건 발생해야 성공이라는 의미.
    		org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,()->ac.getBean("xxxxx",MemberService.class));
    	}
    }

     

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

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

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

    package hello.core.beanfind;
    
    import static org.assertj.core.api.Assertions.assertThat;
    import static org.junit.jupiter.api.Assertions.assertThrows;
    
    import java.util.Map;
    
    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 hello.core.AppConfig;
    import hello.core.member.MemberRepository;
    import hello.core.member.MemberService;
    import hello.core.member.MemoryMemberRepository;
    
    public class ApplicationContextSameBeanFindTest {
    
    	AnnotationConfigApplicationContext ac =new AnnotationConfigApplicationContext(SameBeanConfig.class);
    	
    	@Test
    	@DisplayName("타입으로 조회시 같은 타입이 둘 이상이 있으면, 중복 오류가 발생한다")
    	void findBeanByTypeDuplicatie() {
    		//스프링 입장에서 무엇을 선택할지 모르겠다.
    		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();
    		}
    	}
    }

     

    스프링 빈 조회 - 상속관계

     

    부모 타입으로 조회하면, 자식 타입도 함께 조회한다.

    그래서 모든 자바 객체의 최고 부모인 Object타입으로 조회하면, 모든 스프링 빈을 조회한다.

    코드

    package hello.core.beanfind;
    
    import static org.assertj.core.api.Assertions.assertThat;
    import static org.junit.jupiter.api.Assertions.assertThrows;
    
    import java.util.Map;
    
    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 hello.core.AppConfig;
    import hello.core.discount.DiscountPolicy;
    import hello.core.discount.FixDiscountPolicy;
    import hello.core.discount.RateDiscountPolicy;
    import hello.core.member.MemberRepository;
    
    public class ApplicationContextExtendsFindTest {
    	
    	AnnotationConfigApplicationContext ac =new AnnotationConfigApplicationContext(TestConfig.class);
    	
    	@Test
    	@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다.")
    	void findBeanByParentTypeDuplicate() {
    	    assertThrows(NoUniqueBeanDefinitionException.class, ()->ac.getBean(DiscountPolicy.class));
    	}
    	
    	@Test 
    	@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
    	void findBeanByParentTypeBeanName() {
    		DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy",DiscountPolicy.class);
    		assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
    	}
    	
    	@Test
    	@DisplayName("부모 타입으로 모두 조회하기")
    	void findAllBeanByType() {
    		Map<String, DiscountPolicy> beansOfType=ac.getBeansOfType(DiscountPolicy.class);
    		for(String key : beansOfType.keySet()) {
    			System.out.println("key = "+key +" value = "+ beansOfType.get(key));
    		}
    		assertThat(beansOfType.size()).isEqualTo(2);
    	}
    	
    	@Test
    	@DisplayName("부모 타입으로 모두 조회하기 - Object")
    	void findAllBeanByObjectType() {
    		Map<String, Object> beansOfType=ac.getBeansOfType(Object.class);
    		for(String key : beansOfType.keySet()) {
    			System.out.println("key = "+key +" value = "+ beansOfType.get(key));
    		}
    	}
    	
    	@Configuration
    	static class TestConfig{
    		@Bean
    		public DiscountPolicy rateDiscountPolicy() {
    			return new RateDiscountPolicy();
    		}
    		@Bean
    		public DiscountPolicy fixDiscountPolicy() {
    			return new FixDiscountPolicy();
    		}
    	}
    	
    }

     

    BeanFactory와 ApplicationContext

    beanFactory와 ApplicationContext입니다

    BeanFactory

    • 스프링 컨테이너의 최상위 인터페이스입니다.
    • 스프링 빈을 관리하고 조회하는 역할을 담당합니다.
    • getBean()을 제공합니다.
    • 지금까지 우리가 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능입니다.

    ApplicationContext

    • BeanFactory 기능을 모두 상속받아서 제공합니다.
    • 빈을 관리하고 검색하는 기능을 BeanFactory가 제공하는 데 둘의 차이점이 뭘까?
    • 애플리케이션을 개발할 때는 빈은 관리하고 조회하는 기능을 물론이고 수많은 부가기능이 필요하다.

     

    ApplicationContext가 제공하는 부가기능

    • 메시지 소스를 활용한 국제화 기능
      • 예를 들어서 한국에서 들어온 면한 국어로, 영어권에서 들어오면 영어로 출력 
    • 환경변수
      • 로컬, 개발, 운영 등을 구분해서 처리
    • 애플리케이션 이벤트
      • 이벤트를 발행하고 구독하는 모델을 편리하게 지원
    • 편리한 리소스 조회
      • 파일, 클래스 패스, 외부 등에 서리 소스를 편리하게 조회

     

Designed by Tistory.