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

chaen-ing·2024년 6월 2일
0

Spring 공부

목록 보기
7/13

스프링 컨테이너 생성

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

ApplicationContext를 스프링 컨테이너라고함

ApplicationContext는 인터페이스임 → 다형성 O

new AnnotationConfigApplicationContext(AppConfig.class); 이부분이 구현체

  1. 스프링 컨테이너 생성

    new AnnotationConfigApplicationContext(AppConfig.class);

    생성시 구성정보를 지정해주어야해서 AppConfig.class를 매개변수로 넣는것

  2. 스프링 빈 등록

    @Bean붙은 메소드를 다 호출해서 메소드 이름을 빈 이름(키), 리턴하는 객체를 빈 객체(값)으로 빈 저장소에 저장

    빈이름은 메소드 이름을 사용하지 @Bean(name="memberService2")이런식으로 부여도 가능

    빈 이름은 항상 다른 이름을 부여해야함

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

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

    설정 정보를 참고해서 동적 의존관계를 주입

스프링은 빈을 생성하고, 의존관계를 주입하는 단계가 나누어져있다

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

public class ApplicationContextInfoTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("모든 빈 출력하기")
    void findAllBean(){
        String[] beanDefinitionNames = ac.getBeanDefinitionNames(); // 모든 빈 이름 꺼내기
        // iter
        for (String beanDefinitionName : beanDefinitionNames) {
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println("name = " + beanDefinitionName + "object = "+bean);
        }
    }
    
    @Test
    @DisplayName("애플리케이션 빈 출력하기")
    void findApplicationBean(){
        String[] beanDefinitionNames = ac.getBeanDefinitionNames(); // 모든 빈 이름 꺼내기
        // iter
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

            // 역할은 3가지 존재
            // ROLE_APPLICAITON : 내가 등록한 빈만 고르는 조건
            if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println("name = " + beanDefinitionName + "object = "+bean);
            }

        }
    }
}

getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름 조회 → String 빈 이름 리턴

getBean(빈이름) : 빈이름으로된 빈객체를 조회

beanDefinition.getRole

  • ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
  • ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈

스프링 빈 조회 - 기본

가장 기본적인 빈 조회 방법

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

조회대상 스프링 빈이 없으면 예외 발생NoSuchBeanDefinitionException: No bean named 'xxxxx' available

public class ApplicationContextBasicFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    
    @Test
    @DisplayName("빈 이름으로 조회")
    void findBeanByName(){
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        System.out.println("memberService = " + memberService);
        System.out.println("memberService.getClass() = " + memberService.getClass());
        // MemberServiceImpl이 잘 출력됨
        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(){
        MemberService memberService = ac.getBean("memberService",MemberServiceImpl.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

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

빈 타입으로 조회, 빈이름+타입으로 조회, 구체타입으로 조회 모두 가능

Assertions의 isInstanceOf(MemberServiceImpl.class)로 테스트 가능하다

구체타입으로 조회는 추천 X 유연성이 떨어짐

없는 빈 이름으로 조회하면 예외가 터짐

assertThrow(..)은 예외가 발생해야지 테스트가 성공하는 테스트

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

조회 시 같은 타입의 스프링 빈이 두개 이상이면 오류 발생 : NoUniqueBeanDefinitionException

→ 빈 이름 지정해줘야함

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 findBeanByType(){
        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);

    }

    // 중복 타입이 있는 config를 여기서만 사용할 수 있게 생성
    // static : 이 안에서만 쓰겠다
    @Configuration
    static class SameBeanConfig{
        @Bean
        public MemberRepository memberRepository1(){
            return new MemoryMemberRepository();
        }

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

    }
}

동일 타입의 빈이 두개이상이라면 빈이름을 통해서 getBean해주면됨

getBeansOfType은 빈이름, 빈객체를 으로 리턴

스프링 빈 조회 - 상속 관계

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

자바 객체의 최고 부모인 Object 타입으로 조회하면 모든 스프링빈 조회

public class ApplicationContextExtendsFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

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

    @Test
    @DisplayName("부모 타입으로 조회시, 자식이 둘 이상있으면, 빈 이름을 지정하면 된다")
    void findBeanByParentTypeBeanName(){
        DiscountPolicy rateDiscoutPolicy = ac.getBean("rateDiscountPolicy",DiscountPolicy.class);
        assertThat(rateDiscoutPolicy).isInstanceOf(DiscountPolicy.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();
        }
    }
}

