UMC_Back

Week 3_(1)

oyatplum 2024. 4. 28. 23:23

 

 

1. 스프링 컨테이너와 스프링빈

2. 싱글톤 컨테이너

3. 컴포넌트 스캔

 


 

1. 스프링 컨테이너와 스프링 빈

 

스프링 컨테이너 생성

스프링 컨테이너는 간단히 말해서 객체를 담고있는 것!

 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

이전 포스팅에서 위와 같은 코드를 작성했었다.

ApplicaitonContext는 인터페이스이면서 스프링 컨테이너에 해당한다.

 

 

스프링 컨테이너 생성 과정

1.

스프링 컨테이너를 생성할 때는 구성 정보를 지정해야 하는데

AppConfig.class가 이 구성 정보에 해당한다.

그러면 스프링 컨테이너는 key가 이름이고 value가 객체인 스프링 빈을 생성하게 된다.

2.

이후 스프링 빈은 @Bean 어노테이션을 보고 key에 메소드 이름을, value에 반환 객체를 넣어준다.

+) 추가적으로 빈 이름은 기본적으로 메소드 명을 사용하지만 직접 부여할 수 있고

빈 이름은 모두 달라야 한다.

3.

각각의 스프링 빈이 생성되고 동적인 객체 의존 관계를 스프링이 연결해준다.

 


 

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

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("name = " + beanDefinitionName + " object = " + bean);
        }
    }
    @Test
    @DisplayName("애플리케이션 빈 출력하기")
    void findApplicationBean(){
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

            // Role BeanDefinition.ROLE_APPLICATION : 직접 등록한 애플리케이션 빈
            // Role BeanDefinition.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()로 스프링 내외부 사용 빈 구분

위와 같은 방식으로 컨테이너에 빈이 제대로 등록되었는 지 확인할 수 있다.

 

 

스프링 빈 조회 - 기본

기본적으로 스프링 컨테이너에서 스프링 빈을 찾는 방법은 두 가지다.

1. getBean(빈이름, 타입)

2. getBean(타입)

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(){
        MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
        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));
    }
}

 

아이고 이게 다 무슨 소리인가… 하면

첫번째 Test는 스프링 빈의 이름으로 조회를 해서

assertThat(memberService).isInstanceOf(MemberServiceImpl.class);

즉, memberService가 MemberServiceImpl의 객체이냐~ 라고 물어보는 것

 

두번째 Test는 이름 없이 타입으로만 조회한 것이고

 

세번째는 MemberServiceImpl로 즉, 구현체로 조회한 것인데

좋은 방법은 아니지만(구현에 포커스를 두니까) 할 수 있다는 것을 보여주는 것이고

 

마지막 Test는 조회되지 않는 빈을 보는 것이다.

() -> ac.getBean("xxxx", MemberService.class)일 때 NoSuchBeanDefinitionException.class이러한 예외가 발생한다는 뜻!

 

 

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

동일한 타입인 경우에 조회를 하게 되면 오류가 발생하기 때문에 빈의 이름을 지정해줘야 한다.

 

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
    @Test
    @DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다.")
    void findBeanByTypeDuplicate(){
        //MemberRepository bean = ac.getBean(MemberRepository.class); 예외 발생
        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();
        }
    }

 

같은 타입이 존재해야 하기 때문에 해당 클래스 내부에 static으로 SameBeanConfig라는 클래스를 하나 더 생성했다.

이러면 이 클래스 내부에서만 사용할 수 있음!

MemberRepository 타입으로 memberRepository1,2를 생성했고 이때 중복 오류 발생을 처리해줬다.

 

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

 

추가적으로 위의 코드처럼

첫번째 테스트에서는 MemberRepository 타입으로 조회했을 때, 같은 타입이 둘 이상이니까 "memberRepository1"로써 빈의 이름을 지정해주었고

두번째 테스트에서는 ac.getBeansOfType을 통해 MemberRepository타입의 빈을 모두 조회했다.

이러면 Map으로 반환 값이 나오게 되고 for loop를 돌려서 key와 value를 출력해보면

다음과 같은 결과가 나오게 된다.

 

스프링 빈 조회 - 상속 관계

부모 타입으로 조회하면 자식 타입도 모두 함께 조회가 된다는 것을 기억하자

 

@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 findBeanBySubType(){
        DiscountPolicy 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));
        } 
    }

 

그러면 위와 같은 코드는 위에서 다 설명했으니까 크게 어려운 부분이 없당!!

 


 

BeanFactory와 ApplicationContext

  • BeanFactory
    • 스프링 컨테이너의 최상위 인터페이스
    • 스프링 빈을 조회하고 관리하는 역할
    • getBean() 제공
  • ApplicationContext
    • BeanFactory의 기능을 모두 상속받아서 제공
    • 애플리케이션을 개발할 때 빈을 관리하고 조회하는 것은 기본. >> 추가적인 부가 기능들 필요
      • MeassageSource
      • EnvironmentCapable
      • ApplicationEventPublisher
      • ResoureLoader
  • AnnotationConfig, ApplicationContext

 

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

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

 위의 두 내용은 크게 중요하지 않아서... 우선 작성하진 않겠당.. 필요하면 그때 쓰도록

 

 


 

