Hazelcast는 기본적으로 Hazelcast 자체 ClassLoader (HazelcastInstance가 띄운 스레드의 ClassLoader)를 사용해 클래스를 찾는데, SpringBoot 애플리케이션처럼 다른 ClassLoader 구조를 가진 환경에서는 이 ClassLoader가 애플리케이션 클래스 (예: 내부 클래스, DTO 등)을 못 찾을 때가 있습니다.
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 등 사용자 정의 클래스도 정상적으로 로드할 수 있게 됩니다.
로컬에서는 Hazelcast와 애플리케이션이 같은 ClassLoader를 공유했고, 서버에서는 다른 ClassLoader를 사용했기 때문에 문제가 발생합니다.
IDE에서 실행 시 Spring Boot가 gradle/maven의 classpath 기반으로 실행합니다.
그래서 이 경우 Hazelcast가 사용하는 기본 ClassLoader와 Spring 애플리케이션의 ClassLoader가 동일해 문제가 발생하지 않습니다.
Spring의 스케줄러(@Scheduled)는 내부적으로 Spring 관리 하의 스레드 풀(TaskScheduler)에서 실행됩니다.
이 스레드는 Spring Boot ApplicationContext가 시작될 때 만들어지고,
그 시점에서 이미 올바른 ClassLoader (LaunchedURLClassLoader)가 설정되어 있습니다.
ClassNotFoundException 재발HazelcastClient.newHazelcastClient)를 쓰고 있다면, client와 server의 ClassLoader 설정이 다르다면 충돌 가능성이 있습니다.newHazelcastInstance(config)만 쓰고 있으니 해당하지 않습니다.server
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);
}
HazelcastInstance를 빈으로 등록할 때 ClientConfig를 사용인 경우 이 코드를 추가하여도 에러가 발생하지 않습니다.