Loading...
Spring Framework Reference Documentation 7.0.2의 Null-safety의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
Java는 아직 타입 시스템으로 nullness marker를 표현할 수 없지만, Spring Framework 코드베이스는 API, 필드 및 관련 타입 사용의 nullability를 선언하기 위해 JSpecify 어노테이션으로 어노테이트되어 있습니다. 이러한 어노테이션과 시맨틱에 익숙해지기 위해 JSpecify user guide를 읽는 것이 강력히 권장됩니다.
이 null-safety 구성의 주요 목표는 빌드 타임 체크를 통해 런타임에 NullPointerException이 발생하는 것을 방지하고,
값이 없을 수 있음을 표현하는 방법으로 명시적인 nullability를 사용하는 것입니다.
이는 NullAway와 같은 nullability 체커나 IntelliJ IDEA와 Eclipse(후자는 수동 구성이 필요함)와 같이
JSpecify 어노테이션을 지원하는 IDE를 활용함으로써 Java에서 유용하게 사용할 수 있습니다.
Kotlin에서는 JSpecify 어노테이션이 자동으로 Kotlin의 null safety로 변환됩니다.
Nullness Spring API는
런타임에서 타입 사용, 필드, 메서드 반환 타입 또는 파라미터의 nullness를 감지하는 데 사용할 수 있습니다.
이는 JSpecify 어노테이션, Kotlin null safety 및 Java primitive 타입에 대한 완전한 지원을 제공하며,
또한 어떤 @Nullable 어노테이션(어떤 패키지이든 상관없이)에 대해서도 실용적인 체크를 제공합니다.
Spring Framework 7부터 Spring Framework 코드베이스는 JSpecify 어노테이션을 활용하여 null-safe API를 노출하고, 빌드의 일부로 NullAway를 사용하여 해당 nullability 선언의 일관성을 검사합니다. Spring Framework 및 Spring portfolio 프로젝트에 의존하는 각 라이브러리와 Spring ecosystem과 관련된 다른 라이브러리(Reactor, Micrometer 및 Spring community 프로젝트 포함)에 대해서도 동일하게 하는 것이 권장됩니다.
nullness 어노테이션을 지원하는 IDE로 애플리케이션을 개발하면 nullability 계약이 지켜지지 않을 때
Java에서는 경고를, Kotlin에서는 오류를 제공하여 Spring 애플리케이션 개발자가
런타임에 NullPointerException이 발생하지 않도록 null 처리을 정교하게 다듬을 수 있게 해 줍니다.
선택적으로, Spring 애플리케이션 개발자는 자신의 코드베이스에 어노테이션을 추가하고 NullAway와 같은 빌드 플러그인을 사용하여 빌드 타임 동안 애플리케이션 수준에서 null-safety를 강제할 수 있습니다.
이 섹션의 목적은 Spring 관련 라이브러리 또는 애플리케이션의 nullability를 명시적으로 지정하기 위한 일부 제안된 가이드라인을 공유하는 것입니다.
이해해야 할 핵심 포인트는 Java에서 타입의 nullness가 기본적으로 unknown이며, non-null 타입 사용이 nullable 사용보다 훨씬 더 자주 사용된다는 점입니다. 코드베이스를 읽기 쉽게 유지하기 위해, 일반적으로 특정 스코프에 대해 nullable로 표시되지 않는 한 타입 사용이 기본적으로 non-null이라고 정의하고자 합니다.
이것이 바로
@NullMarked의 목적이며,
이는 일반적으로 Spring 프로젝트에서 package-info.java 파일을 통해 패키지 수준에 설정됩니다.
예를 들면 다음과 같습니다:
1@NullMarked 2package org.springframework.core; 3 4import org.jspecify.annotations.NullMarked;
@NullMarked 코드에서 nullable 타입 사용은
@Nullable로 명시적으로 정의됩니다.
JSpecify @Nullable / @NonNull 어노테이션과 대부분의 다른 variant 사이의 핵심 차이점은
JSpecify 어노테이션이 @Target(ElementType.TYPE_USE)로 메타-어노테이트되어 있어서
타입 사용에만 적용된다는 점입니다.
이는
관련 Java 명세를 준수하거나
코드 스타일 모범 사례를 따르기 위해 이러한 어노테이션을 어디에 배치해야 하는지에 영향을 줍니다.
스타일 관점에서, 이러한 어노테이션의 타입-사용 특성을 수용하여
어노테이트된 타입과 같은 줄에, 그리고 그 바로 앞에 배치하는 것이 권장됩니다.
예를 들어 필드의 경우:
1private @Nullable String fileEncoding;
또는 메서드 파라미터 및 메서드 반환 타입의 경우:
1public @Nullable String buildMessage(@Nullable String message, 2 @Nullable Throwable cause) { 3 // ... 4}
메서드를 override할 때, JSpecify 어노테이션은 원래 메서드로부터 상속되지 않습니다. 이는 implementation을 override하고 동일한 nullability 시맨틱을 유지하려면 JSpecify 어노테이션을 overriding 메서드에 복사해야 함을 의미합니다.
@NonNull과
@NullUnmarked는
일반적인 use case에서는 거의 필요하지 않습니다.
array와 varargs의 경우 element의 nullness와
array 자체의 nullness를 구분할 수 있어야 합니다.
처음에는 놀라울 수 있는
Java 명세에서 정의된 syntax에
주의를 기울이십시오. 예를 들어, @NullMarked 코드에서:
@Nullable Object[] array는 개별 element는 null일 수 있지만 array 자체는 그럴 수 없음을 의미합니다.
Object @Nullable [] array는 개별 element는 null일 수 없지만 array 자체는 그럴 수 있음을 의미합니다.
@Nullable Object @Nullable [] array는 개별 element와 array 모두 null일 수 있음을 의미합니다.
JSpecify 어노테이션은 generics에도 적용됩니다. 예를 들어, @NullMarked 코드에서:
List<String>은 non-null element의 list를 의미합니다(List<@NonNull String>과 동일).
List<@Nullable String>은 nullable element의 list를 의미합니다.
generic 타입 또는 generic 메서드를 선언할 때는 상황이 조금 더 복잡합니다. 자세한 내용은 관련 JSpecify generics documentation를 참조하십시오.
generic 타입 및 generic 메서드의 nullability는 아직 NullAway에서 완전히 지원되지 않습니다.
Java 명세는 또한 @Target(ElementType.TYPE_USE)로 정의된 어노테이션(예: JSpecify의
@Nullable 어노테이션)이 inner 또는 fully qualified 타입 이름 내에서 마지막 점(.) 뒤에
선언되어야 함을 강제합니다:
Cache.@Nullable ValueWrapper
jakarta.validation.@Nullable Validator
권장되는 configuration은 다음과 같습니다:
@NullMarked로 어노테이트된 패키지에 대해서만 nullability 체크를 수행하기 위해 NullAway:OnlyNullMarked=true.
NullAway:CustomContractAnnotations=org.springframework.lang.Contract로 설정하여
org.springframework.lang 패키지의 @Contract
어노테이션을 NullAway가 인식하도록 합니다. 이는 코드베이스에서 불필요한 경고를 피하기 위해
보완적인 시맨틱을 표현하는 데 사용할 수 있습니다.
@Contract 선언의 이점에 대한 좋은 예는
Assert.notNull()에서
볼 수 있으며, 이 메서드는 @Contract("null, _ → fail")로 어노테이트되어 있습니다.
이 contract 선언을 통해 NullAway는 Assert.notNull()을 성공적으로 호출한 후에는
파라미터로 전달된 값이 null일 수 없음을 이해하게 됩니다.
선택적으로, NullAway:JSpecifyMode=true를 설정하여
array, varargs 및 generics에 대한 어노테이션을 포함한
전체 JSpecify 시맨틱에 대한 체크를 활성화할 수 있습니다.
이 mode는
아직 개발 중이며
JDK 22 이상이 필요합니다(일반적으로 예상 baseline을 구성하기 위한 --release Java 컴파일러 플래그와 함께 사용).
이 섹션에서 앞서 언급한 권장 configuration으로 코드베이스가 경고를 생성하지 않는 것을 확인한 후,
두 번째 단계로 JSpecify mode를 활성화하는 것이 좋습니다.
NullAway가 nullability 문제를 잘못 감지하는 몇 가지 유효한 use case가 있습니다. 이러한 경우 관련 경고를 suppress하고 그 이유를 문서화하는 것이 권장됩니다:
필드, 생성자 또는 클래스 수준에서 @SuppressWarnings("NullAway.Init")를 사용하여
필드의 lazy initialization(예: 클래스가
InitializingBean을 구현하는 경우)으로 인해 발생하는
불필요한 경고를 피할 수 있습니다.
@SuppressWarnings("NullAway") // Dataflow analysis limitation은 NullAway dataflow analysis가
nullability 문제를 포함하는 경로가 절대 발생하지 않는다는 것을 감지하지 못할 때 사용할 수 있습니다.
@SuppressWarnings("NullAway") // Lambda는 NullAway가 lambda 외부에서 수행된 assertion을
lambda 내 코드 경로에 대해 고려하지 않을 때 사용할 수 있습니다.
@SuppressWarnings("NullAway") // Reflection은 API로는 표현될 수 없지만
non-null 값을 반환하는 것으로 알려진 일부 reflection operation에 사용할 수 있습니다.
@SuppressWarnings("NullAway") // Well-known map keys는
Map#get 호출이 이전에 non-null 관련 value가 삽입된 것으로 알려진 key로 수행될 때 사용할 수 있습니다.
@SuppressWarnings("NullAway") // Overridden method does not define nullability는
superclass가 nullability를 정의하지 않을 때(일반적으로 superclass가 외부 dependency에서 올 때) 사용할 수 있습니다.
@SuppressWarnings("NullAway") // See github.com/uber/NullAway/issues/1075는
NullAway가 generic 메서드에서 타입 변수 nullness를 감지하지 못할 때 사용할 수 있습니다.
Spring null-safety 어노테이션인
@Nullable,
@NonNull,
@NonNullApi 및
@NonNullFields는
org.springframework.lang 패키지에 있으며 JSpecify가 존재하지 않았던 Spring Framework 5에서 도입되었고,
그 당시 최선의 선택은 JSR 305(dormant 상태지만 널리 사용되는 JSR)의 메타-어노테이션을 활용하는 것이었습니다.
이들은 JSpecify 어노테이션을 선호하는 Spring Framework 7부터 deprecated되었으며,
JSpecify 어노테이션은 제대로 정의된 명세, split-package 문제가 없는 canonical dependency,
더 나은 툴링, 더 나은 Kotlin 통합, 그리고 더 많은 use case에 대해
nullability를 보다 정확하게 지정할 수 있는 기능과 같은 상당한 향상을 제공합니다.
주요 차이점은 JSR 305 시맨틱을 따르는 Spring의 deprecated null-safety 어노테이션은 필드, 파라미터 및 반환 값에 적용되는 반면, JSpecify 어노테이션은 타입 사용에 적용된다는 점입니다. 이 미묘한 차이는 실제로 상당히 중요한데, 개발자가 element의 nullness와 array/varargs의 nullness를 구분하고 generic 타입의 nullness를 정의할 수 있게 해주기 때문입니다.
이는 동일한 시맨틱을 유지하기 위해 array 및 varargs null-safety 선언을 업데이트해야 함을 의미합니다.
예를 들어 Spring 어노테이션에서 @Nullable Object[] array는
JSpecify 어노테이션에서는 Object @Nullable [] array로 변경해야 합니다.
varargs에도 동일하게 적용됩니다.
또한 필드 및 반환 값 어노테이션을 타입에 더 가깝게, 그리고 같은 줄에 배치하는 것이 권장됩니다. 예를 들어:
필드의 경우 Spring 어노테이션에서 @Nullable private String field 대신,
JSpecify 어노테이션에서는 private @Nullable String field를 사용하십시오.
메서드 반환 타입의 경우 Spring 어노테이션에서 @Nullable public String method() 대신,
JSpecify 어노테이션에서는 public @Nullable String method()를 사용하십시오.
또한 JSpecify에서는 null-marked 코드에서 super 메서드의 @Nullable로 어노테이트된 타입 사용을 override할 때
nullable 선언을 "되돌리기" 위해 @NonNull을 지정할 필요가 없습니다.
어노테이션을 지정하지 않고 선언하면 null-marked 기본값이 적용됩니다
(타입 사용은 명시적으로 nullable로 어노테이트되지 않는 한 non-null로 간주됩니다).
Resilience Features
Data Buffers and Codecs