[스프링 핵심 원리 - 기본편] 04. 스프링 컨테이너와 스프링 빈

Turtle·2024년 6월 11일
0
post-thumbnail

🙄스프링 컨테이너 생성

ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

이전에 만들었던 전체 애플리케이션의 실행 동작을 담당하는 설정 클래스인 AppConfig 클래스를 기반으로 스프링 컨테이너를 만든다.

ApplicationContext는 인터페이스고 new AnnotationConfigApplicationContext(AppConfig.class)는 인터페이스의 구현체이다.

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

✔️1. 스프링 컨테이너 생성

new AnnotationConfigApplicationContext(AppConfig.class);

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

✔️2. 스프링 빈 등록

스프링 빈 이름 : 메서드 이름
기본적으로 지정되는 메서드 이름 대신 빈 이름을 직접 부여할 수 있다.

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

스프링 컨테이너 내에 객체를 생성한다.

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

스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입한다.

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

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

    @Test
    @DisplayName("모든 빈 출력하기")
    void findAllBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }

    @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 + " : " + bean);
            } else if (beanDefinition.getRole() == BeanDefinition.ROLE_INFRASTRUCTURE) {
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println(beanDefinitionName + " : " + bean);
            }
        }
    }
}
  • ✔️모든 빈 출력하기

    • 실행하면 스프링에 등록된 모든 빈을 조회한다.
      • ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
      • ac.getBean() : 빈 이름으로 빈 객체를 조회한다.
  • ✔️애플리케이션 빈 출력하기

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

🙄스프링 빈 조회 - 기본

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

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

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

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

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

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

public class ApplicationContextSameBeanFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);

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

    }

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

기존 AppConfig의 수정 대신 새로운 정적 중첩 클래스 SameBeanConfig를 만든 후 MemberRepository 스프링 빈을 등록한다. 테스트를 실행해보면 아래와 같은 에러가 발생한다.

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.member.MemberRepository' available: expected single matching bean but found 2: memberRepository1,memberRepository2

위와 같이 동일한 타입의 빈이 둘 이상 있으면 중복 오류가 발생하기에 타입으로 조회시 같은 타입의 빈이 둘 이상 있다면 빈 이름을 지정해야한다.
또한 특정 타입을 getBeansOfType() 메서드를 사용해서 모두 조회할 수 있다.

public class ApplicationContextSameBeanFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);

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

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

    @Test
    @DisplayName("특정 타입을 모두 조회하기")
    void findAllBeansByType() {
        Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
        for (String s : beansOfType.keySet()) {
            System.out.println("key = " + s + ", value = " + beansOfType.get(s));
        }
    }

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

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

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

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

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

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

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

    @Test
    @DisplayName("부모 타입으로 모두 조회하기 - 자식 타입도 함께 조회가 된다.")
    void findAllBeanByParentType() {
        Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
        for (String s : beansOfType.keySet()) {
            System.out.println("key = " + s + ", value = " + beansOfType.get(s));
        }
    }

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

    @Configuration
    static class TestConfig {

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

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

✔️인터페이스의 상속
DiscountPolicy는 인터페이스이고, RateDiscountPolicyFixDiscountPolicyDiscountPolicy의 구현체라고 한다. 다형성과 계층구조 관점에서 보면 DiscountPolicyRateDiscountPolicyFixDiscountPolicy의 부모로 볼 수 있다.

🙄BeanFactory와 ApplicationContext

✔️BeanFactory

  • 스프링 컨테이너의 최상위 인터페이스
  • 스프링 빈을 관리하고 조회하는 역할을 담당
  • getBean()을 제공

✔️ApplicationContext

  • BeanFactory 기능을 모두 상속받아서 제공
  • 뿐만 아니라 애플리케이션 개발에 필요한 수많은 부가 기능을 사용(빈 관리 기능 + 부가 기능)
  • BeanFactory를 직접 사용할 일은 거의 없다. 부가 기능이 포함된 이 ApplicationContext를 사용한다.
  • BeanFactory나 ApplicationContext를 스프링 컨테이너라고 한다.

  • MessageSource : 메시지 소스를 활용한 국제화 기능
  • EnvironmentCapable : 환경변수
  • ApplicationEventPublisher : 애플리케이션 이벤트
  • ResourceLoader : 리소스 로더

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

✔️어노테이션 기반 자바 코드 설정 사용

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

✔️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);
    }
}
<?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>

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

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

❓ApplicationContext를 사용하지 않는 이유(getBeanDefinition() 메서드 지원 불가로 인한 오류 발생)

public class BeanDefinitionTest {
    ApplicationContext 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);
            }
        }
    }
}

🔒출처

스프링 핵심 원리 - 기본편

0개의 댓글