부모타입으로 조회 → 자식이 둘이상이면 오류 발생 → 빈 이름 지정해줘야함

특정 하위 타입으로도 조회가능 → 비추

부모타입으로 모두 조회 가능 getBeansOfType → 자바 최상위 클래스인 Object.class로도 조회 가능

BeanFactory와 ApplicationContext

BeanFactory

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

스프링 빈 관리, 조회 역할

getBean()을 제공

ApplicationContext

BeanFactory기능을 모두 상속받아서 제공

→ 둘의 차이가 무엇일까? : 애플리케이션 개발에는 빈 관리, 조회 뿐만 아닌 많은 기능들이 추가로 필요함

MessageSource 메세지 소스를 활용한 국제화 기능 : 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력하는 기능

EnvironmentCapable 환경변수 : 로컬, 개발, 운영등을 구분해서 처리

  • 로컬 : 내 컴퓨터에서 개발하는 환경
  • 개발 : 테스트 서버에서 테스트하는 환경
  • 운영 : 실제 프로덕션에 나가는 환경

ApplicationEventPublisher 애플리케이션 이벤트 : 이벤트를 발행하고 구독하는 모델 편리하게 지원

ResourceLoader 편리한 리소스 조회 : 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회

정리하자면

ApplicationContext는 BeanFactory의 기능(관리, 조회) + 편리한 부가기능을 제공

BeanFactory를 직접 사용할일은 거의 없고, ApplicationContext만 쓴다고 보면됨 → 두개 모두 스프링 컨테이너라고 한다

다양한 설정 형식 지원 - 자바 코드, XML

스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있게 유연하게 설계되어있음 → 자바 코드, XML, Groovy 등등…

annotation 기반 자바 코드 설정 사용

  • 지금까지 했던것
  • AnnotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class)

XML 설정 사용

  • 최근에 잘 사용하진 않지만, 아직 많은 레거시 프로젝트들이 XML로 되어있고 컴파일이 필요없다는 장점이 있음
public class XmlAppContext {

    @Test
    void xmlAppContext(){
        ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
    }
}

GenericXmlApplicationContext(”appConfig.xml”) 이것만 다름

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="memberService" class="hello.core.member.MemberServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository"/>
    </bean>

    <bean id="memberRepository" class="hello.core.member.MemoryMemberRepository"/>

    <bean id="orderService" class="hello.core.order.OrderServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository"/>
        <constructor-arg name="discountPolicy" ref="discountPolicy"/>
    </bean>

    <bean id="discountPolicy" class="hello.core.discount.RateDiscountPolicy"/>
</beans>

코드 형식만 다르지 기존 AppConfig와 다르지 않음

스프링 빈 설정 메타 정보 - BeanDefinition

스프링은 어떻게 이런 다양한 설정 형식을 지원하는 것일까

→ 이것도 역할과 구현을 개념적으로 나눈 것

→ 자바코드인지, XML인지 스프링 컨테이너는 몰라도됨. 그냥 읽어서 BeanDefinition을 만들면됨

BeanDefinition빈 설정 메타정보라고함 → @Bean, 당 각각 하나씩 메타정보가 생성됨

스프링 컨테이너는 이 메타정보를 기반으로 스프링빈 생성

BeanDefinition 자체가 인터페이스임

AnnotationConfigApplicationContext : AnnotatedBeanDefinitionReader를 통해서 Appconfig.class를 읽음

→ 설정 정보를 읽어서 BeanDefinition을 생성

GenericXmlApplicationContext : 동일하게 XmlBeanDefinitionReader를 통해 appConfig.xml을 읽고 BeanDefinition 생성

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

BeanDefinition 정보

BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)
factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService
Scope: 싱글톤(기본값)
lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연
처리 하는지 여부
InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
Constructor arguments, Properties: 의존관계 주입에서 사용한다. (자바 설정 처럼 팩토리 역할의 빈을 사용
하면 없음)

BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할수도 있다 → 그러나 쓸일은 잘 없음


인프런 스프링 핵심 원리 기본편 - 김영한 강의를 듣고 정리한 내용입니다

profile
💻 개발 공부 기록장

0개의 댓글

관련 채용 정보