Programming/Kotlin

[Kotlin] Kotlin과 Spring 환경에서 JSR-305란?

기록하는 백앤드개발자 2024. 11. 7. 23:07
반응형

ㅁ 들어가며

kotlinOptions {
    freeCompilerArgs = listOf("-Xjsr305=strict")
    jvmTarget = "17"
}


이 글은 내가 자주 보았던 위의 옵셩에 대해서 정리하였다. 즉, Kotlin과 Spring 환경에서 JSR-305 지원에 대해 정리한다.

 

ㅁ JSR-305란 무엇인가요?

https://kotlinlang.org/docs/java-interop.html#jsr-305-support

 JSR-305는 Java에서 널(null) 안정성을 향상시키기 위한 어노테이션 표준이다. 이 표준은 코드의 의도를 명확히 하고 널 관련 버그를 줄이는 데 도움을 준다.

ㅁ Kotlin에서 JSR-305의 중요성

  Kotlin은 기본적으로 널 안정성을 제공하지만, Java와의 상호 운용성을 위해 JSR-305 지원이 필요하다. Java 코드에서 사용된 JSR-305 어노테이션을 Kotlin이 이해하고 활용할 수 있게 해준다.

ㅁ Spring에서의 JSR-305 활용

  Spring Framework는 JSR-305 어노테이션을 적극적으로 활용하여 Spring의 널 안정성 어노테이션을 통해 API의 널 안정성을 향상시켰다. 이는 Kotlin 개발자들에게 큰 이점을 제공한다. 

  1. 컴파일 시간 널 안정성: Java API를 사용할 때도 Kotlin의 널 안정성 기능을 최대한 활용할 수 있다.
  2. 명확한 API 계약: 메서드의 파라미터나 반환 값이 널일 수 있는지 명확히 알 수 있다.
  3. 버그 감소: 널 관련 런타임 오류를 컴파일 시간에 잡아낼 수 있어 버그를 크게 줄일 수 있다.

 Spring은 구체적으로 org.springframework.lang 패키지에 null관련 어노테이션을 제공한다.

   @Nullable: 값이 널일 수 있음

   @NonNull: 값이 절대 널이 될 수 없음

 

  이러한 어노테이션은 Kotlin 코드에서 자동으로 인식되어 컴파일러 수준의 널 안정성 검사를 가능하게 한다.

 

ㅁKotlin에서 JSR-305 설정하기

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
    }
}

ㅇ Kotlin에서 JSR-305 지원을 활성화하려면 컴파일러 설정을 추가해야 한다. 이는 build.gradle.kts 파일에서 수행할 수 있다.

ㅇ 이 설정은 JSR-305 어노테이션을 엄격하게 처리하도록 Kotlin 컴파일러에 지시한다.

ㅁ JSR-305의 다양한 모드

  1. strict: 가장 엄격한 모드로, 모든 널 안정성 위반을 오류 처리
  2. warn: 널 안정성 위반 시 경고
  3. ignore: JSR-305 어노테이션을 완전히 무시

 

ㅁ 실제 사용 예시

@Service
class UserService(private val userRepository: UserRepository) {

    fun getUser(@NonNull username: String): User? {
        return userRepository.findByUsername(username)
    }

    @NonNull
    fun createUser(@NonNull user: User): User {
        return userRepository.save(user)
    }
}

ㅇ @NonNull은 해당 파라미터가 널이 될 수 없음을 나타낸다.

ㅇ Kotlin 컴파일러는 이를 인식하고 적절한 널 안정성 검사를 수행한다.

 

ㅁ JSR-305와 Kotlin의 플랫폼 타입

ㅇ Kotlin에서 Java 코드를 사용할 때, 기본적으로 모든 타입은 "플랫폼 타입"으로 취급된다.

ㅇ 플랫폼 타입은 널 가능성이 불확실한 타입을 의미한다.

ㅇ 하지만 JSR-305 어노테이션을 사용하면 이러한 불확실성을 제거할 수 있다.

ㅇ 예를 들어, 다음과 같은 Java 코드가 있다고 가정해보자.

public class JavaClass {
    @Nullable
    public String getNullableString() {
        // ...
    }

    @NonNull
    public String getNonNullString() {
        // ...
    }
}

 

ㅇ Kotlin에서 이 클래스를 사용할 때, JSR-305 지원이 활성화되어 있다면 다음과 같이 처리된다.

val javaClass = JavaClass()

val nullableString: String? = javaClass.nullableString
val nonNullString: String = javaClass.nonNullString

