BackEnd/Java

[Spring] 싱글톤 컨테이너, 컴포넌트와 컴포넌트 스캔

kangminhyuk1111 2024. 4. 7. 21:09

싱글톤 컨테이너

스프링 컨테이너는 싱글톤 패턴의 문제점들을 해결하면서, 객체 인스턴스를 싱글톤으로 생성한다.

스프링에서는 싱글톤 패턴을 구현하기 위한 지저분한 코드가 필요없다.

DIP, OCP , 테스트, private 생성자로 부터 자유로워진다.

스프링 컨테이너를 사용하면 기본적으로 싱글톤으로 사용된다.

싱글톤 방식말고 요청할때마다 새로운 객체를 생성해서 반환하는 기능도 제공한다.

하지만 거의 대부분 99% 싱글톤 방식을 사용한다.

싱글톤 방식의 주의점

싱글톤 패턴이든, 스프링 컨테이너든, 하나만 생성해서 공유하는 싱글톤 방식은, 상태를 유지하게

설계되면 안된다.

  • 무상태로 설계해야한다
  • 특정 클라이언트에 의존적인 필드가 있으면 안된다.
  • 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
  • 가급적 읽기만 가능해야한다.
    @Test
    void statefulServiceSingleton() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 = ac.getBean(StatefulService.class);

        // a가 1000원 주문
        statefulService1.order("userA", 1000);
        // b가 2000원 주문
        statefulService2.order("userB", 2000);

        // ThreadA: 사용자 A가 주문 금액 조회
        int price = statefulService1.getPrice();
        System.out.println("price = " + price);

        Assertions.assertThat(statefulService1.getPrice()).isEqualTo(1000);
    }

위의 코드는 오류가 생기는 코드이다.

왜냐하면, 스프링 컨테이너는 하나의 StatefulService가 생성된다 싱글톤 패턴으로,

ac.getBean으로 선택한 StatefulService는 객체가 두개생성 된 것이 아니라

하나의 객체를 두명이 사용하는게 되어버리고 결국,

a주문을 b주문이 덮어버리기 때문에 1000에 equal하지 않고 2000에 equal이 된다.

@Test
    @DisplayName("싱글톤 객체 테스트 successful")
    void statefulServiceSingletonResolve() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 = ac.getBean(StatefulService.class);

        // a가 1000원 주문
        int userA = statefulService1.order("userA", 1000);
        // a가 1000원 주문
        int userB = statefulService2.order("userB", 2000);

        // ThreadA: 사용자 A가 주문 금액 조회
        System.out.println("price = " + userA);

        Assertions.assertThat(userA).isEqualTo(1000);
    }

위 코드는 수정된 로직이며 서비스 로직은 아래와 같이 수정함

public class StatefulService {
    public int order(String name, int price) {
        System.out.println("name = " + name + " price = " + price);
        return price;
    }
}

컴포넌트 스캔

컴포넌트 스캔과 의존관계 자동주입

지금까지 스프링 빈을 등록할 때는 자바코드의 @Bean 이나 xml의 beans를 통해 설정 정보에 저장

스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔을 제공함

의존관계도 자동으로 주입하는 @Autowired 도 지원한다.

컴포넌트 스캔을 사용하려면 먼저 @ComponentScan 을 설정 정보에 붙여주면 된다.

컴포넌트 스캔 기본 대상

@Component 뿐만 아니라 다음과 같은 내용도 추가로 대상에 포함한다

@Component : 컴포넌트 스캔에서 사용 @Controller : 스프링 MVC 컨트롤러에서 사용 @Service : 스프링 비즈니스 로직에서 사용 @Repository : 스프링 데이터 접근 계층에서 사용 @Configuration : 스프링 설정 정보에서 사용

컴포넌트 스캔의 용도 뿐만 아니라 다음 어노테이션 존재시 스프링은 부가 기능을 수행함

@Controller : 스프링 MVC 컨트롤러로 인식 @Repository : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다. @Configuration : 앞서 보았듯이 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리 를 한다. @Service : 사실 @Service 는 특별한 처리를 하지 않는다. 대신 개발자들이 핵심 비즈니스 로직이 여기에 있겠구나 라고 비즈니스 계층을 인식하는데 도움이 된다.

반응형