스프링 핵심 원리 (기본편) CH04 Created Time: June 15, 2022 12:04 PM Last Edited Time: June 23, 2022 3:17 PM
스프링 컨테이너와 스프링 빈
ApplicationContext가 스프링 컨테이너다!!
과정
스프링 컨테이너 생성
스프링 빈 등록(@Bean붙은것들 호출)(반드시 빈이름 무조건다르게하라) key: 메서드이름 , value:반환된객체 가되어 스프링 빈 저장소에 등록됨
스프링 빈 의존관계 설정 준비 (객체 생성함)
스프링 빈 의존관계 설정 완료 (의존관계 주입, 연결됨)
컨테이너에 등록된 모든 빈 조회
스프링 컨테이너에 실제 스프링 빈이 잘들어가있는지확인
테스트코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 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 () { String[] beanDefinitionNames = ac.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { Object bean = ac.getBean(beanDefinitionName); System.out.println("beanDefinitionName = " + beanDefinitionName+"object" +bean); } } @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("beanDefinitionName = " + beanDefinitionName + "object" + bean); } } } }
스프링 빈 조회 - 기본
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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 () { assertThrows(NoSuchBeanDefinitionException.class, ()->ac.getBean("xxx" , MemberService.class)); } }
스프링빈 조회 - 동일한 타입이 둘 이상
타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이면 오류발생>> 빈 이름 지정하자
ac.getBeanOfType()을 사용하면 해당 타입의 모든 빈을 조회할수있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package hello.core.beanfind;import hello.core.AppConfig;import hello.core.discount.DiscountPolicy;import hello.core.member.MemberRepository;import hello.core.member.MemberService;import hello.core.member.MemoryMemberRepository;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;import static org.assertj.core.api.AssertionsForClassTypes.assertThat;import static org.junit.jupiter.api.Assertions.assertThrows;public class ApplicationContextSameFindTest { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class); @Test @DisplayName ("타입으로 조회시 같은 타입이 둘 이상있으면, 중복 오류 발생한다." ) void findBeanByTypeDuplicate () { 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)); assertThat(beansOfType.size()).isEqualTo(2 ); } } @Configuration static class SameBeanConfig { @Bean public MemberRepository memberRepository1 () { return new MemoryMemberRepository(); } @Bean public MemberRepository memberRepository2 () { return new MemoryMemberRepository(); } } }
스프링 빈 조회 - 상속관계(!!!)
부모 타입으로 조회하면, 자식 타입도 함께 조회한다.
그래서 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다.
실제 개발할때는 애플리케이션 스프링 컨테이너 직접들어가서 인잭션까지하지는 않는다. 하지만 지식적인요소
테스트 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class ApplicationContextExtendsFindTest { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class); @Test @DisplayName ("부모타입으로 조회시, 자식이 둘이상있으면, 중복 오류가 발생" ) void findBeanByParentTypeDuplicate () { assertThrows(NoUniqueBeanDefinitionException.class, ()->ac.getBean(DiscountPolicy.class)); } @Test @DisplayName ("부모타입으로 조회시, 자식이 둘이상있으면, 빈 이름을 지정하면 된다." ) void findBeanByParentByName () { DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy" , DiscountPolicy.class); assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class); } @Test @DisplayName ("특정 하위 타입으로 조회" ) void findBeanBySubType () { RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class); assertThat(bean).isInstanceOf(RateDiscountPolicy.class); } @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)); } } @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
스프링 컨테이너의 최상위 인터페이스
스프링 빈을 관리하고 조회하는 역할(getBean()등등제공)
ApplicationContext
다양한 설정 형식 지원 - 자바 코드, XML
애노테이션 기반 자바 코드 설정 사용 지금까지 했던 것이다. new AnnotationConfigApplicationContext(AppConfig.class) AnnotationConfigApplicationContext 클래스를 사용하면서 자바 코드로된 설정 정보를 넘기면 된다.
XML 설정 사용 최근에는 스프링 부트를 많이 사용하면서 XML기반의 설정은 잘 사용하지 않는다. 아직 많은 레거시프로젝트 들이 XML로 되어 있고, 또 XML을 사용하면 컴파일 없이 빈 설정 정보를 변경할 수 있는 장점도 있으므로 한번쯤 배워두는 것도 괜찮다. GenericXmlApplicationContext 를 사용하면서 xml 설정 파일을 넘기면 된다.
파일생략
스프링 빈 설정 메타 정보 - BeanDefinition
스프링은 어떻게 이런 다양한 설정 형식을 지원하는 것일까? 그 중심에는 BeanDefinition 이라는 추상화가 있다.
쉽게 이야기해서 역할과 구현을 개념적으로 나눈 것 이다! XML을 읽어서 BeanDefinition을 만들면 된다. 자바 코드를 읽어서 BeanDefinition을 만들면 된다. 스프링 컨테이너는 자바 코드인지, XML인지 몰라도 된다. 오직 BeanDefinition만 알면 된다.
BeanDefinition 을 빈 설정 메타정보라 한다. @Bean , 당 각각 하나씩 메타 정보가 생성된다.
스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성한다.
ApplicationContext생성과정 (각Reader가 Config파일을 읽어서 각각의 BeanDefinition을 만듬)
BeanDefinition정보
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package hello.core.beandefinition;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 BeanDefinitionTest { AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class); @Test @DisplayName ("빈 설정 메타정보 확인" ) void findApplicationBean () { String[] beanDefinitionNames = ac.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName); if (beanDefinition.getRole()== BeanDefinition.ROLE_APPLICATION){ System.out.println("beanDefinitionName = " + beanDefinitionName +"beanDefinition=" +beanDefinition); } } } }