Open otakijae opened 1 year ago
고객이 요청이 오면 계속해서 객체를 생성해야 함
GVM 메모리에 계속 객체가 생성해서 올라감
웹 어플리케이션 특징: 고객의 요청이 많음. 에를 들어 TPS 5만이면 초당 5만개의 객체가 생성돼야하는 것 -> 메모리 낭비가 심하다.
해당 객체가 딱 1개만 생성되고, 공유하도록 설계하면 됨 -> 싱글톤 패턴
클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴
static : 클래스레벨에 올라가기 때문에 딱 하나만 존재
static 영역에 객체 instance를 미리 하나 생성한다.
이 객체가 필요하면 getInstance() 메서드를 통해서만 조회 가능하다. 이 메서드를 호출하면 항상 같은 인스턴스를 반환한다.
생성자를 private으로 막아서 혹시라도 외부에서 new 키워드로 객체 인스턴스가 생성되는 것을 막는다.
싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
의존관계상 클라이언트가 구체 클래스에 의존한다. -> DIP를 위반한다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
// .getInstance() 로 가져오는 MemberServiceImpl 내의 private static final ... = new memberReository(); 인해서 구체 클래스에 의존하게 됨.
return new MemberServiceImpl.getInstance(memberRepository());
}
}
클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
테스트하기 어렵다.
내부 속성을 변경하거나 초기화하기 어렵다.
private 생성자로 자식 클래스르 만들기 어렵다.
결론적으로 유연성이 떨어진다.
안티패턴으로 불리기도 한다.
둘의 차이는 다음 그림과 같다.
JDK Proxy의 경우 AOP를 적용하여 구현된 클래스의 인터페이스를 프록시 객체로 구현해서 코드를 끼워 넣는 방식
Stringboot의 경우 기본적으로 프록시 객체를 생성할 때 CGLib를 사용. 이유는, JDK Proxy가 프록시 객체를 생성할 때 내부적으로 Reflction을 사용하고 있기 때문이다. Reflection는 자체가 비용이 비싼 API이기 때문에 가급적 사용하지 않는 것을 추천하고 있다. 또, JDK Proxy의 경우 AOP 적용을 위해서 반드시 인터페이스를 구현해야한다는 단점이 있다. 그동안 서비스 계층에서 인터페이스 -> XXXimpl 클래스를 작성하던 관례가 이러한 JDK Proxy의 특성 때문 Dynamic Proxy는 InvocationHandler 라는 인터페이스를 구현한다. InvocationHandler의 invoke 메소드를 오버라이딩 하여 Proxy 위임 기능을 수행하는데, 이 때 메소드에 대한 명세와 파라미터를 가져오는 과정에서 리플렉션(reflection)을 사용한다.
CGLib의 경우 외부 3rd party Library이며 JDK Proxy와는 달리 리플렉션을 사용하지 않고 바이트코드 조작을 통해 프록시 객체생성을 하고 있다. 또, 인터페이스를 구현하지 않고도 해당 구현체를 상속받는 것으로 문제를 해결하기 때문에 성능상에 이점이있다. CGLib은 Enhancer라는 클래스를 바탕으로 Proxy를 생성한다. 상속을 통해 프록시 객체가 생성되기 때문에 더욱 성능상에 이점을 누릴 수 있다. 기본적으로 프록시 객체들은 직접 원본 객체를 호출하기 보다는, 별도의 작업을 수행하는데 CGLib의 경우 Callback을 사용한다. CGLib에서 가장 많이 사용하는 콜백은 net.sf.cglib.proxy.MethodInterceptor인데, 프록시와 원본 객체 사이에 인터셉터를 두어 메소드 호출을 조작하는 것을 도와줄 수 있게 된다.
사용 방법
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
private SingletonService() {
}
}
Singleton 구현 방법은 여러 가지가 있음. 위의 방식은 가장 단순하고 안전한 방법
싱글톤 패턴을 사용하면 메모리 낭비없이 효율적으로 객체를 사용할 수 있는 것을 알 수 있었다. 하지만, 싱글톤 패턴에도 문제점이 존재
위의 문제로 인하여 유연성이 떨어지게되며, 결과적으로 안티패턴으로 불리기도한다.
문제점 너무 많다. 하지만, 걱정하지마라.
손은 눈보다 빠르니까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
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 코드만으로 빈을 등록하기 떄문에 싱글톤이 보장되지 않는다.
@Configuration과 바이트코드 조작의 마법
xxxxCGLIB
class hello.core.AppConfig