Framework/Spring

인프런 스프링 핵심 원리-기본편 #4 스프링 컨테이너와 스프링 빈

MINGYUM 2022. 1. 4. 19:46

2. Bean 조회하기

 

package hello.core.binfined;

import hello.core.AppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.MemberRepository;
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.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.*;

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("타입으로 조회 시 같은 타입이 둘 이상 있으면, Bean 이름을 지정하면 된다.  ")
    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();
        }
    }
}

이렇게 Test 코드 내에서 Config 클래스를 지정해서, 테스트를 위한 함수를 만든다. 

AnnotationConfigApplicationContext에 대한 객체를 만들 때 매개변수에 생성한 Config 클래스를 넣는다. 

 

 

 

 

3. BeanFactory

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

 

ApplicationContext

: BeanFactory의 기능을 상속받아 제공한다. 

- 환경 변수 제공 : 로컬 / 개발 / 운영 을 구분해서 처리

- 메시지 소스를 활용한 국제화 기능

- 애플리케이션 이벤트

 - 편리한 리소스 조회

 

 

4. XML을 이용한 Configuration

 

main > resources에 appConfig.xml 생성

(Enterprise 버전에서는 SpringConfig의 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>

    <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"></bean>
</beans>

bean이라는 태그를 이용해, memberRepostiory 등 다양한 인스턴스의 구체 클래스를 지정한다. 

 

XML 파일을 이용한 Test

package hello.core.xml;

import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.GenericXmlApplicationContext;

import static org.assertj.core.api.Assertions.*;

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

 

 

5. 빈 설정 메타 정보 - Bean Definition

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

 

 

6. 싱글톤 컨테이너

 

고객의 memberService 요청에 대해서 각각 객체를 만들어서 반환하는 문제

package hello.core.singletone;

import hello.core.AppConfig;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest;

public class SingletoneTest {
    @Test
    @DisplayName("스프링 없는 순수한 DI 컨테이너")
    void pureContainer(){
        AppConfig appConfig = new AppConfig();

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

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

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

        // memberService != memberService2
        Assertions.assertThat(memberService1).isNotSameAs(memberService2);
    }

}

 

이를,

해당 객체가 하나만 생성되고 이를 공유하도록 설계한다 

package hello.core.singletone;

public class SingletonService {

    // private static 선언
    private static final SingletonService instance = new SingletonService();

    // 조회
    public static SingletonService getInstance(){
        return instance;
    }


    // private 생성자
    private SingletonService(){

    }

    public static void main(String[] args) {
        SingletonService singletonService1 = new SingletonService();
        SingletonService singletonService2 = new SingletonService();
    }
}

getInstance를 통해서만 instance, SingletonService 객체를 생성할 수 있다. 

외부에서는 SingletoneService에 대한 객체를 new할 수 없으며, 1개의 객체 인스턴스만 존재해야 하므로 private static을 사용한다. 

싱글톤 Test

이렇게 같은 객체가 만들어진 것을 알 수 있다. 

그러나 !

스프링 컨테이너를 쓰면, 기본적으로 싱글톤 객체로 만들어 관리한다. 

 

싱글톤 패턴을 적용하면, 고객의 요청이 올 떄마다 객체를 생성하지 않고 저장된 객체를 공유하게 된다. 

 

문제점 

- 싱글턴 패턴 구현을 위한 코드가 들어간다

- DIP 위반

- OCP 위반 가능성

- 테스트가 어렵다

- private 생성자 사용으로, 자식 클래스를 만들 수 없다

 

다음 포스팅에 이어서 싱글톤 컨테이너를 이용한 단점 해결을 알아보겠다.