본문 바로가기


프로젝트 하면서/spring

싱글톤 컨테이너

by worldforest 2022. 12. 13.

Spring은 기업용 온라인 서비스 기술을 지원을 위해 개발됨

DI 컨테이너 : 직접 만들었던 AppConfig

 

문제1. 요청이 있으면 객체를 new로 생성해서 전달 = 요청하는 횟수만큼 생성됨

 

memberService1: hello.core.member.MemberServiceImpl@305b7c14
memberService2: hello.core.member.MemberServiceImpl@6913c1fb

호출할 때마다 새로운 것이 생성됨. 생성된 객체의 참조값이 다른 것을 확인할 수 있다.

메모리 낭비가 심함 => 생성된 객체 인스턴스를 공유해서 사용하자

 

 

하나의 서비스에서 하나의 인스턴스가 생성되는 것을 보장하는 디자인 패턴 = 싱글톤패턴

 

private으로 설정했기 때문에 외부에서 임의로 객체를 생성할 수 없는 것을 확인할 수 있다.

 

static 영역에 객체 instance를 미리 하나 생성해서 올려주고

getInstance()로만 조회

컴파일 오류로 에러를 잡는 것이 잘 설계한 것이다.

 

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

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

        Assertions.assertThat(singletonService1).isSameAs(singletonService2);
    }
singletonService1 : hello.core.singleton.SingletonService@22fcf7ab
singletonService2 : hello.core.singleton.SingletonService@22fcf7ab

 

isSame : 인스턴스

isEquls : 값

 

 

스프링 컨테이너를 사용하면 싱글톤 패턴이 적용된다.

getInstance로 가져와서 하지 않아도 된다

 

==> 유연성 떨어지고 안티패턴, 자식 클래스 만들기 어려움

 

싱글톤 컨테이너

싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리

컨테이너는 객체를 하나만 생성해서 관리함=싱글톤 레지스트리(싱글톤 객체 생성하고 관리하는 기능)

DIP, OCP, 테스트, private 모두 사용가능

 

memberService1: hello.core.member.MemberServiceImpl@5829e4f4
memberService2: hello.core.member.MemberServiceImpl@5829e4f4

memberService1: hello.core.member.MemberServiceImpl@1698fc68
memberService2: hello.core.member.MemberServiceImpl@1698fc68

 

 

같은 객체를 사용하는 것을 확인할 수 있다. 

 

싱글톤 방식의 주의점!

 

특정 클라이언트에 의존적이면 안 된다                                                                                                                                                                                                                                                                                                                                     

특정 클라이언트가 값을 변경할 수 없도록

공유필드를 사용할 때는 스프일 빈을 항상 무상태 stateless로 설계해야 한다.

public class StatefulService {

    //private int price;

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

    public void getPrice(){
        //return price;
    }
}

 

 

@Configuration

@Bean이 있는데 memberService 호출되면 memberRepository 호출

orderService호출되면 memberRepository호출됨

==> 싱글톤이 깨질까 깨지지 않을까

 

public class ConfigurationSingletonTest {

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

        MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
        OrderServiceImpl orderService = ac.getBean("orderService", OrderServiceImpl.class);
        MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);

        MemberRepository memberRepository1 = memberService.getMemberRepository();
        MemberRepository memberRepository2 = orderService.getMemberRepository();

        System.out.println("memberService -> memberRepository1: "+memberRepository1);
        System.out.println("orderService -> memberRepository2: "+memberRepository2);
        System.out.println("memberRepository: "+memberRepository);
    }
}
memberService -> memberRepository1: hello.core.member.MemoryMemberRepository@708400f6
orderService -> memberRepository2: hello.core.member.MemoryMemberRepository@708400f6
memberRepository: hello.core.member.MemoryMemberRepository@708400f6
    //call AppConfig.memberService
    //call AppConfig.memberRepository
    //call AppConfig.memberRepository
    //call AppConfig.orderService
    //call AppConfig.memberRepository
    
    //call AppConfig.memberService
    //call AppConfig.memberRepository
    //call AppConfig.orderService

 

@Configuration과 바이트코드 조작의 마법

    @Test
    void configurationDeep() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        AppConfig bean = ac.getBean(AppConfig.class);

        System.out.println("bean: "+bean.getClass());
    }
bean: class hello.core.AppConfig$$EnhancerBySpringCGLIB$$c329221c

bean: class hello.core.AppConfig

$$EnhancerBySpringCGLIB : CGLIB 바이트코드 조작 라이브러리

$$c329221c :  클래스 정보

 

순수한 클래스

class hello.core.AppConfig

 

 

스프링이 빈을 등록할 때 "EnhancerBySpringCGLIB"라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고 그 다른 클래스를 스프링 빈으로 등록한 것!

instance 객체가 AppConfig@CGLIB 로 등록되어있다.

 

스프링 컨테이너에 등록되어 있으면 찾아서 반환, 없으면 생성하고 스프링 컨테이너에 등록하는 로직을 실행

 

@Configuration을 사용하지 않고 @Bean만 사용하면

순수한 클래스로 등록되고 싱글톤을 보장하지 않는다.

 

@Configuration을 사용하면 아래와 같이 스프링 컨테이너에 등록된 bean을 조회해서 사용할 수 있지만

@Bean
    public MemberService memberService(){
        // 생성자 주입
        System.out.println("call AppConfig.memberService");
        return new MemberServiceImpl(memberRepository());// 배우
    }

@Configuration을 사용하지 않으면 객체를 새로 생성해서 사용하는 것과 같다.

@Bean
    public MemberService memberService(){
        // 생성자 주입
        System.out.println("call AppConfig.memberService");
        return new MemberServiceImpl(new MemoryMemberRepository());// 배우
    }

 

@Autowired를 사용해서 의존관계주입을 해주면 스프링에 등록된 bean을 가져올 수 있다.

@Autowired MemberRepository memberRepository;
@Bean
public MemberService memberService(){
    // 생성자 주입
    System.out.println("call AppConfig.memberService")
    return new MemberServiceImpl(memberRepository);// 배우
}

 

@Configuration과 @Bean의 조합으로 싱글톤을 보장하는 경우는 정적이지 않은 메서드일 때입니다.
정적 메서드에 @Bean을 사용하게 되면 싱글톤 보장을 위한 지원을 받지 못합니다.

 

By marking this method as static, it can be invoked without causing instantiation of its declaring @Configuration class, thus avoiding the above-mentioned lifecycle conflicts. Note however that static @Bean methods will not be enhanced for scoping and AOP semantics as mentioned above.
반응형

댓글