들어가며

  • 이전 글에서는 스프링 프레임워크에서 생성하고 관리하는 스프링 DI 컨테이너(이하 스프링 컨테이너)가 어떻게 Bean을 조회하고, 어떠한 문제점이 발생할 수 있으며 그것을 어떻게 해결할 수 있는지에 대해 알아보았다.
  • 이번 포스트에서는 이 Bean들이 스프링 컨테이너 내부에서 어떻게 관리되고 있는지에 대해서 알아본다.
 

Spring Framework - Spring Bean 조회

들어가며 이전 포스트 에서 스프링에서 자체적으로 만들고 관리하는 스프링 DI 컨테이너에 대해 알아보았다. 스프링 DI 컨테이너에서 @Bean으로 등록한 모든 객체들을 관리하는 동시에 DI가 필요

sehun5515.tistory.com

 

 

 

웹 애플리케이션에서 발생하는 DI 컨테이너의 문제

  • 스프링을 사용하지 않은 DI 컨테이너인 AppConfig을 다시 보도록 하자.
public class AppConfig{
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }

    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
    
    public MemberRepository memberRepository(){
        return new MemoryMemberService()
    }
    
    public DiscountPolicy discountPolicy(){
        return new FixDiscountPolicy();
    }
}
  • 일반적인 웹 애플리케이션은 많은 사용자들이 동시에 동일한 요청을 하는 경우가 존재한다.
  • 예를 들어 위 코드에서 사람들이 새로운 주문을 하기 위해서 orderService를 여러번 사용해야 한다고 가정한다면 DI컨테이너인 AppCofig는 다음과 같은 과정을 수행할 것이다.
    • 우선 새로운 OrderService 구현체를 생성하기 위해서 memberRepository와 discountPolicy를 가져와야 한다. 이 둘 역시 DI 컨테이너인 AppConfig에 존재하므로 새로운 객체를 생성하여 반환한다.
    • 이후에 새로운 OrderServiceImpl 객체를 만들어 반환한다.
  • 문제는 여러 고객이 주문을 처리하는 경우이다. 각각의 주문에 대해서 DI 컨테이너인 AppConfig는 새로운 객체들을 계속해서 생성한다.
  • 문제는 이렇게 새로운 객체를 각 주문 요청마다 만들어 주는게 메모리나 시간 코스트에서 좋지 않다는 문제가 있다. 새로운 객체를 만들면 어찌됬던 JVM의 힙 영역 메모리를 잡아먹게 되고 GC가 존재하지만 메모리 사용면에서 좋지 않다.
  • 또한 이러한 객체를 생성하는 동안의 시간이 투자된다. 물론 OrderServiceImpl을 생성하는동안 필요한 memberRepository와 discountPolicy를 생성하는 시간도 투자된다.
  • 여기서 생각을 전환시켜보자. 이미 객체가 존재한다면 그냥 그것을 바로 넘겨버리면 해결된다. 이미 존재하는 객체의 참조주소만 넘겨주면 되기 때문에 위의 객체를 생성하는 일보다 시간 면에서도 매우 좋고, 새로운 객체를 만들 필요가 없으니 메모리 코스트면에서도 매우 좋아보인다.

 

 

 

Singleton 패턴

  • 싱글톤 패턴은 해당 패턴을 적용한 클래스의 인스턴스가 단 1개만 생성되는 것을 보장하는 디자인 패턴이다.
  • 클래스의 인스턴스가 단 1개만 생성되는것을 보장하기 위해서는 constructor을 다른 클래스에서 사용하지 못하도록 제한시켜야 할 필요가 있다. 즉 private으로 지정해야할 필요가 있다.
  • 일반적으로 싱글톤 패턴은 다음과 같은 코드로 구현된다.
public class SingletonTest{
    public static final SingletonTest singletonTest = new SingletonTest();
    
    public static SingletonTest getInstance(){
        return singletonTest;
    }
    
    private SingletonTest(){}
    
    public void logic(){
        ...
    }
}
  • static 변수를 통해 프로그램이 실행될 시 해당 객체가 자신을 호출하여 그 객체를 가지고 있는다.(private는 자기 자신만 사용이 가능하므로 가능한 동작이다.)
  • 이후 생성자를 private으로 지정하여 다른 클래스에서 해당 클래스의 인스턴스를 생성하는것을 제한시킨다. 다른 클래스에서 해당 클래스의 인스턴스를 받기 위해서는 getInstance()를 통해 사용해야 한다. 짤막한 예시를 보면 다음과 같다.
