[Kotlin] Kotlin과 Spring 환경에서 JSR-305란?
ㅁ 들어가며
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
이 글은 내가 자주 보았던 위의 옵셩에 대해서 정리하였다. 즉, Kotlin과 Spring 환경에서 JSR-305 지원에 대해 정리한다.
ㅁ JSR-305란 무엇인가요?
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 개발자들에게 큰 이점을 제공한다.
- 컴파일 시간 널 안정성: Java API를 사용할 때도 Kotlin의 널 안정성 기능을 최대한 활용할 수 있다.
- 명확한 API 계약: 메서드의 파라미터나 반환 값이 널일 수 있는지 명확히 알 수 있다.
- 버그 감소: 널 관련 런타임 오류를 컴파일 시간에 잡아낼 수 있어 버그를 크게 줄일 수 있다.
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의 다양한 모드
- strict: 가장 엄격한 모드로, 모든 널 안정성 위반을 오류 처리
- warn: 널 안정성 위반 시 경고
- 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는 매우 유용하지만, 몇 가지 한계와 주의사항이 있다.
- 제네릭 타입 인자: 현재 JSR-305는 제네릭 타입 인자의 널 가능성을 완벽하게 처리하지 못한다.
- 배열 요소: 배열 요소의 널 가능성도 완벽하게 처리되지 않는다.
- 라이브러리 의존성: JSR-305 어노테이션을 사용하려면 해당 라이브러리가 클래스패스에 있어야 한다.
- 성능 영향: 엄격한 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