ㅇ 컴파일러는 @Nullable@NonNull을 인식하여 적절한 Kotlin 타입으로 변환한다.

 

ㅁ Spring Data JPA와 JSR-305

ㅇ Spring Data JPA에서도 JSR-305 어노테이션이 널리 사용된다.

interface UserRepository : JpaRepository<User, Long> {
    fun findByUsername(@NonNull username: String): User?
}

 ㅇ findByUsername 메서드의 username 파라미터는 절대 널이 될 수 없음을 나타낸다.

ㅇ Kotlin 컴파일러는 이를 강제하여 널 값이 전달되는 것을 방지한다.

 

ㅁ JSR-305의 한계와 주의사항

ㅇ JSR-305는 매우 유용하지만, 몇 가지 한계와 주의사항이 있다.

  1. 제네릭 타입 인자: 현재 JSR-305는 제네릭 타입 인자의 널 가능성을 완벽하게 처리하지 못한다.
  2. 배열 요소: 배열 요소의 널 가능성도 완벽하게 처리되지 않는다.
  3. 라이브러리 의존성: JSR-305 어노테이션을 사용하려면 해당 라이브러리가 클래스패스에 있어야 한다.
  4. 성능 영향: 엄격한 JSR-305 검사를 활성화하면 컴파일 시간이 약간 증가할 수 있다.

 

ㅁ JSR-305와 커스텀 어노테이션

 ㅇ JSR-305는 커스텀 널 안정성 어노테이션을 정의할 수 있는 기능을 제공한다.

 ㅇ 이는 @TypeQualifierNickname과 @TypeQualifierDefault 어노테이션을 통해 가능하다.

 ㅇ 예를 들어, 다음과 같이 커스텀 어노테이션을 정의할 수 있다.

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@TypeQualifierNickname
@NonNull
annotation class ReturnValueNonNull

ㅇ 이 어노테이션은 @NonNull의 별칭으로 작동하며, 메서드의 반환 값이 절대 널이 될 수 없음을 나타낸다.

 

ㅁ Spring Boot 테스트와 JSR-305

ㅇ Spring Boot 테스트에서도 JSR-305 어노테이션을 활용할 수 있다.

ㅇ 이는 테스트 코드의 안정성을 높이는 데 도움이 된다.

@SpringBootTest
class UserServiceTest {

    @Autowired
    lateinit var userService: UserService

    @Test
    fun `should create user`() {
        val user = User("testuser", "Test User")
        val createdUser = userService.createUser(user)
        assertNotNull(createdUser)
        assertEquals("testuser", createdUser.username)
    }

    @Test
    fun `should throw exception when creating user with null username`() {
        assertThrows<IllegalArgumentException> {
            userService.createUser(User(null, "Test User"))
        }
    }
}

ㅇ 이 테스트 코드에서 createUser 메서드가 @NonNull 어노테이션이 붙은 User 객체를 받는다고 가정하면, 널 값으로 사용자를 생성하려는 시도는 컴파일 시간에 오류를 발생시킬 것이다.

 

ㅁ JSR-305와 코틀린 코루틴

ㅇ 코틀린 코루틴과 JSR-305를 함께 사용할 때도 주의가 필요하다.

ㅇ 특히 suspend 함수에서 JSR-305 어노테이션을 사용할 때는 추가적인 고려사항이 있다.

@Service
class AsyncUserService(private val userRepository: UserRepository) {

    @NonNull
    suspend fun getUser(@NonNull username: String): User? {
        return userRepository.findByUsername(username)
    }
}

suspend 함수의 반환 값에 대한 널 안정성 검사가 정확히 작동하지 않을 수 있다.

ㅇ 이는 코루틴의 내부 구현 방식 때문이다. 따라서 코루틴을 사용할 때는 추가적인 널 검사를 수행하는 것이 좋다.

 

ㅁ 마무리

  JSR-305를 적절히 활용하면 Kotlin과 Spring의 장점을 최대한 살리면서 안정적이고 유지보수가 쉬운 코드를 작성할 수 있다. 컴파일 시간에 널 안정성을 보장함으로써 런타임 오류를 크게 줄이고, 코드의 안정성과 가독성을 향상시킬 수 있다. 하지만 JSR-305를 효과적으로 활용하기 위해서는 제네릭 타입과 배열 요소의 널 안정성에 대해서는 추가적인 주의가 필요하다.

  

ㅁ 함께 보면 좋은 사이트

https://kotlinlang.org/docs/java-interop.html#jsr-305-support

Spring Kotlin support

반응형