Selective backport of annotation retrieval refinements (from 5.0.5)
In particular AnnotationTypeFilter's ignoring of standard Java interfaces, AnnotationUtils.clearCache() and a few extra common classes in ClassUtils. Issue: SPR-16667 Issue: SPR-16675
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -22,7 +22,6 @@ import java.lang.annotation.Repeatable;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -38,7 +37,6 @@ import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static java.util.stream.Collectors.*;
|
||||
@@ -63,23 +61,8 @@ public class AnnotationUtilsTests {
|
||||
|
||||
|
||||
@Before
|
||||
public void clearCachesBeforeTests() {
|
||||
clearCaches();
|
||||
}
|
||||
|
||||
static void clearCaches() {
|
||||
clearCache("findAnnotationCache", "annotatedInterfaceCache", "metaPresentCache", "synthesizableCache",
|
||||
"attributeAliasesCache", "attributeMethodsCache", "aliasDescriptorCache");
|
||||
}
|
||||
|
||||
static void clearCache(String... cacheNames) {
|
||||
stream(cacheNames).forEach(cacheName -> getCache(cacheName).clear());
|
||||
}
|
||||
|
||||
static Map<?, ?> getCache(String cacheName) {
|
||||
Field field = ReflectionUtils.findField(AnnotationUtils.class, cacheName);
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
return (Map<?, ?>) ReflectionUtils.getField(field, null);
|
||||
public void clearCacheBeforeTests() {
|
||||
AnnotationUtils.clearCache();
|
||||
}
|
||||
|
||||
|
||||
@@ -727,7 +710,7 @@ public class AnnotationUtilsTests {
|
||||
public void getAttributeOverrideNameFromWrongTargetAnnotation() throws Exception {
|
||||
Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile");
|
||||
assertThat("xmlConfigFile is not an alias for @Component.",
|
||||
getAttributeOverrideName(attribute, Component.class), is(nullValue()));
|
||||
getAttributeOverrideName(attribute, Component.class), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -860,12 +843,6 @@ public class AnnotationUtilsTests {
|
||||
assertEquals("value attribute: ", "webController", synthesizedComponent.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationsFromNullSources() throws Exception {
|
||||
assertNull("null annotation", synthesizeAnnotation(null, null));
|
||||
assertNull("null map", synthesizeAnnotation(null, WebMapping.class, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAlreadySynthesizedAnnotation() throws Exception {
|
||||
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
|
||||
@@ -927,7 +904,6 @@ public class AnnotationUtilsTests {
|
||||
public void synthesizeAnnotationWithAttributeAliasWithMirroredAliasForWrongAttribute() throws Exception {
|
||||
AliasForWithMirroredAliasForWrongAttribute annotation =
|
||||
AliasForWithMirroredAliasForWrongAttributeClass.class.getAnnotation(AliasForWithMirroredAliasForWrongAttribute.class);
|
||||
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(startsWith("Attribute 'bar' in"));
|
||||
exception.expectMessage(containsString(AliasForWithMirroredAliasForWrongAttribute.class.getName()));
|
||||
@@ -1035,18 +1011,18 @@ public class AnnotationUtilsTests {
|
||||
@Test
|
||||
public void synthesizeAnnotationWithImplicitAliasesWithImpliedAliasNamesOmitted() throws Exception {
|
||||
assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(
|
||||
ValueImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "value");
|
||||
ValueImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "value");
|
||||
assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(
|
||||
LocationsImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "location");
|
||||
LocationsImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "location");
|
||||
assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(
|
||||
XmlFilesImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "xmlFile");
|
||||
XmlFilesImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "xmlFile");
|
||||
}
|
||||
|
||||
private void assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(Class<?> clazz,
|
||||
String expected) throws Exception {
|
||||
|
||||
ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig config = clazz.getAnnotation(
|
||||
ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class);
|
||||
ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class);
|
||||
assertNotNull(config);
|
||||
|
||||
ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig synthesizedConfig = synthesizeAnnotation(config);
|
||||
@@ -1241,9 +1217,11 @@ public class AnnotationUtilsTests {
|
||||
assertEquals("location: ", "", contextConfig.location());
|
||||
}
|
||||
|
||||
@Test(expected = AnnotationConfigurationException.class)
|
||||
public void synthesizeAnnotationWithAttributeAliasesDifferentValues() throws Exception {
|
||||
getValue(synthesizeAnnotation(ContextConfigMismatch.class.getAnnotation(ContextConfig.class)));
|
||||
@Test
|
||||
public void synthesizeAnnotationWithAttributeAliasesWithDifferentValues() throws Exception {
|
||||
ContextConfig contextConfig = synthesizeAnnotation(ContextConfigMismatch.class.getAnnotation(ContextConfig.class));
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
getValue(contextConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.core.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -25,9 +26,6 @@ import java.util.Map;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.ImplicitAliasesContextConfig;
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.RequestMethod;
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.WebMapping;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
@@ -44,14 +42,21 @@ import static org.springframework.core.annotation.AnnotationUtilsTests.*;
|
||||
@SuppressWarnings("serial")
|
||||
public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnnotationAttributeExtractorTestCase {
|
||||
|
||||
@Before
|
||||
public void clearCachesBeforeTests() {
|
||||
AnnotationUtilsTests.clearCaches();
|
||||
@Override
|
||||
protected AnnotationAttributeExtractor<?> createExtractorFor(Class<?> clazz, String expected, Class<? extends Annotation> annotationType) {
|
||||
Map<String, Object> attributes = Collections.singletonMap(expected, expected);
|
||||
return new MapAnnotationAttributeExtractor(attributes, annotationType, clazz);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void clearCacheBeforeTests() {
|
||||
AnnotationUtils.clearCache();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void enrichAndValidateAttributesWithImplicitAliasesAndMinimalAttributes() {
|
||||
Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
public void enrichAndValidateAttributesWithImplicitAliasesAndMinimalAttributes() throws Exception {
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
Map<String, Object> expectedAttributes = new HashMap<String, Object>() {{
|
||||
put("groovyScript", "");
|
||||
put("xmlFile", "");
|
||||
@@ -67,7 +72,7 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enrichAndValidateAttributesWithImplicitAliases() {
|
||||
public void enrichAndValidateAttributesWithImplicitAliases() throws Exception {
|
||||
Map<String, Object> attributes = new HashMap<String, Object>() {{
|
||||
put("groovyScript", "groovy!");
|
||||
}};
|
||||
@@ -88,7 +93,6 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
|
||||
|
||||
@Test
|
||||
public void enrichAndValidateAttributesWithSingleElementThatOverridesAnArray() {
|
||||
// @formatter:off
|
||||
Map<String, Object> attributes = new HashMap<String, Object>() {{
|
||||
// Intentionally storing 'value' as a single String instead of an array.
|
||||
// put("value", asArray("/foo"));
|
||||
@@ -102,7 +106,6 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
|
||||
put("name", "test");
|
||||
put("method", new RequestMethod[0]);
|
||||
}};
|
||||
// @formatter:on
|
||||
|
||||
MapAnnotationAttributeExtractor extractor = new MapAnnotationAttributeExtractor(attributes, WebMapping.class, null);
|
||||
Map<String, Object> enriched = extractor.getSource();
|
||||
@@ -112,22 +115,21 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void assertEnrichAndValidateAttributes(Map<String, Object> sourceAttributes, Map<String, Object> expected) {
|
||||
private void assertEnrichAndValidateAttributes(Map<String, Object> sourceAttributes, Map<String, Object> expected) throws Exception {
|
||||
Class<? extends Annotation> annotationType = ImplicitAliasesContextConfig.class;
|
||||
|
||||
// Since the ordering of attribute methods returned by the JVM is
|
||||
// non-deterministic, we have to rig the attributeAliasesCache in AnnotationUtils
|
||||
// so that the tests consistently fail in case enrichAndValidateAttributes() is
|
||||
// buggy.
|
||||
//
|
||||
// Otherwise, these tests would intermittently pass even for an invalid
|
||||
// implementation.
|
||||
// Since the ordering of attribute methods returned by the JVM is non-deterministic,
|
||||
// we have to rig the attributeAliasesCache in AnnotationUtils so that the tests
|
||||
// consistently fail in case enrichAndValidateAttributes() is buggy.
|
||||
// Otherwise, these tests would intermittently pass even for an invalid implementation.
|
||||
Field cacheField = AnnotationUtils.class.getDeclaredField("attributeAliasesCache");
|
||||
cacheField.setAccessible(true);
|
||||
Map<Class<? extends Annotation>, MultiValueMap<String, String>> attributeAliasesCache =
|
||||
(Map<Class<? extends Annotation>, MultiValueMap<String, String>>) AnnotationUtilsTests.getCache("attributeAliasesCache");
|
||||
(Map<Class<? extends Annotation>, MultiValueMap<String, String>>) cacheField.get(null);
|
||||
|
||||
// Declare aliases in an order that will cause enrichAndValidateAttributes() to
|
||||
// fail unless it considers all aliases in the set of implicit aliases.
|
||||
MultiValueMap<String, String> aliases = new LinkedMultiValueMap<String, String>();
|
||||
MultiValueMap<String, String> aliases = new LinkedMultiValueMap<>();
|
||||
aliases.put("xmlFile", Arrays.asList("value", "groovyScript", "location1", "location2", "location3"));
|
||||
aliases.put("groovyScript", Arrays.asList("value", "xmlFile", "location1", "location2", "location3"));
|
||||
aliases.put("value", Arrays.asList("xmlFile", "groovyScript", "location1", "location2", "location3"));
|
||||
@@ -144,10 +146,4 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno
|
||||
expected.forEach((attr, expectedValue) -> assertThat("for attribute '" + attr + "'", enriched.get(attr), is(expectedValue)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AnnotationAttributeExtractor<?> createExtractorFor(Class<?> clazz, String expected, Class<? extends Annotation> annotationType) {
|
||||
Map<String, Object> attributes = Collections.singletonMap(expected, expected);
|
||||
return new MapAnnotationAttributeExtractor(attributes, annotationType, clazz);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -49,7 +49,7 @@ public class AnnotationTypeFilterTests {
|
||||
@Test
|
||||
public void testInheritedAnnotationFromInterfaceDoesNotMatch() throws Exception {
|
||||
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
|
||||
String classUnderTest = "org.springframework.core.type.AnnotationTypeFilterTests$SomeSubClassOfSomeComponentInterface";
|
||||
String classUnderTest = "org.springframework.core.type.AnnotationTypeFilterTests$SomeClassWithSomeComponentInterface";
|
||||
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(classUnderTest);
|
||||
|
||||
AnnotationTypeFilter filter = new AnnotationTypeFilter(InheritedAnnotation.class);
|
||||
@@ -61,7 +61,7 @@ public class AnnotationTypeFilterTests {
|
||||
@Test
|
||||
public void testInheritedAnnotationFromBaseClassDoesMatch() throws Exception {
|
||||
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
|
||||
String classUnderTest = "org.springframework.core.type.AnnotationTypeFilterTests$SomeSubClassOfSomeComponent";
|
||||
String classUnderTest = "org.springframework.core.type.AnnotationTypeFilterTests$SomeSubclassOfSomeComponent";
|
||||
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(classUnderTest);
|
||||
|
||||
AnnotationTypeFilter filter = new AnnotationTypeFilter(InheritedAnnotation.class);
|
||||
@@ -94,22 +94,21 @@ public class AnnotationTypeFilterTests {
|
||||
|
||||
@Test
|
||||
public void testMatchesInterfacesIfConfigured() throws Exception {
|
||||
|
||||
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
|
||||
String classUnderTest = "org.springframework.core.type.AnnotationTypeFilterTests$SomeComponentInterface";
|
||||
String classUnderTest = "org.springframework.core.type.AnnotationTypeFilterTests$SomeClassWithSomeComponentInterface";
|
||||
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(classUnderTest);
|
||||
|
||||
AnnotationTypeFilter filter = new AnnotationTypeFilter(InheritedAnnotation.class, false, true);
|
||||
|
||||
assertTrue(filter.match(metadataReader, metadataReaderFactory));
|
||||
ClassloadingAssertions.assertClassNotLoaded(classUnderTest);
|
||||
}
|
||||
|
||||
|
||||
// We must use a standalone set of types to ensure that no one else is loading them
|
||||
// and interfering with ClassloadingAssertions.assertClassNotLoaded()
|
||||
|
||||
@Inherited
|
||||
private static @interface InheritedAnnotation {
|
||||
private @interface InheritedAnnotation {
|
||||
}
|
||||
|
||||
|
||||
@@ -119,21 +118,21 @@ public class AnnotationTypeFilterTests {
|
||||
|
||||
|
||||
@InheritedAnnotation
|
||||
private static interface SomeComponentInterface {
|
||||
private interface SomeComponentInterface {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class SomeSubClassOfSomeComponentInterface implements SomeComponentInterface {
|
||||
private static class SomeClassWithSomeComponentInterface implements Cloneable, SomeComponentInterface {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class SomeSubClassOfSomeComponent extends SomeComponent {
|
||||
private static class SomeSubclassOfSomeComponent extends SomeComponent {
|
||||
}
|
||||
|
||||
|
||||
private static @interface NonInheritedAnnotation {
|
||||
private @interface NonInheritedAnnotation {
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user