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:
Juergen Hoeller
2019-03-26 16:13:41 +01:00
parent f7a4850675
commit 210b178922
16 changed files with 430 additions and 552 deletions

View File

@@ -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 {
}
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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);
}