1. 글을 작성하는 이유

이번 글에서는 제가 직접 겪었던 서블릿 간 데이터 공유 문제를 정리해보려고 합니다.

프로젝트에서 메뉴를 등록하고, 목록을 조회하는 기능을 구현하면서 등록한 메뉴가 조회 페이지에 나타나지 않는 상황을 마주했습니다.
처음에는 단순한 코드 버그라고 생각했지만, 문제를 들여다보니 서블릿마다 생성된 MenuService 인스턴스가 달라서 데이터가 공유되지 않는 것이 핵심 원인이었습니다.

문제 해결 과정 기록과 함께 서블릿과 싱글턴 패턴을 적용할 때 발생할 수 있는 함정을 정리합니다.


2. 문제 상황 설명

처음에는 각각의 서블릿에서 이렇게 MenuService를 생성했습니다.

private static final MenuService menuService = new MenuService();
  • MenuRegisterController에서는 메뉴 등록 기능 담당
  • MenuSearchController에서는 메뉴 조회 기능 담당

각 서블릿에서 menuService를 새로 생성했기 때문에, 등록된 메뉴 데이터는 Register 서블릿의 안에만 저장되었습니다.
그 결과, 메뉴를 조회하면 항상 빈 리스트가 나왔습니다.


3. 내가 했던 고민들

이 상황에서 내가 가졌던 의문은 다음과 같습니다. 

 

“왜 데이터를 등록했는데 조회하면 안 나올까?”

단순히 메뉴를 등록하고, list 서블릿에서 조회하면 바로 보여질 줄 알았습니다.

서버 쪽 로직을 잘못 쓴 줄만 생각했고 문제가 단순한 코드 오류가 아니라 객체 참조와 서블릿 구조에 있다는 점을 몰랐습니다.

 

“두 서블릿에서 같은 MenuService를 사용해야 하는 걸까?”

데이터가 공유되지 않는 이유를 고민하면서 떠오른 의문입니다.

“혹시 index와 list 서블릿이 각각 독립적으로 MenuService를 가지고 있어서 서로 다른 공간에 데이터를 저장하는 건 아닐까?”
이 질문을 던지면서 처음으로 서블릿 간 서비스 객체의 공유 여부가 중요하다는 것을 깨달았습니다.

“static final을 사용했는데도 공유가 된다고?”

static final로 MenuService를 선언하면, 모든 서블릿에서 같은 인스턴스를 공유할 수 있을 거라고 생각했습니다.

'한 번 선언했으니 어디서든 동일한 객체가 쓰이겠지'라는 단순한 논리였습니다. 

하지만 서블릿 컨테이너는 멀티스레드 환경에서 클래스 로더를 통해 각각의 서블릿을 관리하기 때문에, 단순한 static final 선언만으로는 완전한 싱글턴을 보장할 수 없다는 사실을 알게 되었습니다.


4. 문제 원인 분석

핵심 원인은 서블릿마다 MenuService 인스턴스를 별도로 생성했다는 점입니다.

  • Register 서블릿에서 새 메뉴를 넣어도
    Search 서블릿에서는 다른 MenuService 인스턴스를 사용하기 때문에 데이터가 존재하지 않음
[MenuRegisterController] ---> MenuService 인스턴스 A ---> 메뉴 등록
[MenuSearchController]   ---> MenuService 인스턴스 B ---> 메뉴 조회 (인스턴스 B에는 데이터 없음)

 

단순히 static final로 선언하면 인스턴스가 한 번만 생성되는 것 같지만, 서블릿 컨테이너 환경에서는 멀티스레드와 클래스 로딩 방식 때문에 안전하게 모든 서블릿이 같은 데이터를 공유하지 못할 수 있습니다.

 

* 멀티스레드와 클래스 로딩 방식

더보기
더보기
  • 멀티서블릿 환경
    • 서블릿 컨테이너는 하나의 서비스 요청마다 스레드를 새로 만들어 처리합니다.
    • 여러 스레드가 동시에 menuService에 접근하면, 객체 초기화 시점이나 참조가 꼬일 수 있습니다.
    • static final은 JVM 레벨에서 클래스 로딩 시 단 한 번만 초기화되지만, 서블릿 컨테이너가 클래스 로딩을 다르게 처리하거나, 재배포(reload) 시점에는 새롭게 초기화될 수 있습니다.
  • 클래스 로딩 문제
    • 톰캣 같은 서블릿 컨테이너는 각 웹 애플리케이션마다 별도의 클래스 로더를 사용합니다.
    • 즉, 서로 다른 서블릿이 같은 클래스 이름을 가진 MenuService를 각각의 클래스 로더에서 로딩하면, 사실상 다른 인스턴스로 존재하게 됩니다.
    • 그래서 static 필드가 있어도 “서블릿 간 공유”가 완벽하게 보장되지 않습니다.

5. 해결 방법

MenuService를 싱글턴 패턴으로 만들어 서블릿 전체에서 같은 인스턴스를 공유하도록 수정했습니다.

public class MenuService { 

    private static final MenuService instance = new MenuService(); 
    private final List<Menu> menuList = new ArrayList<>(); 

    private MenuService() {} 
    public static MenuService getInstance() { 
        return instance; 
    } 

}
 
 
그리고 각 서블릿에서는 이렇게 인스턴스를 가져오도록 했습니다.
private static final MenuService menuService = MenuService.getInstance();

 


6. 결론

이번 문제를 통해 배운 점은 다음과 같습니다.

  1. 서블릿은 멀티스레드 환경에서 동작하므로, 단순히 new로 객체를 생성하면 서블릿마다 별도의 인스턴스가 만들어진다.
  2. 여러 서블릿에서 공통 데이터를 관리하려면 싱글턴 패턴 같은 공유 방법을 사용해야 한다.
  3. 문제를 단순히 “조회가 안 된다”로만 판단하면 근본 원인을 놓치게 된다.
    데이터 흐름과 인스턴스 관리까지 함께 고려해야 한다.

이 경험 덕분에, 앞으로 서블릿 기반 프로젝트에서 인스턴스 관리와 데이터 공유 문제를 보다 체계적으로 설계할 수 있을 것 같습니다.

+ Recent posts