Avoid annotation synthesizing for getAnnotationAttributes retrieval
Issue: SPR-13621
This commit is contained in:
@@ -977,7 +977,7 @@ public class AnnotatedElementUtils {
|
||||
* annotation attributes from lower levels in the annotation hierarchy
|
||||
* during the {@link #postProcess} phase.
|
||||
* @since 4.2
|
||||
* @see AnnotationUtils#getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean, boolean)
|
||||
* @see AnnotationUtils#retrieveAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
|
||||
* @see AnnotationUtils#postProcessAnnotationAttributes
|
||||
*/
|
||||
private static class MergedAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {
|
||||
@@ -1003,8 +1003,8 @@ public class AnnotatedElementUtils {
|
||||
public AnnotationAttributes process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
|
||||
boolean found = (this.annotationType != null ? annotation.annotationType() == this.annotationType :
|
||||
annotation.annotationType().getName().equals(this.annotationName));
|
||||
return (found ? AnnotationUtils.getAnnotationAttributes(annotatedElement, annotation,
|
||||
this.classValuesAsString, this.nestedAnnotationsAsMap, true) : null);
|
||||
return (found ? AnnotationUtils.retrieveAnnotationAttributes(annotatedElement, annotation,
|
||||
this.classValuesAsString, this.nestedAnnotationsAsMap) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,8 +25,8 @@ import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -112,12 +112,6 @@ public abstract class AnnotationUtils {
|
||||
public static final String VALUE = "value";
|
||||
|
||||
|
||||
/**
|
||||
* An object that can be stored in {@link AnnotationAttributes} as a
|
||||
* placeholder for an attribute's declared default value.
|
||||
*/
|
||||
private static final Object DEFAULT_VALUE_PLACEHOLDER = new String("<SPRING DEFAULT VALUE PLACEHOLDER>");
|
||||
|
||||
private static final Map<AnnotationCacheKey, Annotation> findAnnotationCache =
|
||||
new ConcurrentReferenceHashMap<AnnotationCacheKey, Annotation>(256);
|
||||
|
||||
@@ -1002,7 +996,10 @@ public abstract class AnnotationUtils {
|
||||
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
|
||||
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
|
||||
return getAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap, false);
|
||||
AnnotationAttributes attributes =
|
||||
retrieveAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap);
|
||||
postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, nestedAnnotationsAsMap);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1010,16 +1007,9 @@ public abstract class AnnotationUtils {
|
||||
* <p>This method provides fully recursive annotation reading capabilities on par with
|
||||
* the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}.
|
||||
* <p><strong>NOTE</strong>: This variant of {@code getAnnotationAttributes()} is
|
||||
* only intended for use within the framework. Specifically, the {@code mergeMode} flag
|
||||
* can be set to {@code true} in order to support processing of attribute aliases while
|
||||
* merging attributes within an annotation hierarchy. When running in <em>merge mode</em>,
|
||||
* the following special rules apply:
|
||||
* only intended for use within the framework. The following special rules apply:
|
||||
* <ol>
|
||||
* <li>The supplied annotation will <em>not</em> be
|
||||
* {@linkplain #synthesizeAnnotation synthesized} before retrieving its attributes;
|
||||
* however, nested annotations and arrays of nested annotations <em>will</em> be
|
||||
* synthesized.</li>
|
||||
* <li>Default values will be replaced with {@link #DEFAULT_VALUE_PLACEHOLDER}.</li>
|
||||
* <li>Default values will be replaced with default value placeholders.</li>
|
||||
* <li>The resulting, merged annotation attributes should eventually be
|
||||
* {@linkplain #postProcessAnnotationAttributes post-processed} in order to
|
||||
* ensure that placeholders have been replaced by actual default values and
|
||||
@@ -1035,33 +1025,26 @@ public abstract class AnnotationUtils {
|
||||
* {@link AnnotationAttributes} maps (for compatibility with
|
||||
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
|
||||
* {@code Annotation} instances
|
||||
* @param mergeMode whether the annotation attributes should be created
|
||||
* using <em>merge mode</em>
|
||||
* @return the annotation attributes (a specialized Map) with attribute names as keys
|
||||
* and corresponding attribute values as values (never {@code null})
|
||||
* @since 4.2
|
||||
* @see #postProcessAnnotationAttributes
|
||||
*/
|
||||
static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation,
|
||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap, boolean mergeMode) {
|
||||
|
||||
if (!mergeMode) {
|
||||
annotation = synthesizeAnnotation(annotation, annotatedElement);
|
||||
}
|
||||
static AnnotationAttributes retrieveAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation,
|
||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
|
||||
Class<? extends Annotation> annotationType = annotation.annotationType();
|
||||
AnnotationAttributes attrs = new AnnotationAttributes(annotationType);
|
||||
AnnotationAttributes attributes = new AnnotationAttributes(annotationType);
|
||||
|
||||
for (Method method : getAttributeMethods(annotationType)) {
|
||||
try {
|
||||
Object value = method.invoke(annotation);
|
||||
Object attributeValue = method.invoke(annotation);
|
||||
Object defaultValue = method.getDefaultValue();
|
||||
if (mergeMode && defaultValue != null) {
|
||||
if (ObjectUtils.nullSafeEquals(value, defaultValue)) {
|
||||
value = DEFAULT_VALUE_PLACEHOLDER;
|
||||
}
|
||||
if (defaultValue != null && ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) {
|
||||
attributeValue = new DefaultValueHolder(defaultValue);
|
||||
}
|
||||
attrs.put(method.getName(),
|
||||
adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
attributes.put(method.getName(),
|
||||
adaptValue(annotatedElement, attributeValue, classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (ex instanceof InvocationTargetException) {
|
||||
@@ -1071,7 +1054,8 @@ public abstract class AnnotationUtils {
|
||||
throw new IllegalStateException("Could not obtain annotation attribute value for " + method, ex);
|
||||
}
|
||||
}
|
||||
return attrs;
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1109,7 +1093,6 @@ public abstract class AnnotationUtils {
|
||||
|
||||
if (value instanceof Annotation) {
|
||||
Annotation annotation = (Annotation) value;
|
||||
|
||||
if (nestedAnnotationsAsMap) {
|
||||
return getAnnotationAttributes(annotatedElement, annotation, classValuesAsString, true);
|
||||
}
|
||||
@@ -1120,12 +1103,11 @@ public abstract class AnnotationUtils {
|
||||
|
||||
if (value instanceof Annotation[]) {
|
||||
Annotation[] annotations = (Annotation[]) value;
|
||||
|
||||
if (nestedAnnotationsAsMap) {
|
||||
AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[annotations.length];
|
||||
for (int i = 0; i < annotations.length; i++) {
|
||||
mappedAnnotations[i] = getAnnotationAttributes(annotatedElement, annotations[i],
|
||||
classValuesAsString, true);
|
||||
mappedAnnotations[i] =
|
||||
getAnnotationAttributes(annotatedElement, annotations[i], classValuesAsString, true);
|
||||
}
|
||||
return mappedAnnotations;
|
||||
}
|
||||
@@ -1138,6 +1120,101 @@ public abstract class AnnotationUtils {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the supplied {@link AnnotationAttributes}.
|
||||
* <p>Specifically, this method enforces <em>attribute alias</em> semantics
|
||||
* for annotation attributes that are annotated with {@link AliasFor @AliasFor}
|
||||
* and replaces default value placeholders with their original default values.
|
||||
* @param annotatedElement the element that is annotated with an annotation or
|
||||
* annotation hierarchy from which the supplied attributes were created;
|
||||
* may be {@code null} if unknown
|
||||
* @param attributes the annotation attributes to post-process
|
||||
* @param classValuesAsString whether to convert Class references into Strings (for
|
||||
* compatibility with {@link org.springframework.core.type.AnnotationMetadata})
|
||||
* or to preserve them as Class references
|
||||
* @param nestedAnnotationsAsMap whether to convert nested annotations into
|
||||
* {@link AnnotationAttributes} maps (for compatibility with
|
||||
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
|
||||
* {@code Annotation} instances
|
||||
* @since 4.2
|
||||
* @see #retrieveAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
|
||||
* @see #getDefaultValue(Class, String)
|
||||
*/
|
||||
static void postProcessAnnotationAttributes(AnnotatedElement annotatedElement,
|
||||
AnnotationAttributes attributes, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
|
||||
// Abort?
|
||||
if (attributes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Class<? extends Annotation> annotationType = attributes.annotationType();
|
||||
|
||||
// Track which attribute values have already been replaced so that we can short
|
||||
// circuit the search algorithms.
|
||||
Set<String> valuesAlreadyReplaced = new HashSet<String>();
|
||||
|
||||
// Validate @AliasFor configuration
|
||||
Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
|
||||
for (String attributeName : aliasMap.keySet()) {
|
||||
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||
continue;
|
||||
}
|
||||
Object value = attributes.get(attributeName);
|
||||
boolean valuePresent = (value != null && !(value instanceof DefaultValueHolder));
|
||||
|
||||
for (String aliasedAttributeName : aliasMap.get(attributeName)) {
|
||||
if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object aliasedValue = attributes.get(aliasedAttributeName);
|
||||
boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder));
|
||||
|
||||
// Something to validate or replace with an alias?
|
||||
if (valuePresent || aliasPresent) {
|
||||
if (valuePresent && aliasPresent) {
|
||||
// Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals().
|
||||
if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) {
|
||||
String elementAsString = (annotatedElement != null ? annotatedElement.toString() : "unknown element");
|
||||
throw new AnnotationConfigurationException(String.format(
|
||||
"In AnnotationAttributes for annotation [%s] declared on %s, " +
|
||||
"attribute '%s' and its alias '%s' are declared with values of [%s] and [%s], " +
|
||||
"but only one is permitted.", annotationType.getName(), elementAsString,
|
||||
attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value),
|
||||
ObjectUtils.nullSafeToString(aliasedValue)));
|
||||
}
|
||||
}
|
||||
else if (aliasPresent) {
|
||||
// Replace value with aliasedValue
|
||||
attributes.put(attributeName,
|
||||
adaptValue(annotatedElement, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
|
||||
valuesAlreadyReplaced.add(attributeName);
|
||||
}
|
||||
else {
|
||||
// Replace aliasedValue with value
|
||||
attributes.put(aliasedAttributeName,
|
||||
adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
valuesAlreadyReplaced.add(aliasedAttributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace any remaining placeholders with actual default values
|
||||
for (String attributeName : attributes.keySet()) {
|
||||
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||
continue;
|
||||
}
|
||||
Object value = attributes.get(attributeName);
|
||||
if (value instanceof DefaultValueHolder) {
|
||||
value = ((DefaultValueHolder) value).defaultValue;
|
||||
attributes.put(attributeName,
|
||||
adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the <em>value</em> of the {@code value} attribute of a
|
||||
* single-element Annotation, given an annotation instance.
|
||||
@@ -1436,7 +1513,7 @@ public abstract class AnnotationUtils {
|
||||
return map;
|
||||
}
|
||||
|
||||
map = new HashMap<String, List<String>>();
|
||||
map = new LinkedHashMap<String, List<String>>();
|
||||
for (Method attribute : getAttributeMethods(annotationType)) {
|
||||
List<String> aliasNames = getAttributeAliasNames(attribute);
|
||||
if (!aliasNames.isEmpty()) {
|
||||
@@ -1609,103 +1686,6 @@ public abstract class AnnotationUtils {
|
||||
return (method != null && method.getName().equals("annotationType") && method.getParameterTypes().length == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process the supplied {@link AnnotationAttributes}.
|
||||
* <p>Specifically, this method enforces <em>attribute alias</em> semantics
|
||||
* for annotation attributes that are annotated with {@link AliasFor @AliasFor}
|
||||
* and replaces {@linkplain #DEFAULT_VALUE_PLACEHOLDER placeholders} with their
|
||||
* original default values.
|
||||
* @param element the element that is annotated with an annotation or
|
||||
* annotation hierarchy from which the supplied attributes were created;
|
||||
* may be {@code null} if unknown
|
||||
* @param attributes the annotation attributes to post-process
|
||||
* @param classValuesAsString whether to convert Class references into Strings (for
|
||||
* compatibility with {@link org.springframework.core.type.AnnotationMetadata})
|
||||
* or to preserve them as Class references
|
||||
* @param nestedAnnotationsAsMap whether to convert nested annotations into
|
||||
* {@link AnnotationAttributes} maps (for compatibility with
|
||||
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
|
||||
* {@code Annotation} instances
|
||||
* @since 4.2
|
||||
* @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean, boolean)
|
||||
* @see #DEFAULT_VALUE_PLACEHOLDER
|
||||
* @see #getDefaultValue(Class, String)
|
||||
*/
|
||||
static void postProcessAnnotationAttributes(AnnotatedElement element, AnnotationAttributes attributes,
|
||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
|
||||
// Abort?
|
||||
if (attributes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Class<? extends Annotation> annotationType = attributes.annotationType();
|
||||
|
||||
// Track which attribute values have already been replaced so that we can short
|
||||
// circuit the search algorithms.
|
||||
Set<String> valuesAlreadyReplaced = new HashSet<String>();
|
||||
|
||||
// Validate @AliasFor configuration
|
||||
Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
|
||||
for (String attributeName : aliasMap.keySet()) {
|
||||
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||
continue;
|
||||
}
|
||||
Object value = attributes.get(attributeName);
|
||||
boolean valuePresent = (value != null && value != DEFAULT_VALUE_PLACEHOLDER);
|
||||
|
||||
for (String aliasedAttributeName : aliasMap.get(attributeName)) {
|
||||
if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object aliasedValue = attributes.get(aliasedAttributeName);
|
||||
boolean aliasPresent = (aliasedValue != null && aliasedValue != DEFAULT_VALUE_PLACEHOLDER);
|
||||
|
||||
// Something to validate or replace with an alias?
|
||||
if (valuePresent || aliasPresent) {
|
||||
if (valuePresent && aliasPresent) {
|
||||
// Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals().
|
||||
if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) {
|
||||
String elementAsString = (element != null ? element.toString() : "unknown element");
|
||||
String msg = String.format("In AnnotationAttributes for annotation [%s] declared on [%s], " +
|
||||
"attribute [%s] and its alias [%s] are declared with values of [%s] and [%s], " +
|
||||
"but only one declaration is permitted.", annotationType.getName(),
|
||||
elementAsString, attributeName, aliasedAttributeName,
|
||||
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
else if (aliasPresent) {
|
||||
// Replace value with aliasedValue
|
||||
attributes.put(attributeName,
|
||||
adaptValue(element, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
|
||||
valuesAlreadyReplaced.add(attributeName);
|
||||
}
|
||||
else {
|
||||
// Replace aliasedValue with value
|
||||
attributes.put(aliasedAttributeName,
|
||||
adaptValue(element, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
valuesAlreadyReplaced.add(aliasedAttributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace any remaining placeholders with actual default values
|
||||
for (String attributeName : attributes.keySet()) {
|
||||
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||
continue;
|
||||
}
|
||||
Object value = attributes.get(attributeName);
|
||||
if (value == DEFAULT_VALUE_PLACEHOLDER) {
|
||||
attributes.put(attributeName,
|
||||
adaptValue(element, getDefaultValue(annotationType, attributeName), classValuesAsString,
|
||||
nestedAnnotationsAsMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>If the supplied throwable is an {@link AnnotationConfigurationException},
|
||||
* it will be cast to an {@code AnnotationConfigurationException} and thrown,
|
||||
@@ -2163,4 +2143,14 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class DefaultValueHolder {
|
||||
|
||||
final Object defaultValue;
|
||||
|
||||
public DefaultValueHolder(Object defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -94,8 +94,7 @@ public class AnnotatedElementUtilsTests {
|
||||
public void hasMetaAnnotationTypesOnClassWithMetaDepth2() {
|
||||
assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, TX_NAME));
|
||||
assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Component.class.getName()));
|
||||
assertFalse(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class,
|
||||
ComposedTransactionalComponent.class.getName()));
|
||||
assertFalse(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -111,7 +110,7 @@ public class AnnotatedElementUtilsTests {
|
||||
@Test
|
||||
public void isAnnotatedOnSubclassWithMetaDepth0() {
|
||||
assertFalse("isAnnotated() does not search the class hierarchy.",
|
||||
isAnnotated(SubTransactionalComponentClass.class, TransactionalComponent.class.getName()));
|
||||
isAnnotated(SubTransactionalComponentClass.class, TransactionalComponent.class.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -124,8 +123,7 @@ public class AnnotatedElementUtilsTests {
|
||||
public void isAnnotatedOnClassWithMetaDepth2() {
|
||||
assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, TX_NAME));
|
||||
assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, Component.class.getName()));
|
||||
assertTrue(isAnnotated(ComposedTransactionalComponentClass.class,
|
||||
ComposedTransactionalComponent.class.getName()));
|
||||
assertTrue(isAnnotated(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -167,7 +165,6 @@ public class AnnotatedElementUtilsTests {
|
||||
* type within the class hierarchy. Such undesirable behavior would cause the
|
||||
* logic in {@link org.springframework.context.annotation.ProfileCondition}
|
||||
* to fail.
|
||||
*
|
||||
* @see org.springframework.core.env.EnvironmentSystemIntegrationTests#mostSpecificDerivedClassDrivesEnvironment_withDevEnvAndDerivedDevConfigClass
|
||||
*/
|
||||
@Test
|
||||
@@ -179,7 +176,6 @@ public class AnnotatedElementUtilsTests {
|
||||
|
||||
/**
|
||||
* Note: this functionality is required by {@link org.springframework.context.annotation.ProfileCondition}.
|
||||
*
|
||||
* @see org.springframework.core.env.EnvironmentSystemIntegrationTests
|
||||
*/
|
||||
@Test
|
||||
@@ -187,7 +183,7 @@ public class AnnotatedElementUtilsTests {
|
||||
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(TxFromMultipleComposedAnnotations.class, TX_NAME);
|
||||
assertNotNull("Annotation attributes map for @Transactional on TxFromMultipleComposedAnnotations", attributes);
|
||||
assertEquals("value for TxFromMultipleComposedAnnotations.", asList("TxInheritedComposed", "TxComposed"),
|
||||
attributes.get("value"));
|
||||
attributes.get("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -419,12 +415,12 @@ public class AnnotatedElementUtilsTests {
|
||||
public void getMergedAnnotationAttributesWithInvalidConventionBasedComposedAnnotation() {
|
||||
Class<?> element = InvalidConventionBasedComposedContextConfigClass.class;
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(either(containsString("attribute [value] and its alias [locations]")).or(
|
||||
containsString("attribute [locations] and its alias [value]")));
|
||||
exception.expectMessage(either(containsString("attribute 'value' and its alias 'locations'")).or(
|
||||
containsString("attribute 'locations' and its alias 'value'")));
|
||||
exception.expectMessage(either(
|
||||
containsString("values of [{duplicateDeclaration}] and [{requiredLocationsDeclaration}]")).or(
|
||||
containsString("values of [{requiredLocationsDeclaration}] and [{duplicateDeclaration}]")));
|
||||
exception.expectMessage(containsString("but only one declaration is permitted"));
|
||||
containsString("values of [{duplicateDeclaration}] and [{requiredLocationsDeclaration}]")).or(
|
||||
containsString("values of [{requiredLocationsDeclaration}] and [{duplicateDeclaration}]")));
|
||||
exception.expectMessage(containsString("but only one is permitted"));
|
||||
getMergedAnnotationAttributes(element, ContextConfig.class);
|
||||
}
|
||||
|
||||
@@ -432,11 +428,11 @@ public class AnnotatedElementUtilsTests {
|
||||
public void getMergedAnnotationAttributesWithInvalidAliasedComposedAnnotation() {
|
||||
Class<?> element = InvalidAliasedComposedContextConfigClass.class;
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(either(containsString("attribute [value] and its alias [locations]")).or(
|
||||
containsString("attribute [locations] and its alias [value]")));
|
||||
exception.expectMessage(either(containsString("attribute 'value' and its alias 'locations'")).or(
|
||||
containsString("attribute 'locations' and its alias 'value'")));
|
||||
exception.expectMessage(either(containsString("values of [{duplicateDeclaration}] and [{test.xml}]")).or(
|
||||
containsString("values of [{test.xml}] and [{duplicateDeclaration}]")));
|
||||
exception.expectMessage(containsString("but only one declaration is permitted"));
|
||||
containsString("values of [{test.xml}] and [{duplicateDeclaration}]")));
|
||||
exception.expectMessage(containsString("but only one is permitted"));
|
||||
getMergedAnnotationAttributes(element, ContextConfig.class);
|
||||
}
|
||||
|
||||
@@ -493,11 +489,9 @@ public class AnnotatedElementUtilsTests {
|
||||
/**
|
||||
* <p>{@code AbstractClassWithInheritedAnnotation} declares {@code handleParameterized(T)}; whereas,
|
||||
* {@code ConcreteClassWithInheritedAnnotation} declares {@code handleParameterized(String)}.
|
||||
*
|
||||
* <p>As of Spring 4.2 RC1, {@code AnnotatedElementUtils.processWithFindSemantics()} does not resolve an
|
||||
* <p>As of Spring 4.2, {@code AnnotatedElementUtils.processWithFindSemantics()} does not resolve an
|
||||
* <em>equivalent</em> method in {@code AbstractClassWithInheritedAnnotation} for the <em>bridged</em>
|
||||
* {@code handleParameterized(String)} method.
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
@Test
|
||||
@@ -517,6 +511,7 @@ public class AnnotatedElementUtilsTests {
|
||||
Method[] methods = StringGenericParameter.class.getMethods();
|
||||
Method bridgeMethod = null;
|
||||
Method bridgedMethod = null;
|
||||
|
||||
for (Method method : methods) {
|
||||
if ("getFor".equals(method.getName()) && !method.getParameterTypes()[0].equals(Integer.class)) {
|
||||
if (method.getReturnType().equals(Object.class)) {
|
||||
@@ -546,13 +541,13 @@ public class AnnotatedElementUtilsTests {
|
||||
String qualifier = "aliasForQualifier";
|
||||
|
||||
// 1) Find and merge AnnotationAttributes from the annotation hierarchy
|
||||
AnnotationAttributes attributes = findMergedAnnotationAttributes(AliasedTransactionalComponentClass.class,
|
||||
AliasedTransactional.class);
|
||||
AnnotationAttributes attributes = findMergedAnnotationAttributes(
|
||||
AliasedTransactionalComponentClass.class, AliasedTransactional.class);
|
||||
assertNotNull("@AliasedTransactional on AliasedTransactionalComponentClass.", attributes);
|
||||
|
||||
// 2) Synthesize the AnnotationAttributes back into the target annotation
|
||||
AliasedTransactional annotation = AnnotationUtils.synthesizeAnnotation(attributes,
|
||||
AliasedTransactional.class, AliasedTransactionalComponentClass.class);
|
||||
AliasedTransactional.class, AliasedTransactionalComponentClass.class);
|
||||
assertNotNull(annotation);
|
||||
|
||||
// 3) Verify that the AnnotationAttributes and synthesized annotation are equivalent
|
||||
@@ -573,8 +568,8 @@ public class AnnotatedElementUtilsTests {
|
||||
|
||||
@Test
|
||||
public void findMergedAnnotationForMultipleMetaAnnotationsWithClashingAttributeNames() {
|
||||
final String[] xmlLocations = asArray("test.xml");
|
||||
final String[] propFiles = asArray("test.properties");
|
||||
String[] xmlLocations = asArray("test.xml");
|
||||
String[] propFiles = asArray("test.properties");
|
||||
|
||||
Class<?> element = AliasedComposedContextConfigAndTestPropSourceClass.class;
|
||||
|
||||
@@ -600,6 +595,7 @@ public class AnnotatedElementUtilsTests {
|
||||
String[] expected = asArray("com.example.app.test");
|
||||
Class<?> element = TestComponentScanClass.class;
|
||||
AnnotationAttributes attributes = findMergedAnnotationAttributes(element, ComponentScan.class);
|
||||
|
||||
assertNotNull("Should find @ComponentScan on " + element, attributes);
|
||||
assertArrayEquals("basePackages for " + element, expected, attributes.getStringArray("basePackages"));
|
||||
|
||||
@@ -628,9 +624,10 @@ public class AnnotatedElementUtilsTests {
|
||||
|
||||
@Test
|
||||
public void findMergedAnnotationWithLocalAliasesThatConflictWithAttributesInMetaAnnotationByConvention() {
|
||||
final String[] EMPTY = new String[] {};
|
||||
final String[] EMPTY = new String[0];
|
||||
Class<?> element = SpringAppConfigClass.class;
|
||||
ContextConfig contextConfig = findMergedAnnotation(element, ContextConfig.class);
|
||||
|
||||
assertNotNull("Should find @ContextConfig on " + element, contextConfig);
|
||||
assertArrayEquals("locations for " + element, EMPTY, contextConfig.locations());
|
||||
// 'value' in @SpringAppConfig should not override 'value' in @ContextConfig
|
||||
|
||||
Reference in New Issue
Block a user