Spring - 싱글톤 컨테이너

YUNU·2023년 8월 3일
0

스프링

목록 보기
14/33
post-thumbnail

🌱 Spring


웹 어플리케이션 - 고객이 계속 요청, 여러 고객이 동시에 요청

문제점 : 요청이 올 때마다 DI 컨테이너는 객체를 계속 생성 ➡️ 메모리 낭비가 심함

해결방안 : 해당 객체를 1개만 생성하고 이를 공유하도록 설계 (싱클톤 패턴)

🟦 싱글톤 패턴

클래스의 인스턴스가 오직 1개만 생성되는 것을 보장하는 디자인 패턴

➡️ 객체 인스턴스를 2개 이상 생성하지 못하도록 제한

➡️ private 생성자를 사용하여 외부에서의 new 키워드를 통한 인스턴스 생성 제한

public class SingletonService {
    
    // 자기 자신(객체)을 생성하여 instance에 참조를 넣어둠
    // static 영역에 1개만 존재
    private static final SingletonService instance = new SingletonService(); 
    
    // instacne의 참조를 꺼낼 수 있는 유일한 방법
    // 아래의 static 메서드를 통해서만 조회 가능
    public static SingletonService getInstance() {
        return instance;
    }
    
    // 외부에서 new를 통한 객체 생성 불가
    private SingletonService() {}
    
}

➡️ 이미 만들어진 객체를 공유해서 효율적으로 사용 가능

싱글톤 패턴 문제점
 1. 싱글톤 패턴을 구현하는 코드 자체가 많이 들어감
 2. 클라이언트가 구체 클래스에 의존 -> DIP 위반, OCP 위반 가능
 3. 테스트하기 어려움
 4. 내부 속성 변경하거나 초기화 하기 어려움
 5. private 생성자로 자식 클래스 만들기 어려움
 6. 유연성이 떨어짐

🟦 싱글톤 컨테이너

스프링 컨테이너는 싱글톤 컨테이너의 역할을 한다.

스프링 컨테이너는 싱글톤 패턴의 문제점을 해결 + 객체 인스턴스를 싱글톤으로 관리

스프링 컨테이너는 고객의 요청이 올 때 마다 이미 만들어진 객체를 공유

	@Test
    void springContainer() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

        OrderService orderService1 = ac.getBean("orderService",OrderService.class);
        OrderService orderService2 = ac.getBean("orderService",OrderService.class);
     
        System.out.println("orderService1 = "+orderService1);
        System.out.println("orderService2 = "+orderService2);

        //orderService1 == orderService2
        assertThat(orderService1).isSameAs(orderService2);
    }
스프링이 싱글톤 방식만 지원하는 것은 아님, 요청할 때 마다 새로운 객체 생성해서 반환하는 기능도 존재

🟦 싱글톤 방식의 주의 사항

객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식

➡️ 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유

➡️ 싱글톤 객체는 상태를 유지(stateful)하게 설계 X

➡️ 최대한 무상태(stateless)로 설계

➡️ 스프링 빈의 필드에 공유 값을 설정하면 정말 큰 장애 발생 가능
ex) orderSerivce에 공유 필드를 사용하면 orderSerivce1과 orderService2가 같기에 문제 발생 가능

stateless한 설계
🔹 특정 클라이언트에 의존적인 필드가 있으면 안됨
🔹 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안됨
🔹 가급적 읽기 가능으로만 // 값 수정 X
🔹 필드 대신 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용

🔷 Stateful한 설계

public class StatefulOrder {

    private int price; // 상태 유지 필드

    public void order(int price) {
    
        this.price = price; // 문제 발생

    }

    public int getPrice(){
        return price;
    }
}

class StatefulOrderT {

	@Test
    void singletonStatefulOrder() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(OrderConfig.class);
        StatefulOrder statefulOrder1 = ac.getBean(StatefulOrder.class);
        StatefulOrder statefulOrder2 = ac.getBean(StatefulOrder.class);

        //A 1000원 주문
        statefulOrder1.order("A",1000);

        //B 500원 주문
        statefulOrder2.order("B",500);

        int price = statefulOrder1.getPrice();
        System.out.println(price); // 500 -> 문제 발생

    }

    static class OrderConfig {

        @Bean
        public StatefulOrder statefulOrder() {
            return new StatefulOrder();
        }
    }

}

🔷 Stateless한 설계

public class StatelessOrder {

    public int order(int price) {
    
        return price;
    }
}
class StatelessOrderT {

    void singletonStatelessOrder() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(OrderConfig.class);
        StatefulOrder statefulOrder1 = ac.getBean(StatefulOrder.class);
        StatefulOrder statefulOrder2 = ac.getBean(StatefulOrder.class);

        //A 1000원 주문, 지역 변수 사용
        int APrice = statefulOrder1.order("A",1000);

        //B 500원 주문, 지역 변수 사용
        int BPrice = statefulOrder2.order("B",500);

        System.out.println(APrice); // 1000
    }

    static class OrderConfig {

        @Bean
        public StatefulOrder statefulOrder() {
            return new StatefulOrder();
        }
    }

}


인프런 스프링 핵심 원리 - 기본편 (김영한) 참조

profile
DDeo99

0개의 댓글