From efb80bcdcb686eb14a9b54636d33e0eb21f41592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 19 Aug 2025 15:34:38 +0200 Subject: [PATCH] Polish "Support additional nullness signal for Actuator endpoints" See gh-46854 --- .../MetadataGenerationEnvironment.java | 17 ++++----- .../EndpointMetadataGenerationTests.java | 28 ++++---------- .../endpoint/NullableParameterEndpoint.java | 5 ++- .../endpoint/OptionalParameterEndpoint.java | 38 +++++++++---------- 4 files changed, 37 insertions(+), 51 deletions(-) diff --git a/configuration-metadata/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java b/configuration-metadata/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java index 8b11b6bade6..b4ace8decec 100644 --- a/configuration-metadata/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java +++ b/configuration-metadata/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java @@ -52,13 +52,10 @@ import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation; * @author Stephane Nicoll * @author Scott Frederick * @author Moritz Halbritter - * @author Wonyong Hwang */ class MetadataGenerationEnvironment { - private static final Set NULLABLE_ANNOTATIONS = Set.of( - "org.springframework.lang.Nullable", - "org.jspecify.annotations.Nullable"); + private static final String NULLABLE_ANNOTATION = "org.jspecify.annotations.Nullable"; private static final Set TYPE_EXCLUDES = Set.of("com.zaxxer.hikari.IConnectionCustomizer", "groovy.lang.MetaClass", "groovy.text.markup.MarkupTemplateEngine", "java.io.Writer", "java.io.PrintWriter", @@ -268,7 +265,12 @@ class MetadataGenerationEnvironment { return annotation; } } + } + return null; + } + private AnnotationMirror getTypeUseAnnotation(Element element, String type) { + if (element != null) { for (AnnotationMirror annotation : element.asType().getAnnotationMirrors()) { if (type.equals(annotation.getAnnotationType().toString())) { return annotation; @@ -377,12 +379,7 @@ class MetadataGenerationEnvironment { } boolean hasNullableAnnotation(Element element) { - for (String nullableAnnotation : NULLABLE_ANNOTATIONS) { - if (getAnnotation(element, nullableAnnotation) != null) { - return true; - } - } - return false; + return getTypeUseAnnotation(element, NULLABLE_ANNOTATION) != null; } boolean hasOptionalParameterAnnotation(Element element) { diff --git a/configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/EndpointMetadataGenerationTests.java b/configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/EndpointMetadataGenerationTests.java index 4058e7fae6e..264f42585ac 100644 --- a/configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/EndpointMetadataGenerationTests.java +++ b/configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/EndpointMetadataGenerationTests.java @@ -28,6 +28,8 @@ import org.springframework.boot.configurationsample.endpoint.CamelCaseEndpoint; import org.springframework.boot.configurationsample.endpoint.CustomPropertiesEndpoint; import org.springframework.boot.configurationsample.endpoint.EnabledEndpoint; import org.springframework.boot.configurationsample.endpoint.NoAccessEndpoint; +import org.springframework.boot.configurationsample.endpoint.NullableParameterEndpoint; +import org.springframework.boot.configurationsample.endpoint.OptionalParameterEndpoint; import org.springframework.boot.configurationsample.endpoint.ReadOnlyAccessEndpoint; import org.springframework.boot.configurationsample.endpoint.SimpleEndpoint; import org.springframework.boot.configurationsample.endpoint.SimpleEndpoint2; @@ -35,8 +37,6 @@ import org.springframework.boot.configurationsample.endpoint.SimpleEndpoint3; import org.springframework.boot.configurationsample.endpoint.SpecificEndpoint; import org.springframework.boot.configurationsample.endpoint.UnrestrictedAccessEndpoint; import org.springframework.boot.configurationsample.endpoint.incremental.IncrementalEndpoint; -import org.springframework.boot.configurationsample.endpoint.NullableParameterEndpoint; -import org.springframework.boot.configurationsample.endpoint.OptionalParameterEndpoint; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatRuntimeException; @@ -196,37 +196,25 @@ class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests { } @Test - void nullableParameterEndpoint() { + void endpointWithNullableParameter() { ConfigurationMetadata metadata = compile(NullableParameterEndpoint.class); - assertThat(metadata).has(Metadata.withGroup("management.endpoint.nullable").fromSource(NullableParameterEndpoint.class)); + assertThat(metadata) + .has(Metadata.withGroup("management.endpoint.nullable").fromSource(NullableParameterEndpoint.class)); assertThat(metadata).has(access("nullable", Access.UNRESTRICTED)); assertThat(metadata).has(cacheTtl("nullable")); assertThat(metadata.getItems()).hasSize(3); } @Test - void optionalParameterEndpoint() { + void endpointWithOptionalParameter() { ConfigurationMetadata metadata = compile(OptionalParameterEndpoint.class); - assertThat(metadata).has(Metadata.withGroup("management.endpoint.optional").fromSource(OptionalParameterEndpoint.class)); + assertThat(metadata) + .has(Metadata.withGroup("management.endpoint.optional").fromSource(OptionalParameterEndpoint.class)); assertThat(metadata).has(access("optional", Access.UNRESTRICTED)); assertThat(metadata).has(cacheTtl("optional")); assertThat(metadata.getItems()).hasSize(3); } - @Test - void nullableAndOptionalParameterEquivalence() { - ConfigurationMetadata nullableMetadata = compile(NullableParameterEndpoint.class); - ConfigurationMetadata optionalMetadata = compile(OptionalParameterEndpoint.class); - - assertThat(nullableMetadata.getItems()).hasSize(3); - assertThat(optionalMetadata.getItems()).hasSize(3); - - assertThat(nullableMetadata).has(access("nullable", Access.UNRESTRICTED)); - assertThat(optionalMetadata).has(access("optional", Access.UNRESTRICTED)); - assertThat(nullableMetadata).has(cacheTtl("nullable")); - assertThat(optionalMetadata).has(cacheTtl("optional")); - } - private Metadata.MetadataItemCondition access(String endpointId, Access defaultValue) { return defaultAccess(endpointId, endpointId, defaultValue); } diff --git a/configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/NullableParameterEndpoint.java b/configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/NullableParameterEndpoint.java index bff98e57945..f9488248141 100644 --- a/configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/NullableParameterEndpoint.java +++ b/configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/NullableParameterEndpoint.java @@ -16,12 +16,13 @@ package org.springframework.boot.configurationsample.endpoint; +import org.jspecify.annotations.Nullable; + import org.springframework.boot.configurationsample.Endpoint; import org.springframework.boot.configurationsample.ReadOperation; -import org.jspecify.annotations.Nullable; /** - * An endpoint with @Nullable parameter to test. + * An endpoint that uses {@code Nullable} to signal an optional parameter. * * @author Wonyong Hwang */ diff --git a/configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/OptionalParameterEndpoint.java b/configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/OptionalParameterEndpoint.java index 96e92b8c4a0..781a10053e2 100644 --- a/configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/OptionalParameterEndpoint.java +++ b/configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/OptionalParameterEndpoint.java @@ -14,23 +14,23 @@ * limitations under the License. */ - package org.springframework.boot.configurationsample.endpoint; +package org.springframework.boot.configurationsample.endpoint; - import org.springframework.boot.configurationsample.Endpoint; - import org.springframework.boot.configurationsample.ReadOperation; - import org.springframework.boot.configurationsample.OptionalParameter; - - /** - * An endpoint with @OptionalParameter to compare with @Nullable behavior. - * - * @author Wonyong Hwang - */ - @Endpoint(id = "optional") - public class OptionalParameterEndpoint { - - @ReadOperation - public String invoke(@OptionalParameter String parameter) { - return "test with " + parameter; - } - - } +import org.springframework.boot.configurationsample.Endpoint; +import org.springframework.boot.configurationsample.OptionalParameter; +import org.springframework.boot.configurationsample.ReadOperation; + +/** + * An endpoint that uses {@code OptionalParameter} to signal an optional parameter. + * + * @author Wonyong Hwang + */ +@Endpoint(id = "optional") +public class OptionalParameterEndpoint { + + @ReadOperation + public String invoke(@OptionalParameter String parameter) { + return "test with " + parameter; + } + +}