endsharp / study

1 stars 0 forks source link

[20221225] 싱글톤 컨테이너 #9

Open otakijae opened 1 year ago

otakijae commented 1 year ago
LeeeeDain commented 1 year ago

싱글톤 컨테이너

웹 어플리케이션 문제점

싱글톤 패턴

싱글톤 패턴 문제점

싱글톤 컨테이너

싱글톤 방식의 주의점

스프링에서 싱글톤 구현방법

CGLIB vs Dynamic Proxy

kdhooon commented 1 year ago

Spring의 코드삽입방법

  1. 바이트 코드생성(CGLIB 사용) -> SpringBoot 에서 기본적으로 사용
  2. 프록시 객체 사용 -> Spring에서 기본적으로 사용 Spring AOP는 기본적으로 디자인 패턴 중 하나인 Proxy 패턴을 사용하여 구현되는데, Spring에서 사용하는 두 가지 프록시 구현체가 있다. 하나는 JDK Proxy(=Dynamic Proxy)와 CGLib이다.  

둘의 차이는 다음 그림과 같다.   JDK Proxy

JDK Proxy의 경우 AOP를 적용하여 구현된 클래스의 인터페이스를 프록시 객체로 구현해서 코드를 끼워 넣는 방식  


왜 두가지가 존재하는지?( JDK Proxy vs CGLib Proxy)

Stringboot의 경우 기본적으로 프록시 객체를 생성할 때 CGLib를 사용.   이유는, JDK Proxy가 프록시 객체를 생성할 때 내부적으로 Reflction을 사용하고 있기 때문이다. Reflection는 자체가 비용이 비싼 API이기 때문에 가급적 사용하지 않는 것을 추천하고 있다.   또, JDK Proxy의 경우 AOP 적용을 위해서 반드시 인터페이스를 구현해야한다는 단점이 있다. 그동안 서비스 계층에서 인터페이스 -> XXXimpl 클래스를 작성하던 관례가 이러한 JDK Proxy의 특성 때문   Dynamic Proxy는 InvocationHandler 라는 인터페이스를 구현한다. InvocationHandler의 invoke 메소드를 오버라이딩 하여 Proxy 위임 기능을 수행하는데, 이 때 메소드에 대한 명세와 파라미터를 가져오는 과정에서 리플렉션(reflection)을 사용한다.  


CGLib 이란?

CGLib의 경우 외부 3rd party Library이며 JDK Proxy와는 달리 리플렉션을 사용하지 않고 바이트코드 조작을 통해 프록시 객체생성을 하고 있다.   또, 인터페이스를 구현하지 않고도 해당 구현체를 상속받는 것으로 문제를 해결하기 때문에 성능상에 이점이있다.   CGLib은 Enhancer라는 클래스를 바탕으로 Proxy를 생성한다. 상속을 통해 프록시 객체가 생성되기 때문에 더욱 성능상에 이점을 누릴 수 있다.   기본적으로 프록시 객체들은 직접 원본 객체를 호출하기 보다는, 별도의 작업을 수행하는데 CGLib의 경우 Callback을 사용한다.   CGLib에서 가장 많이 사용하는 콜백은 net.sf.cglib.proxy.MethodInterceptor인데, 프록시와 원본 객체 사이에 인터셉터를 두어 메소드 호출을 조작하는 것을 도와줄 수 있게 된다.

정리

JDK Proxy(Dynamic Proxy)

qazyj commented 1 year ago

문제점

해결방안

싱글톤 패턴

사용 방법

public class SingletonService {

    private static final SingletonService instance = new SingletonService();

    public static SingletonService getInstance() {
        return instance;
    }

    private SingletonService() {
    }
}
  1. static 영역에 객체 instance를 미리 하나 생성
  2. 객체 Instance를 오직 getInstance() 메서드를 통해서만 조회하도록 구현
  3. 생성자를 private로 하여, 객체 외부에서 new 생성자로 객체를 생성할 수 없도록 함

    Singleton 구현 방법은 여러 가지가 있음. 위의 방식은 가장 단순하고 안전한 방법

싱글톤 패턴을 사용하면 메모리 낭비없이 효율적으로 객체를 사용할 수 있는 것을 알 수 있었다. 하지만, 싱글톤 패턴에도 문제점이 존재

싱글톤 패턴 문제점

  1. 싱글톤 패턴 구현 코드가 많이 들어감
  2. 의존관계상 클라이언트가 구현체에 의존. 결과적으로 DIP 위반
  3. 클라이언트가 구현체에 의존하여 OCP 위반할 가능성 높음
  4. 테스트하기 어려움
  5. 내부 속성을 변경하거나 초기화 하기 어려움
  6. private 생성자이기 때문에 자식 클래스 만들기 어려움

    위의 문제로 인하여 유연성이 떨어지게되며, 결과적으로 안티패턴으로 불리기도한다.

문제점 너무 많다. 하지만, 걱정하지마라.손은 눈보다 빠르니까 Spring framework는 싱글톤 패턴의 문제점을 전부 다 해결하면서 사용할 수 있도록 해준다.

싱글톤 컨테이너

싱글톤 방식의 주의점

예시

public class StatefulService {
    private int price;

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

    public int getPrice() {
        return price;
    }
}

    @Test
    void statefulServiceSingleton() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StatefulService statefulService1 = ac.getBean(StatefulService.class);
        StatefulService statefulService2 = ac.getBean(StatefulService.class);

        // ThreadA : A사용자 10000원 주문
        statefulService1.order("userA", 10000);      // StatefulService 필드값 price를 변경

        // ThreadB : B사용자 10000원 주문
        statefulService1.order("userB", 20000);      // StatefulService 필드값 price를 변경

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

statefulService1이 주문한 금액은 10000원이기 때문에 출력도 10000을 기대했다. 하지만, 객체는 1개이기 때문에 Stateful에서는 마지막에 변경한 값인 20000이라는 값을 가지고 있다.

결론 : Spring Bean은 항상 Stateless로 설계하자.

@Configuration과 싱글톤

@Configuration
public class AppConfig
{
    @Bean
    public MemberService memberService()
    {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    private MemberRepository memberRepository()
    {
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService()
    {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public DiscountPolicy discountPolicy()
    {
        return new FixDiscountPolicy();
    }
}

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

bean = class hello.core.AppConfig$$EnhancerBySpringCGLIB$$8790151e

xxxCGLIB가 클래스 명에 들어가는 경우가 있다. 이 경우는 스프링이 라이브러리를 사용하여 임의의 다른 클래스를 만들고, 만든 클래스를 스프링 빈으로 등록한 것이다. 왜 ?

싱글톤을 보장하기 위함. 스프링은 @Bean이 붙은 메소드마다 스프링 빈이 존재하는지 확인하여 있으면 스프링 빈에 등록된 객체를 반환, 없으면 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들면서 싱글톤을 보장한다.

그렇다면, @Configuration을 사용하지 않으면 어떻게 될까?

스프링에서 라이브러리를 통해 따로 동적인 코드를 만들어내지 않고, java 코드만으로 빈을 등록하기 떄문에 싱글톤이 보장되지 않는다.