져니의 개발 정원 가꾸기

Java AWS SDK 2 DynamoDB 모듈과 Spring Boot DevTools를 같이 쓸 때 주의점 (feat. 클래스로더 충돌) 본문

개발노트/dynamoDB

Java AWS SDK 2 DynamoDB 모듈과 Spring Boot DevTools를 같이 쓸 때 주의점 (feat. 클래스로더 충돌)

전전쪄니 2024. 9. 1. 13:08

이슈

AWS SDK 2 DynamoDB 모듈의 DynamoDbEnhancedClient 클래스와 Spring Boot DevTools를 같이 사용하니 클래스가 있는데도 같은 타입으로 캐스팅할 수 없다는 런타임 에러가 발생했다.

class com.dynamo.practice.db.User cannot be cast to class com.dynamo.practice.db.User (com.dynamo.practice.db.User is in unnamed module of loader 'app'; com.dynamo.practice.db.User is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @23e19c8e)
java.lang.ClassCastException: class com.dynamo.practice.db.User cannot be cast to class com.dynamo.practice.db.User (com.dynamo.practice.db.User is in unnamed module of loader 'app'; com.dynamo.practice.db.User is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @23a19c8e)

 

원인

DynamoDbEnhancedClient와 @DynamoDbBean을 같이 사용할 때 직접 개발한 클래스(com.dynamo.practice.db.User)가 두 개의 클래스 로더에 로드되었기 때문에 발생한 문제이다.

 

Spring boot devTools 는 재시작시 구동시간을 단축하기 위해 2개의 클래스 로더(base classloader, restart classloader)를 사용한다. 3rd party 클래스들은 base classloader에 로드되고 직접 개발한 클래스들을 restart classloader에 로드하는데 애플리케이션을 재시작할 때 base classloader는 그대로 사용하고 restart classloader만 지웠다 다시 생성하여 로드한다.

 

예상한대로(?) dynamoDb에 관련된 써드파티 라이브러리들은 base classoader 에 로드되는 반면 (에러 메시지에서 볼 수 있듯이) 직접 개발한 (@DynamoDbBean이 붙은) 다이나모디비 매핑 객체는 restart classloader에 로드되어야한다.

 

그런데 Spring Boot DevTools를 사용하면 User 클래스는 Base ClassLoader와 RestartClassLoader 양쪽에 로드되고, JVM은 이 두 클래스를 다른 타입으로 간주해버린다. DynamoDbEnhancedClient는 Base ClassLoader에서 로드된 User 클래스를 참조하지만 애플리케이션 코드는 Restart ClassLoader에서 로드된 User 객체를 사용하려고 한다. 사용자 입장에서는 User 클래스가 하나라고 보일지라도 DynamoDbEnhancedClient가 애플리케이션 코드의 User클래스를 사용하기 위해서는 타입 변환이 필요하다. 안타깝게도 라이브러리 내부적으로 타입 변환을 지원하진 않은듯하다. 그래서 ClassCastException이 발생하는 것이다.

요약하자면, DynamoDbEnhancedClient가 User 객체를 반환하거나 이 객체를 인자로 받는 메서드를 호출할 때, JVM이 서로 다른 클래스 로더에 의해 로드된 두 개의 객체들을 다른 타입으로 간주하여 User객체의 타입 변환을 시도하기 때문에 예외가 발생한다.

 

클래스 로드 문제에 대해 구글링을 하던 중에 다음과 같은 Github 이슈를 발견했다. 많은 내용들이 오갔지만, 요약하자면 아직까지도 라이브러리 내부적으로 이슈가 해결되지 않은 상태이다. 지금으로서는 DynamoDbEnhancedClient 의 버그가 고쳐지 않았기 때문에 사용자 측에서 번거롭게 사용해줘야 한다! ^^

 

> https://github.com/aws/aws-sdk-java-v2/issues/2604

 

DynamoDb-enhanced NoClassDefFoundError with different ClassLoaders · Issue #2604 · aws/aws-sdk-java-v2

The BeanTableSchema of dynamodb-enhanced does not work correctly when this library and the bean class reside in different classloaders. A NoClassDefFoundError for the bean class is thrown, when a c...

github.com

 

해결

dynamoDbEnhancedClient의 클래스 로더 충돌 문제에서 취할 수 있는 해결 방법은 크게 세 가지이다.

 

(application.yml에서 Spring Boot DevTools Restart기능을 비활성화하는 설정을 추가하는 방법도 있는데, 나는 이걸로 해결이 안되었어서 이 부분은 생략했다.)

1. spring-boot-devtools 의존성 제외시키기

방법은 간단하다. build.gradle에서 의존성을 제거해준다.

> spring-boot-starter-web에서 의존성을 제외하는 방법 

// build.gradle
... 
    dependencies {
        implementation 'io.awspring.cloud:spring-cloud-aws-starter-dynamodb'
        implementation('org.springframework.boot:spring-boot-starter-web') { 
           exclude group: 'org.springframework.boot', module: 'spring-boot-devtools'}
    }
...

 

그런데 DevTools를 사용하는 이유는 로컬에서 개발할 때 편하기 위함인데 이렇게 DevTools사용을 포기하면 구동시간이 느려지는 불편함을 감수해야 한다. dynamoDB 모듈을 사용하기 위해 모든 써드파티들도 재시작할 때마다 전부 다시 로드해야한다니 치러야 할 대가가 크다고 생각한다.

2. 다이나모디비의 써드파티 클래스들을 restart classloader에 로드시키기

다이나모디비 관련 써드파티 클래스들만 restart classloader에 로드시키고, 그 외 다른 써드파티 클래스들은 그대로 base classloader에 로드하는 방법이다. (나의 경우 이 방법으로 해결했다.)

 

> resources/META-INF 하위에 다음과 같은 내용의 spring-devtools.properties 파일을 만든다. (resources/META-INF/spring-devtools.properties)

(.yml안 된다! 꼭 .properties 확장자로 생성할 것!)

restart.include.dynamodb=/dynamodb-[\\w\\d-\.]+\.jar

3. 테이블 스키마 수동하여 정의하기 

다이나모디비 매핑 객체에서 @DynamoDbBean을 사용하지 않고 수동으로 TableSchema 빈 정의를 하는 방법이다. 공식문서에서는 이 방법으로 해결하라고 한다. 근데 매핑 객체가 많아지면 일일이 또 빈을 생성해줘야하니 개인적으로는 다소 비효율적이라고 생각한다.

@Configuration
public class UserTableSchemaConfiguration {

    @Bean
    public TableSchema<User> UserTableSchema() {
        // User class 의 테이블 스키마 리턴 - 주로 StaticTableSchema.builder()를 사용
    }
}

 

세부 내용은 아래 공식 문서 참고하자

> https://docs.awspring.io/spring-cloud-aws/docs/3.1.0/reference/html/index.html#resolving-table-schema

 

Spring Cloud AWS

Secrets Manager helps to protect secrets needed to access your applications, services, and IT resources. The service enables you to easily rotate, manage, and retrieve database credentials, API keys, and other secrets throughout their lifecycle. Spring Clo

docs.awspring.io

 

 

참고