AppConfig 내에서 같은 클래스의 객체를 필요로 하는 메서드가 여러 개 있을 수 있다.

 

memberService(), orderService() 내부에서 MemberRepository 의 객체를 필요로 하여 memberRepository() 를 호출한다면? 

-> memberRepository() 는 두 번 호출되어 MemberRepository 의 객체는 두 번 생성될 것이다.

하지만 로그를 찍어보면 memberRepository() 는 딱 한 번만 호출되어 싱글톤이 유지되는 것을 알 수 있다.

 

스프링 컨테이너는 어떻게 이를 가능하게 하는가? 

 

->

 

스프링 컨테이너는 @Configuration 이 달린 클래스를 상속받는 클래스를 따로 만들어낸다.

(이 경우는 AppConfig@@@CGLIB)

 

이 자식클래스 내부에서 기존 AppConfig의 메서드들을 오버라이딩 한다.

오버라이딩 된 메서드의 대략적인 "예상" 로직은 아래와 같다.

@Override
@Bean 
public MemberRepository memberRepository() { 
    
    if (memoryMemberRepository가 이미 스프링 컨테이너에 등록되어 있으면?) { 
        return 스프링 컨테이너에서 찾아서 반환; 
    } else { //스프링 컨테이너에 없으면 
        기존 로직을 호출해서 MemoryMemberRepository를 생성하고 스프링 컨테이너에 등록 
        return 반환 
    }
}

이런 식으로 싱글톤을 보장한다.

 

이때 AppConfig를 상속받은 AppConfig@@@CGLIB는 AppConfig의 자식 클래스이기 때문에 AppConfig 타입으로 조회가 가능하다.

 

 

 

만약 @Configuration 없이 @Bean만 사용한다면?

 

@Bean에 의해 스프링 컨테이너에 빈 등록이 되긴 하지만 싱글톤 패턴은 @Configuration에 의해서 보장받는 것이기 때문에 여러 개의 객체가 생성된다.

(심지어 의존관계에 따라 Bean 등록이 되지 않는 것들도 있다.)

 

 

 

 

 

출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

기존에 스프링 없이 작성한 DI 컨테이너 AppConfig를 사용하면 사용자의 요청이 들어올 때마다 객체들을 하나씩 계속해서 생성한다.

 

사용자의 요청이 매우 많은 웹 애플리케이션에서 이같은 구조는 비효율적이다.

 

이를 해결하기 위해 싱글톤 디자인패턴을 활용한다.

 

싱글톤 패턴 : 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.

객체 인스턴스를 2개 이상 생성하지 못하도록 막아야 한다

public class SingletonService {

    // 하나만 존재하도록 하기 위해 static으로 선언
    private static final SingletonService instance = new SingletonService();

    // 객체의 외부 생성을 막기 위해 생성자 private 설정
    private SingletonService() {}

    // 이 클래스의 인스턴스가 필요하다면 getInstance()를 통해서만 접근할 수 있다
    public static SingletonService getInstance() {
        return instance;
    }
}

 

그럼 이제 AppConfig에서 객체를 생성하는 모든 함수들에 getInstance() 를 적용시켜 주면 된다.

 

하지만 싱글톤 패턴에도 여러 가지 단점이 존재한다.

 

1. 싱글톤 패턴 구현을 위한 추가 코드들 작성해야함

2. AppConfig 없이 싱글톤만 사용할 경우 클라이언트가 서버 객체의 구체 클래스에 의존하게 되어 OCP, DIP가 위반된다. (SingletonService.getInstance() -> static 메서드를 사용하기 위해 어쩔 수 없이 구체 클래스에 의존하게 된다)

3. 객체 내부 속성을 변경하거나 초기화하기 어렵다.

4. 유연성이 떨어진다

 

등등..

 

 

 

스프링 컨테이너를 사용하면 싱글톤 패턴의 장점은 모두 챙기면서 단점은 모두 피할 수 있게 된다.

 

 

 

(싱글톤 패턴 사용 시 주의점)

모든 사용자들의 요청을 하나의 빈이 수행하기 때문에 아래와 같은 사항들을 주의해야 한다.

1. stateless로 설계해야 한다.

2. 가급적 읽기만 가능해야 한다.

3. 스프링 빈의 필드에 공유 값을 설정하면 장애가 발생할 수 있다.

 

 

 

 

출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

// JUNIT 테스트
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);
            
            // Bean의 종류에 따라 해당하는 것만 찾을 수 있음
            // Role ROLE_APPLICATION : 직접 등록한 애플리케이션 빈 
            // Role ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈 
            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) { 
                Object bean = ac.getBean(beanDefinitionName); 
                System.out.println("name=" + beanDefinitionName + " object=" + bean); 
            } 
        } 
    }
}

컨테이너에 등록된 스프링 빈을 조회할 수 있다.

 

 

 

 

 

출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

// getBean(메서드 명, 메서드가 반환하는 객체의 타입) or getBean(메서드가 반환하는 객체의 타입)
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);

 

프로젝트가 실행되면 @Configuration이 등록된 클래스 (=AppConfig) 에서 @Bean 이 달린 모든 메서드를 찾아 실행시킨 후 반환된 객체를 스프링 컨테이너에 등록해둔다. (스프링 컨테이너 == ApplicationContext)

 

이때 저장되는 객체의 이름은 호출된 메서드의 이름으로 한다. 또는 @Bean(name = "~~") 로 직접 설정도 가능하다.

(key : value == 메서드명 : 생성된 객체,  빈의 이름이 겹치지 않도록 해야한다)

 

 

 

 

 

 

출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

1. SRP : 단일 책임 원칙 (Single responsibility principle)

- 한 클래스는 하나의 책임만을 가져야 한다.

 

2. OCP : 개방/폐쇄 원칙 (Open/closed principle)

- 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

- 다형성을 사용해도 OCP를 위배하고 마는데 어떻게? -> AppConfig로 해결했으나 비효율적 -> 이를 해결하기 위해 스프링 등장

 

3. LSP : 리스코프 치환 원칙 (Liskov substitution principle)

- 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

- 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것, 다형성을 지원하기 위 한 원칙, 인터페이스를 구현한 구현체는 믿고 사용하려면, 이 원칙이 필요하다.

- 프로그램의 정확성?

-> ex : 자동차 인터페이스의 goForward() 는 앞으로 나아가는 기능을 해야한다. 나아가는 방식이 다르더라도 반드시 '앞으로' 나아가야 한다

 

4. ISP : 인터페이스 분리 원칙 (Interface segregation principle)

- 범용 인터페이스 하나보다는 여러 개의 인터페이스로 분리하는 것이 낫다.

- ex : 자동차 인터페이스 하나보다는 운전 인터페이스, 정비 인터페이스 둘로 나누는 것이 낫다.

 

5. DIP : 의존관계 역전 원칙 (Dependency inversion principle)

- 프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 의존성 주입은 이 원칙 을 따르는 방법 중 하나다.

- 구현 클래스에 의존하지 말고 인터페이스에 의존하도록 해야한다는 말

- ex : 아반떼에 의존하는 것이 아니라 자동차에 의존해야한다.

- OCP와 마찬가지로 다형성을 사용해도 DIP를 위배하게 된다. -> AppConfig로 해결했으나 비효율적 -> 스프링으로 해결

 

 

 

출처 : www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

+ Recent posts