2. 싱글톤 컨테이너

웹 애플리케이션과 싱글톤

이전에 작성한 코드를 기반으로 할 때

여러 클라이언트가 요청을 하는 횟수만큼 객체를 생성하게 된다.

public class SingletonTest {

    @Test
    @DisplayName("스프링 없는 순수한 DI 컨테이너")
    void pureContainer(){
        AppConfig appConfig = new AppConfig();
        //1. 조회 : 호출할 때마다 객체 생성
        MemberService memberService1 = appConfig.memberService();

        //1. 조회 : 호출할 때마다 객체 생성
        MemberService memberService2 = appConfig.memberService();

        //참조 값이 다른 것을 확인
        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);
        
    }
}

 

위와 같이 코드로 객체를 만들어 확인해보면

 

이렇게 각기 다른 객체가 호출할 때마다 생성되는 것을 확인할 수 있다.

이 방법은 딱 봐도 효율적이지 않겠지!

 

해결 방법은 객체를 딱 1개만 생성하자!

이게 바로 싱글톤 패턴~~

 

 

 

이제 싱글톤 패턴을 이용해서 구현해보자.

public class SingletonService {
    //static이어서 하나만 생성됨
    private static final SingletonService instance = new SingletonService();
    public static SingletonService getInstance() {
        return instance;
    }
    private SingletonService(){
    }
    public void logic(){
        System.out.println("싱글톤 객체 로직 호출");
    }
}
  • static 영역에 객체 instance 하나만 미리 생성해서 올려둔다.
  • 이 객체 instance가 필요하면 오직 getInstance() 메서드를 통해서만 조회 가능하고, 이 메서드를 호출하면 항상 같은 인스턴스를 반환한다.
  • 딱 1개의 인스터스만 존재하기 때문에 생성자를 위와 같이 private으로 막아서 혹시라도 외부에서 new로 객체 인스턴스가 생성되는 것을 방지해야 한다.

 

private으로 막았기 때문에 위와같이 new 하면 에러 뜨는 것을 볼 수 있음!!

 

 

@Test
    @DisplayName("싱글톤 패턴을 적용한 객체 적용")
    void singletonServiceTest(){
        //new SingletonService();
        SingletonService singletonService1 = SingletonService.getInstance();
        SingletonService singletonService2 = SingletonService.getInstance();

        System.out.println("singletonService1 = " + singletonService1);
        System.out.println("singletonService2 = " + singletonService2);

        assertThat(singletonService1).isSameAs(singletonService2);
    }

이렇게 테스트 코드를 작성해서 확인해보면

 

이전과 달리 같은 객체가 나오는 것을 확인할 수 있다.

 

 

이제 appConfig 관련 코드 모두 싱글톤 패턴으로 바꿔서 객체를 반환하면 되는데…..

이렇게 하나하나 모두 해줄 필요 없음!

왜냐면 스프링 컨테이너를 쓰면 기본적으로 싱글톤 패턴으로 만들어 관리해주기 때문이지~

 

 

싱글톤 패턴 문제점

  • 싱글톤 패턴 구현 코드 자체가 많이 필요함
  • 의존관계상 클라이언트가 구체 클래스에 의존 > DIP 위반
  • 클라이언트가 구체 클래스에 의존해서 OCP 원칙 위반할 가능성 높음
  • 테스트 어려움
  • 내부 속성 변경하거나 초기화 어려움
  • private 생성자로 자식 클래스 만들기 어려움
  • 유연성 떨어짐

스프링 프레임워크는 이러한 싱글톤 패턴의 문제점을 모두 해결해준다.

 

 


 

싱글톤 컨테이너

스프링 컨테이너는 싱글톤 패턴의 문제점을 해결하면서 객체 인스턴스를 싱글톤(1개)으로 관리한다.

지금까지 학습한 스프링 빈이 바로 싱글톤으로 관리되는 빈이다!!

 

@Test
    @DisplayName("스프링 컨테이너와 싱글톤")
    void springContainer(){
        //AppConfig appConfig = new AppConfig();
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

        MemberService memberService1 = ac.getBean("memberService", MemberService.class);
        MemberService memberService2 = ac.getBean("memberService", MemberService.class);

        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);

        assertThat(memberService1).isSameAs(memberService2);
    }

 

위와 같이 스프링 컨테이너(ApplicationContext)와 싱글톤을 적용해서 테스트를 해보면

 

같은 객체가 반환되는 것을 확인할 수 있다.

 

즉, 스프링 컨테이너를 사용하면 싱글톤으로 작동한다!

 

 

 

 

 

 

분량 조절을 위해 다음 포스팅으로...

 

 

 

'UMC_Back' 카테고리의 다른 글

여행기 - 도시, 국가 검색 정리 (전반적인 스프링 코드 이해)  (0) 2024.08.18
Week 3_(2)  (0) 2024.04.29
Week 2  (0) 2024.04.06
Week 1  (0) 2024.03.30