diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index 61860d73e809..068360e441fb 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedType; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Field; @@ -388,8 +389,8 @@ private MethodParameter nested(int nestingLevel, @Nullable Integer typeIndex) { /** * Return whether this method indicates a parameter which is not required: * either in the form of Java 8's {@link java.util.Optional}, any variant - * of a parameter-level {@code Nullable} annotation (such as from JSR-305 - * or the FindBugs set of annotations), or a language-level nullable type + * of a parameter-level {@code Nullable} annotation (such as from JSpecify, + * JSR-305 or Jakarta set of annotations), or a language-level nullable type * declaration or {@code Continuation} parameter in Kotlin. * @since 4.3 */ @@ -402,8 +403,8 @@ public boolean isOptional() { /** * Check whether this method parameter is annotated with any variant of a - * {@code Nullable} annotation, for example, {@code jakarta.annotation.Nullable} or - * {@code edu.umd.cs.findbugs.annotations.Nullable}. + * {@code Nullable} annotation, for example, {@code org.springframework.lang.Nullable}, + * {@code org.jspecify.annotations.Nullable} or {@code jakarta.annotation.Nullable}. */ private boolean hasNullableAnnotation() { for (Annotation ann : getParameterAnnotations()) { @@ -411,6 +412,13 @@ private boolean hasNullableAnnotation() { return true; } } + for (AnnotatedType annotatedType : this.executable.getAnnotatedParameterTypes()) { + for (Annotation ann : annotatedType.getAnnotations()) { + if ("Nullable".equals(ann.annotationType().getSimpleName())) { + return true; + } + } + } return false; } diff --git a/spring-core/src/test/java/org/springframework/core/MethodParameterTests.java b/spring-core/src/test/java/org/springframework/core/MethodParameterTests.java index 22433969befa..715d88788185 100644 --- a/spring-core/src/test/java/org/springframework/core/MethodParameterTests.java +++ b/spring-core/src/test/java/org/springframework/core/MethodParameterTests.java @@ -49,6 +49,10 @@ class MethodParameterTests { private MethodParameter intReturnType; + private MethodParameter jspecifyNullableParameter; + + private MethodParameter springNullableParameter; + @BeforeEach void setup() throws NoSuchMethodException { @@ -56,6 +60,10 @@ void setup() throws NoSuchMethodException { stringParameter = new MethodParameter(method, 0); longParameter = new MethodParameter(method, 1); intReturnType = new MethodParameter(method, -1); + Method jspecifyNullableMethod = getClass().getMethod("jspecifyNullableMethod", String.class); + jspecifyNullableParameter = new MethodParameter(jspecifyNullableMethod, 0); + Method springNullableMethod = getClass().getMethod("springNullableMethod", String.class); + springNullableParameter = new MethodParameter(springNullableMethod, 0); } @@ -238,21 +246,29 @@ void nestedWithTypeIndexReturnsNewInstance() throws Exception { } @Test - void nullableWithSpringAnnotation() { - MethodParameter m = MethodParameter.forExecutable(method, 1); - assertThat(m.isOptional()).isTrue(); + void jspecifyNullableParameter() { + assertThat(jspecifyNullableParameter.isOptional()).isTrue(); } @Test - void nullableWithJSpecifyAnnotation() { - MethodParameter m = MethodParameter.forExecutable(method, 0); - assertThat(m.isOptional()).isTrue(); + void springNullableParameter() { + assertThat(springNullableParameter.isOptional()).isTrue(); } - public int method(@org.jspecify.annotations.Nullable String p1, @org.springframework.lang.Nullable long p2) { + public int method(String p1, long p2) { return 42; } + public @org.jspecify.annotations.Nullable String jspecifyNullableMethod(@org.jspecify.annotations.Nullable String parameter) { + return parameter; + } + + @SuppressWarnings("deprecation") + @org.springframework.lang.Nullable + public String springNullableMethod(@org.springframework.lang.Nullable String parameter) { + return parameter; + } + @SuppressWarnings("unused") private static class NestedClass {