Restore retrieval of plain annotations through direct presence checks
Includes deprecation of several AnnotationUtils methods and nullability refinements for passed-in function arguments at the MergedAnnotation API level... also, MergedAnnotation.getType() returns a Class now. Closes gh-22663 Closes gh-22685
This commit is contained in:
@@ -23,7 +23,9 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Resource;
|
||||
@@ -34,9 +36,9 @@ import org.junit.Test;
|
||||
import org.junit.internal.ArrayComparisonFailure;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Indexed;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
@@ -631,7 +633,6 @@ public class AnnotatedElementUtilsTests {
|
||||
}
|
||||
|
||||
private AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
|
||||
Assert.notNull(annotationType, "annotationType must not be null");
|
||||
return AnnotatedElementUtils.findMergedAnnotationAttributes(element, annotationType.getName(), false, false);
|
||||
}
|
||||
|
||||
@@ -699,6 +700,15 @@ public class AnnotatedElementUtilsTests {
|
||||
assertArrayEquals("path attribute: ", asArray("/test"), webMapping.path());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void javaLangAnnotationTypeViaFindMergedAnnotation() throws Exception {
|
||||
Constructor<?> deprecatedCtor = Date.class.getConstructor(String.class);
|
||||
assertEquals(deprecatedCtor.getAnnotation(Deprecated.class),
|
||||
findMergedAnnotation(deprecatedCtor, Deprecated.class));
|
||||
assertEquals(Date.class.getAnnotation(Deprecated.class),
|
||||
findMergedAnnotation(Date.class, Deprecated.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void javaxAnnotationTypeViaFindMergedAnnotation() throws Exception {
|
||||
assertEquals(ResourceHolder.class.getAnnotation(Resource.class),
|
||||
@@ -707,17 +717,26 @@ public class AnnotatedElementUtilsTests {
|
||||
findMergedAnnotation(SpringAppConfigClass.class, Resource.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullableAnnotationTypeViaFindMergedAnnotation() throws Exception {
|
||||
Method method = TransactionalServiceImpl.class.getMethod("doIt");
|
||||
assertEquals(method.getAnnotation(Resource.class),
|
||||
findMergedAnnotation(method, Resource.class));
|
||||
assertEquals(method.getAnnotation(Resource.class),
|
||||
findMergedAnnotation(method, Resource.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAllMergedAnnotationsOnClassWithInterface() throws Exception {
|
||||
Method m = TransactionalServiceImpl.class.getMethod("doIt");
|
||||
Set<Transactional> allMergedAnnotations = getAllMergedAnnotations(m, Transactional.class);
|
||||
Method method = TransactionalServiceImpl.class.getMethod("doIt");
|
||||
Set<Transactional> allMergedAnnotations = getAllMergedAnnotations(method, Transactional.class);
|
||||
assertTrue(allMergedAnnotations.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAllMergedAnnotationsOnClassWithInterface() throws Exception {
|
||||
Method m = TransactionalServiceImpl.class.getMethod("doIt");
|
||||
Set<Transactional> allMergedAnnotations = findAllMergedAnnotations(m, Transactional.class);
|
||||
Method method = TransactionalServiceImpl.class.getMethod("doIt");
|
||||
Set<Transactional> allMergedAnnotations = findAllMergedAnnotations(method, Transactional.class);
|
||||
assertEquals(1, allMergedAnnotations.size());
|
||||
}
|
||||
|
||||
@@ -1299,20 +1318,22 @@ public class AnnotatedElementUtilsTests {
|
||||
interface TransactionalService {
|
||||
|
||||
@Transactional
|
||||
void doIt();
|
||||
@Nullable
|
||||
Object doIt();
|
||||
}
|
||||
|
||||
class TransactionalServiceImpl implements TransactionalService {
|
||||
|
||||
@Override
|
||||
public void doIt() {
|
||||
@Nullable
|
||||
public Object doIt() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@ComponentScan
|
||||
class ForAnnotationsClass {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -99,39 +99,6 @@ public class AnnotationFilterTests {
|
||||
assertThat(AnnotationFilter.NONE.matches(TestAnnotation.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pacakgesReturnsPackagesAnnotationFilter() {
|
||||
assertThat(AnnotationFilter.packages("com.example")).isInstanceOf(PackagesAnnotationFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mostAppropriateForCollectionReturnsPlainWhenPossible() {
|
||||
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(
|
||||
Arrays.asList(TestAnnotation.class, OtherAnnotation.class));
|
||||
assertThat(filter).isSameAs(AnnotationFilter.PLAIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mostAppropriateForCollectionWhenCantUsePlainReturnsNone() {
|
||||
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(Arrays.asList(
|
||||
TestAnnotation.class, OtherAnnotation.class, Nullable.class));
|
||||
assertThat(filter).isSameAs(AnnotationFilter.NONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mostAppropriateForArrayReturnsPlainWhenPossible() {
|
||||
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(
|
||||
TestAnnotation.class, OtherAnnotation.class);
|
||||
assertThat(filter).isSameAs(AnnotationFilter.PLAIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mostAppropriateForArrayWhenCantUsePlainReturnsNone() {
|
||||
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(
|
||||
TestAnnotation.class, OtherAnnotation.class, Nullable.class);
|
||||
assertThat(filter).isSameAs(AnnotationFilter.NONE);
|
||||
}
|
||||
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface TestAnnotation {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.core.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Repeatable;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -36,7 +37,7 @@ import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
@@ -55,6 +56,7 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
* @author Phillip Webb
|
||||
* @author Oleg Zhurakousky
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class AnnotationUtilsTests {
|
||||
|
||||
@Rule
|
||||
@@ -396,7 +398,7 @@ public class AnnotationUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAnnotationDeclaredLocallyForAllScenarios() throws Exception {
|
||||
public void isAnnotationDeclaredLocallyForAllScenarios() {
|
||||
// no class-level annotation
|
||||
assertFalse(isAnnotationDeclaredLocally(Transactional.class, NonAnnotatedInterface.class));
|
||||
assertFalse(isAnnotationDeclaredLocally(Transactional.class, NonAnnotatedClass.class));
|
||||
@@ -435,6 +437,12 @@ public class AnnotationUtilsTests {
|
||||
assertFalse(isAnnotationInherited(Order.class, SubNonInheritedAnnotationClass.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAnnotationMetaPresentForJavaLangType() {
|
||||
assertTrue(isAnnotationMetaPresent(Order.class, Documented.class));
|
||||
assertTrue(isAnnotationMetaPresent(NonNullApi.class, Documented.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnotationAttributesWithoutAttributeAliases() {
|
||||
Component component = WebController.class.getAnnotation(Component.class);
|
||||
@@ -540,6 +548,13 @@ public class AnnotationUtilsTests {
|
||||
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findRepeatableAnnotation() {
|
||||
Repeatable repeatable = findAnnotation(MyRepeatable.class, Repeatable.class);
|
||||
assertNotNull(repeatable);
|
||||
assertEquals(MyRepeatableContainer.class, repeatable.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRepeatableAnnotationsDeclaredOnMethod() throws Exception {
|
||||
Method method = InterfaceWithRepeated.class.getMethod("foo");
|
||||
@@ -924,10 +939,8 @@ public class AnnotationUtilsTests {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage(either(allOf(startsWith("Attributes map"),
|
||||
containsString("returned null for required attribute 'text'"),
|
||||
containsString("defined by annotation type ["
|
||||
+ AnnotationWithoutDefaults.class.getName() + "]"))).or(
|
||||
containsString(
|
||||
"No value found for attribute named 'text' in merged annotation")));
|
||||
containsString("defined by annotation type [" + AnnotationWithoutDefaults.class.getName() + "]"))).or(
|
||||
containsString("No value found for attribute named 'text' in merged annotation")));
|
||||
synthesizeAnnotation(attributes, AnnotationWithoutDefaults.class, null);
|
||||
}
|
||||
|
||||
@@ -936,8 +949,8 @@ public class AnnotationUtilsTests {
|
||||
Map<String, Object> map = Collections.singletonMap(VALUE, 42L);
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage(containsString(
|
||||
"Attribute 'value' in annotation org.springframework.stereotype.Component "
|
||||
+ "should be compatible with java.lang.String but a java.lang.Long value was returned"));
|
||||
"Attribute 'value' in annotation org.springframework.stereotype.Component " +
|
||||
"should be compatible with java.lang.String but a java.lang.Long value was returned"));
|
||||
synthesizeAnnotation(map, Component.class, null);
|
||||
}
|
||||
|
||||
@@ -1055,12 +1068,6 @@ public class AnnotationUtilsTests {
|
||||
void fromInterfaceImplementedByRoot();
|
||||
}
|
||||
|
||||
public interface NullableAnnotatedInterface {
|
||||
|
||||
@Nullable
|
||||
void fromInterfaceImplementedByRoot();
|
||||
}
|
||||
|
||||
public static class Root implements AnnotatedInterface {
|
||||
|
||||
@Order(27)
|
||||
|
||||
@@ -77,18 +77,17 @@ public class MergedAnnotationsTests {
|
||||
|
||||
@Test
|
||||
public void streamWhenFromClassWithMetaDepth1() {
|
||||
Stream<String> names = MergedAnnotations.from(TransactionalComponent.class)
|
||||
Stream<Class<?>> classes = MergedAnnotations.from(TransactionalComponent.class)
|
||||
.stream().map(MergedAnnotation::getType);
|
||||
assertThat(names).containsExactly(Transactional.class.getName(),
|
||||
Component.class.getName(), Indexed.class.getName());
|
||||
assertThat(classes).containsExactly(Transactional.class, Component.class, Indexed.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void streamWhenFromClassWithMetaDepth2() {
|
||||
Stream<String> names = MergedAnnotations.from(ComposedTransactionalComponent.class)
|
||||
Stream<Class<?>> classes = MergedAnnotations.from(ComposedTransactionalComponent.class)
|
||||
.stream().map(MergedAnnotation::getType);
|
||||
assertThat(names).containsExactly(TransactionalComponent.class.getName(),
|
||||
Transactional.class.getName(), Component.class.getName(), Indexed.class.getName());
|
||||
assertThat(classes).containsExactly(TransactionalComponent.class,
|
||||
Transactional.class, Component.class, Indexed.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1149,17 +1148,16 @@ public class MergedAnnotationsTests {
|
||||
|
||||
@Test
|
||||
public void getDirectWithoutAttributeAliases() {
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(WebController.class).get(
|
||||
Component.class);
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(WebController.class)
|
||||
.get(Component.class);
|
||||
assertThat(annotation.getString("value")).isEqualTo("webController");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDirectWithNestedAnnotations() {
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(
|
||||
ComponentScanClass.class).get(ComponentScan.class);
|
||||
MergedAnnotation<Filter>[] filters = annotation.getAnnotationArray(
|
||||
"excludeFilters", Filter.class);
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(ComponentScanClass.class)
|
||||
.get(ComponentScan.class);
|
||||
MergedAnnotation<Filter>[] filters = annotation.getAnnotationArray("excludeFilters", Filter.class);
|
||||
assertThat(Arrays.stream(filters).map(
|
||||
filter -> filter.getString("pattern"))).containsExactly("*Foo", "*Bar");
|
||||
}
|
||||
@@ -1167,8 +1165,7 @@ public class MergedAnnotationsTests {
|
||||
@Test
|
||||
public void getDirectWithAttributeAliases1() throws Exception {
|
||||
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(method).get(
|
||||
RequestMapping.class);
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(method).get(RequestMapping.class);
|
||||
assertThat(annotation.getString("name")).isEqualTo("foo");
|
||||
assertThat(annotation.getStringArray("value")).containsExactly("/test");
|
||||
assertThat(annotation.getStringArray("path")).containsExactly("/test");
|
||||
@@ -1177,8 +1174,7 @@ public class MergedAnnotationsTests {
|
||||
@Test
|
||||
public void getDirectWithAttributeAliases2() throws Exception {
|
||||
Method method = WebController.class.getMethod("handleMappedWithPathAttribute");
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(method).get(
|
||||
RequestMapping.class);
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(method).get(RequestMapping.class);
|
||||
assertThat(annotation.getString("name")).isEqualTo("bar");
|
||||
assertThat(annotation.getStringArray("value")).containsExactly("/test");
|
||||
assertThat(annotation.getStringArray("path")).containsExactly("/test");
|
||||
@@ -1186,8 +1182,7 @@ public class MergedAnnotationsTests {
|
||||
|
||||
@Test
|
||||
public void getDirectWithAttributeAliasesWithDifferentValues() throws Exception {
|
||||
Method method = WebController.class.getMethod(
|
||||
"handleMappedWithDifferentPathAndValueAttributes");
|
||||
Method method = WebController.class.getMethod("handleMappedWithDifferentPathAndValueAttributes");
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(
|
||||
() -> MergedAnnotations.from(method).get(
|
||||
RequestMapping.class)).withMessageContaining(
|
||||
@@ -1197,10 +1192,9 @@ public class MergedAnnotationsTests {
|
||||
|
||||
@Test
|
||||
public void getValueFromAnnotation() throws Exception {
|
||||
Method method = TransactionalStringGeneric.class.getMethod("something",
|
||||
Object.class);
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(method,
|
||||
SearchStrategy.EXHAUSTIVE).get(Order.class);
|
||||
Method method = TransactionalStringGeneric.class.getMethod("something", Object.class);
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(method, SearchStrategy.EXHAUSTIVE)
|
||||
.get(Order.class);
|
||||
assertThat(annotation.getInt("value")).isEqualTo(1);
|
||||
}
|
||||
|
||||
@@ -1210,21 +1204,17 @@ public class MergedAnnotationsTests {
|
||||
assertThat(declaredAnnotations).hasSize(1);
|
||||
Annotation annotation = declaredAnnotations[0];
|
||||
MergedAnnotation<Annotation> mergedAnnotation = MergedAnnotation.from(annotation);
|
||||
assertThat(mergedAnnotation.getType()).contains("NonPublicAnnotation");
|
||||
assertThat(
|
||||
mergedAnnotation.synthesize().annotationType().getSimpleName()).isEqualTo(
|
||||
"NonPublicAnnotation");
|
||||
assertThat(mergedAnnotation.getType().getSimpleName()).isEqualTo("NonPublicAnnotation");
|
||||
assertThat(mergedAnnotation.synthesize().annotationType().getSimpleName()).isEqualTo("NonPublicAnnotation");
|
||||
assertThat(mergedAnnotation.getInt("value")).isEqualTo(42);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDefaultValueFromAnnotation() throws Exception {
|
||||
Method method = TransactionalStringGeneric.class.getMethod("something",
|
||||
Object.class);
|
||||
MergedAnnotation<Order> annotation = MergedAnnotations.from(method,
|
||||
SearchStrategy.EXHAUSTIVE).get(Order.class);
|
||||
assertThat(annotation.getDefaultValue("value")).contains(
|
||||
Ordered.LOWEST_PRECEDENCE);
|
||||
Method method = TransactionalStringGeneric.class.getMethod("something", Object.class);
|
||||
MergedAnnotation<Order> annotation = MergedAnnotations.from(method, SearchStrategy.EXHAUSTIVE)
|
||||
.get(Order.class);
|
||||
assertThat(annotation.getDefaultValue("value")).contains(Ordered.LOWEST_PRECEDENCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1233,7 +1223,7 @@ public class MergedAnnotationsTests {
|
||||
assertThat(declaredAnnotations).hasSize(1);
|
||||
Annotation declaredAnnotation = declaredAnnotations[0];
|
||||
MergedAnnotation<?> annotation = MergedAnnotation.from(declaredAnnotation);
|
||||
assertThat(annotation.getType()).isEqualTo(
|
||||
assertThat(annotation.getType().getName()).isEqualTo(
|
||||
"org.springframework.core.annotation.subpackage.NonPublicAnnotation");
|
||||
assertThat(annotation.getDefaultValue("value")).contains(-1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user