public class Test{
    public static void main(String[] args){
        SingletonTest singletonTest1 = SingletonTest.getInstance();
        SingletonTest singletonTest2 = SingletonTest.getInstance();
        
        ...
    }
}
  • 위의 두 SingletonTest 인스턴스들은 동일한 인스턴스이다. 새로운 객체의 생성을 막고 이미 존재하는 인스턴스를 반환하기 때문이다.

 

 

 

Singleton - Pros and Cons

  • 싱글톤 패턴의 장점(Pros)는 다음과 같다.
    • 동일한 객체를 각 요청마다 계속해서 생성하여 반환하지 않고, 이미 존재하는 인스턴스를 반환하기 때문에 객체를 생성하는 시간과 자원이 필요하지 않다.
    • 사실 이 특성은 JDBC DAO 등 어떠한 객체가 굳이 새로운 객체가 필요 없이 동일한 작업들을 여러번 수행하는 일일수록 효과가 강력해진다.
  • 싱글톤 패턴의 단점(Cons)은 다음과 같다.
    • 싱글톤 패턴을 구현하는데 코드가 많이 사용된다,
      • 이는 getInstance를 구현하는것 부터 static으로 인스턴스를 초기에 올리는 것 까지 기존의 클래스에서는 없어도 되는 코드들이 들어간다는 것이다.
    • 의존관계에서 클라이언트가 구체 클래스에 의존한다.
      • 위의 싱글톤 사용 코드를 보자. 인스턴스를 요청할 때 SingletonTest.getInstance()로 구체 클래스를 직접 지정하고 있는 것을 확인할 수 있을 것이다.
      • 문제는 이렇게 될 시에 우리가 이전 포스트에서 다루었던 DIP에 정면으로 위배된다는 것이다. 우리는 인터페이스에 의존하도록 코드를 구현해야하는데 싱글톤을 사용함으로서 구체 클래스에 의존하도록 되어버렸다.
      • 또한 동일한 이유로 OCP에 대해서도 위반될 가능성이 매우 높아진다.
    • private 생성자로 인하여 자식 클래스를 만들기 어렵다.
      • 말 그대로 private 생성자로 인해 싱글톤 패턴을 적용시킨 클래스를 상속받는 다른 자식 클래스를 만들기가 매우 까다롭다.(자식 생성자가 부모 생성자를 호출해야 하는 상황이 올 시에 방법이 딱히 없다.)
    • 내부 속성을 변경하거나 초기화하기 어렵다.
      • 싱글톤 패턴은 기본적으로 하나의 인스턴스를 여러 곳에서 돌려쓰는 것에 대해 효율적으로 설계되어있는 디자인 패턴이다.
      • 문제는 하나의 인스턴스를 여러 곳에서 돌려쓰는 특성으로 인하여 공용적으로 사용하는 속성(Field)을 이용해야하며 특정 객체에 특화된 속성들이 사용되면 안된다.
      • 마찬가지의 이유로, 어떠한 값을 수정하거나 초기화하는데에 주의가 요구된다. 속성값이 변경될 시 연관되어 있는 모든 클래스들 역시 영향이 미치기 때문이다.
    • 상기한 이유들로 인하여 해당 패턴을 사용하면 유연성이 떨어진다.
      • OCP, DIP의 위배로 인하여 변경 시에 클라이언트 코드를 변경해야 하는 문제가 발생한다.
      • private 생성자로 인하여 해당 클래스에 대한 유연한 확장이 어려워진다.
      • 속성에 대한 초기화 및 변경이 자유롭지 않다.

 

 

 

정리

  • 일반적으로 구현한 위의 AppConfig는 동일한 요청이 들어와도 계속해서 객체를 생성하는 문제점이 존재하였다.
  • 객체의 생성으로 인한 메모리 낭비 및 시간 오버헤드가 문제가 되었고 이는 하나의 객체를 여러 곳에서 사용할 수 있도록 하는 생각을 하게 되었다.
  • 해당 생각을 구현한 것이 싱글톤 패턴으로 하나의 인스턴스를 만들어 해당 인스턴스를 여러 곳에서 사용할 수 있도록 하는 패턴이다. 문제는 이 방식이 꽤 단점이 많다는 것이다.
  • 다음 포스트에서는 이 싱글톤 패턴과 스프링 컨테이너의 연관성에 대해서 알아본다.
복사했습니다!