SpringBoot에서 Hazelcast사용시 JAR안의 Innerclass를 인식하지 못하는 문제

henu·2026년 4월 17일

1. 문제 원인

Hazelcast는 기본적으로 Hazelcast 자체 ClassLoader (HazelcastInstance가 띄운 스레드의 ClassLoader)를 사용해 클래스를 찾는데, SpringBoot 애플리케이션처럼 다른 ClassLoader 구조를 가진 환경에서는 이 ClassLoader가 애플리케이션 클래스 (예: 내부 클래스, DTO 등)을 못 찾을 때가 있습니다.

2. 해결 방법

Spring Boot는 내부적으로 자체적인 CloassLoader (LaunchedURLClassLoader)를 써서 JAR 안의 클래스들을 로딩합니다. 즉 Hazelcast가 띄운 기본 ClassLoader는 이 클래스를 인식하지 못하는 상황이었습니다.

Thread.currentThread().getContextClassLoader()는 현재 실행 중인 스레드의 컨텍스트 ClassLoader, 즉 Spring Boot가 실제로 사용하는 ClassLoader를 반환합니다.

따라서

config.setClassLoader(Thread.currentThread().getContextClassLoader());

이 설정을 통해 HazelCast가 내부적으로 클래스를 로드할 때, Spring Boot의 ClassLoader를 사용하게 되고 JAR 안의 내부 클래스, DTO 등 사용자 정의 클래스도 정상적으로 로드할 수 있게 됩니다.

3. 로컬에서는 에러가 발생하지 않은 이유

로컬에서는 Hazelcast와 애플리케이션이 같은 ClassLoader를 공유했고, 서버에서는 다른 ClassLoader를 사용했기 때문에 문제가 발생합니다.

IDE에서 실행 시 Spring Boot가 gradle/maven의 classpath 기반으로 실행합니다.

  • .class 파일들이 /target/classes나 /build/classes/java/main 폴더에 그대로 존재하고
  • 모든 라이브러리는 IDE가 하나의 Application ClassLoader로 합쳐서 실행해줍니다.

그래서 이 경우 Hazelcast가 사용하는 기본 ClassLoader와 Spring 애플리케이션의 ClassLoader가 동일해 문제가 발생하지 않습니다.

4. 스케줄러에서도 에러가 발생하지 않은 이유

Spring의 스케줄러(@Scheduled)는 내부적으로 Spring 관리 하의 스레드 풀(TaskScheduler)에서 실행됩니다.

이 스레드는 Spring Boot ApplicationContext가 시작될 때 만들어지고,

그 시점에서 이미 올바른 ClassLoader (LaunchedURLClassLoader)가 설정되어 있습니다.

5. 이 설정으로 인해 유발될 수 있는 에러 (잠재적 리스크)

  1. 다중 서버 사용 시 Hazelcast 클러스터 간 ClassLoader 불일치
    • 각 노드의 ClassLoader가 동일하지 않으면 역직렬화 실패 가능성이 있습니다.
    • 예를 들어
      • 서버 1은 Spring Boot A 버전
      • 서버 2는 Spring Boot B 버전 (패키지 구조 다름)
      • → 이때 서로 다른 ClassLoader를 지정하면 ClassNotFoundException 재발
    • 단일 서버일 경우 문제 없음
  2. Hazelcast client/server 혼용 환경
    • 만약 Hazelcast client 모드(HazelcastClient.newHazelcastClient)를 쓰고 있다면, client와 server의 ClassLoader 설정이 다르다면 충돌 가능성이 있습니다.
    • 현재 프로젝트에서는 newHazelcastInstance(config)만 쓰고 있으니 해당하지 않습니다.
    • 현재 구조와의 차이 MSA와 같은 환경에서 각 서비스를 독립적으로 배포하지만, 캐시를 공유할 때 사용합니다.
      1. Server 역할
        • 데이터를 직접 저장하고 관리
      2. Client 역할
        • Hazelcast 서버에 접속해서 캐시 데이터만 사용
        • 데이터를 직접 갖지 않고, 서버에 요청을 보내고 결과를 받음
        • 서버와 달리 클러스터 멤버로 참여하지 않음 → 서버 다운 시 직접 데이터는 없음
        • 가벼운 리소스로 다수 애플리케이션에서 연결 가능
      3. config 설정 방법
        1. server

          • 기존과 동일
        2. client

              @Bean
              public HazelcastInstance hazelcastClient() {
                  ClientConfig clientConfig = new ClientConfig();
          
                  // 클러스터 이름
                  clientConfig.setClusterName("remarkablesoft-cache-cluster");
          
                  // 클래스 로더 명시
                  clientConfig.setClassLoader(Thread.currentThread().getContextClassLoader());
          
                  // 서버 IP/포트
                  clientConfig.getNetworkConfig().addAddress(
                      "211.106.81.22:5701",
                      "211.106.81.23:5702"
                  );
          
                  // 필요 시 인증/SSL 설정
                  // clientConfig.getSecurityConfig().setUsernamePassword(...);
          
                  return HazelcastClient.newHazelcastClient(clientConfig);
              }
      • Spring 애플리케이션은 Hazelcast Client로 동작
      • 캐시는 별도 Hazelcast Server 클러스터에서 관리
      • Spring Boot에서는 HazelcastInstance를 빈으로 등록할 때 ClientConfig를 사용
      • 장점:
        • 서비스와 캐시 서버 분리 가능 → MSA 환경에서 다른 서비스와도 독립적
        • 서버 재배포시 캐시 서버에는 영향이 없어 데이터가 그대로 남아있음
      • 단점:
        • 네트워크를 거치므로 접근 속도가 Embedded보다 느릴 수 있음
  • 단일 서버
  • Spring Boot 실행 환경
  • Hazelcast 인스턴스 1개만 존재

인 경우 이 코드를 추가하여도 에러가 발생하지 않습니다.

profile
주니어 백엔드 개발자입니다

0개의 댓글