Consistent alias processing behind AnnotatedTypeMetadata abstraction (also for ASM)

Issue: SPR-14427
This commit is contained in:
Juergen Hoeller 2016-07-15 13:59:25 +02:00
parent b1663585dc
commit 3d3407c789
21 changed files with 320 additions and 679 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -79,9 +79,10 @@ public class AnnotationScopeMetadataResolver implements ScopeMetadataResolver {
ScopeMetadata metadata = new ScopeMetadata();
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(annDef.getMetadata(), this.scopeAnnotationType);
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
annDef.getMetadata(), this.scopeAnnotationType);
if (attributes != null) {
metadata.setScopeName(attributes.getAliasedString("value", this.scopeAnnotationType, definition.getSource()));
metadata.setScopeName(attributes.getString("value"));
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = this.defaultProxyMode;

View File

@ -122,7 +122,7 @@ class ComponentScanAnnotationParser {
}
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getAliasedStringArray("basePackages", ComponentScan.class, declaringClass);
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
@ -148,7 +148,7 @@ class ComponentScanAnnotationParser {
List<TypeFilter> typeFilters = new ArrayList<>();
FilterType filterType = filterAttributes.getEnum("type");
for (Class<?> filterClass : filterAttributes.getAliasedClassArray("classes", ComponentScan.Filter.class, null)) {
for (Class<?> filterClass : filterAttributes.getClassArray("classes")) {
switch (filterType) {
case ANNOTATION:
Assert.isAssignable(Annotation.class, filterClass,

View File

@ -236,7 +236,7 @@ class ConfigurationClassBeanDefinitionReader {
ScopedProxyMode proxyMode = ScopedProxyMode.NO;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
if (attributes != null) {
beanDef.setScope(attributes.getAliasedString("value", Scope.class, configClass.getResource()));
beanDef.setScope(attributes.getString("value"));
proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = ScopedProxyMode.NO;

View File

@ -287,8 +287,9 @@ class ConfigurationClassParser {
// Process any @ImportResource annotations
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
String[] resources = importResource.getAliasedStringArray("locations", ImportResource.class, sourceClass);
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);

View File

@ -955,7 +955,7 @@ public class ConfigurationClassPostProcessorTests {
@ComponentScan(basePackages = "org.springframework.context.annotation.componentscan.simple")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface ComposedConfiguration {
public @interface ComposedConfiguration {
}
@ComposedConfiguration
@ -966,7 +966,7 @@ public class ConfigurationClassPostProcessorTests {
@ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface ComposedConfigurationWithAttributeOverrides {
public @interface ComposedConfigurationWithAttributeOverrides {
String[] basePackages() default {};
@ -985,7 +985,7 @@ public class ConfigurationClassPostProcessorTests {
@ComposedConfigurationWithAttributeOverrides
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface ComposedComposedConfigurationWithAttributeOverrides {
public @interface ComposedComposedConfigurationWithAttributeOverrides {
String[] basePackages() default {};
}
@ -997,14 +997,14 @@ public class ConfigurationClassPostProcessorTests {
@ComponentScan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface MetaComponentScan {
public @interface MetaComponentScan {
}
@MetaComponentScan
@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface MetaComponentScanConfigurationWithAttributeOverrides {
public @interface MetaComponentScanConfigurationWithAttributeOverrides {
String[] basePackages() default {};
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -35,13 +35,13 @@ import org.springframework.util.ObjectUtils;
* @param <S> the type of source supported by this extractor
* @see Annotation
* @see AliasFor
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
* @see AnnotationUtils#synthesizeAnnotation(Annotation, Object)
*/
abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements AnnotationAttributeExtractor<S> {
private final Class<? extends Annotation> annotationType;
private final AnnotatedElement annotatedElement;
private final Object annotatedElement;
private final S source;
@ -56,7 +56,7 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
* @param source the underlying source of annotation attributes; never {@code null}
*/
AbstractAliasAwareAnnotationAttributeExtractor(
Class<? extends Annotation> annotationType, AnnotatedElement annotatedElement, S source) {
Class<? extends Annotation> annotationType, Object annotatedElement, S source) {
Assert.notNull(annotationType, "annotationType must not be null");
Assert.notNull(source, "source must not be null");
@ -73,7 +73,7 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
}
@Override
public final AnnotatedElement getAnnotatedElement() {
public final Object getAnnotatedElement() {
return this.annotatedElement;
}
@ -89,18 +89,18 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
List<String> aliasNames = this.attributeAliasMap.get(attributeName);
if (aliasNames != null) {
Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName);
Object defaultValue = AnnotationUtils.getDefaultValue(this.annotationType, attributeName);
for (String aliasName : aliasNames) {
Object aliasValue = getRawAttributeValue(aliasName);
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
!ObjectUtils.nullSafeEquals(aliasValue, defaultValue)) {
String elementName = (getAnnotatedElement() != null ? getAnnotatedElement().toString() : "unknown element");
String elementName = (this.annotatedElement != null ? this.annotatedElement.toString() : "unknown element");
throw new AnnotationConfigurationException(String.format(
"In annotation [%s] declared on %s and synthesized from [%s], attribute '%s' and its " +
"alias '%s' are present with values of [%s] and [%s], but only one is permitted.",
getAnnotationType().getName(), elementName, getSource(), attributeName, aliasName,
this.annotationType.getName(), elementName, this.source, attributeName, aliasName,
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue)));
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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,7 +17,6 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
/**
@ -44,7 +43,7 @@ interface AnnotationAttributeExtractor<S> {
* type supported by this extractor.
* @return the annotated element, or {@code null} if unknown
*/
AnnotatedElement getAnnotatedElement();
Object getAnnotatedElement();
/**
* Get the underlying source of annotation attributes.

View File

@ -50,30 +50,21 @@ import org.springframework.util.StringUtils;
@SuppressWarnings("serial")
public class AnnotationAttributes extends LinkedHashMap<String, Object> {
private final Class<? extends Annotation> annotationType;
private static final String UNKNOWN = "unknown";
private Class<? extends Annotation> annotationType;
private final String displayName;
boolean validated = false;
/**
* Create a new, empty {@link AnnotationAttributes} instance.
*/
public AnnotationAttributes() {
this.annotationType = null;
this.displayName = "unknown";
}
/**
* Create a new, empty {@link AnnotationAttributes} instance for the
* specified {@code annotationType}.
* @param annotationType the type of annotation represented by this
* {@code AnnotationAttributes} instance; never {@code null}
* @since 4.2
*/
public AnnotationAttributes(Class<? extends Annotation> annotationType) {
Assert.notNull(annotationType, "annotationType must not be null");
this.annotationType = annotationType;
this.displayName = annotationType.getName();
this.displayName = UNKNOWN;
}
/**
@ -84,20 +75,68 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
public AnnotationAttributes(int initialCapacity) {
super(initialCapacity);
this.annotationType = null;
this.displayName = "unknown";
this.displayName = UNKNOWN;
}
/**
* Create a new {@link AnnotationAttributes} instance, wrapping the
* provided map and all its <em>key-value</em> pairs.
* @param map original source of annotation attribute <em>key-value</em>
* pairs
* Create a new, empty {@link AnnotationAttributes} instance for the
* specified {@code annotationType}.
* @param annotationType the type of annotation represented by this
* {@code AnnotationAttributes} instance; never {@code null}
* @since 4.2
*/
public AnnotationAttributes(Class<? extends Annotation> annotationType) {
Assert.notNull(annotationType, "'annotationType' must not be null");
this.annotationType = annotationType;
this.displayName = annotationType.getName();
}
/**
* Create a new, empty {@link AnnotationAttributes} instance for the
* specified {@code annotationType}.
* @param annotationType the type of annotation represented by this
* {@code AnnotationAttributes} instance; never {@code null}
* @param classLoader the ClassLoader to try to load the annotation type on,
* or {@code null} to just store the annotation type name
* @since 4.3.2
*/
@SuppressWarnings("unchecked")
public AnnotationAttributes(String annotationType, ClassLoader classLoader) {
Assert.notNull(annotationType, "'annotationType' must not be null");
if (classLoader != null) {
try {
this.annotationType = (Class<? extends Annotation>) classLoader.loadClass(annotationType);
}
catch (ClassNotFoundException ex) {
// Annotation Class not resolvable
}
}
this.displayName = annotationType;
}
/**
* Create a new {@link AnnotationAttributes} instance, wrapping the provided
* map and all its <em>key-value</em> pairs.
* @param map original source of annotation attribute <em>key-value</em> pairs
* @see #fromMap(Map)
*/
public AnnotationAttributes(Map<String, Object> map) {
super(map);
this.annotationType = null;
this.displayName = "unknown";
this.displayName = UNKNOWN;
}
/**
* Create a new {@link AnnotationAttributes} instance, wrapping the provided
* map and all its <em>key-value</em> pairs.
* @param other original source of annotation attribute <em>key-value</em> pairs
* @see #fromMap(Map)
*/
public AnnotationAttributes(AnnotationAttributes other) {
super(other);
this.annotationType = other.annotationType;
this.displayName = other.displayName;
this.validated = other.validated;
}
@ -124,34 +163,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
return getRequiredAttribute(attributeName, String.class);
}
/**
* Get the value stored under the specified {@code attributeName} as a
* string, taking into account alias semantics defined via
* {@link AliasFor @AliasFor}.
* <p>If there is no value stored under the specified {@code attributeName}
* but the attribute has an alias declared via {@code @AliasFor}, the
* value of the alias will be returned.
* @param attributeName the name of the attribute to get; never
* {@code null} or empty
* @param annotationType the type of annotation represented by this
* {@code AnnotationAttributes} instance; never {@code null}
* @param annotationSource the source of the annotation represented by
* this {@code AnnotationAttributes} (e.g., the {@link AnnotatedElement});
* or {@code null} if unknown
* @return the string value
* @throws IllegalArgumentException if the attribute and its alias do
* not exist or are not of type {@code String}
* @throws AnnotationConfigurationException if the attribute and its
* alias are both present with different non-empty values
* @since 4.2
* @see ObjectUtils#isEmpty(Object)
*/
public String getAliasedString(String attributeName, Class<? extends Annotation> annotationType,
Object annotationSource) {
return getRequiredAttributeWithAlias(attributeName, annotationType, annotationSource, String.class);
}
/**
* Get the value stored under the specified {@code attributeName} as an
* array of strings.
@ -168,33 +179,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
return getRequiredAttribute(attributeName, String[].class);
}
/**
* Get the value stored under the specified {@code attributeName} as an
* array of strings, taking into account alias semantics defined via
* {@link AliasFor @AliasFor}.
* <p>If there is no value stored under the specified {@code attributeName}
* but the attribute has an alias declared via {@code @AliasFor}, the
* value of the alias will be returned.
* @param attributeName the name of the attribute to get; never
* {@code null} or empty
* @param annotationType the type of annotation represented by this
* {@code AnnotationAttributes} instance; never {@code null}
* @param annotationSource the source of the annotation represented by
* this {@code AnnotationAttributes} (e.g., the {@link AnnotatedElement});
* or {@code null} if unknown
* @return the array of strings
* @throws IllegalArgumentException if the attribute and its alias do
* not exist or are not of type {@code String[]}
* @throws AnnotationConfigurationException if the attribute and its
* alias are both present with different non-empty values
* @since 4.2
*/
public String[] getAliasedStringArray(String attributeName, Class<? extends Annotation> annotationType,
Object annotationSource) {
return getRequiredAttributeWithAlias(attributeName, annotationType, annotationSource, String[].class);
}
/**
* Get the value stored under the specified {@code attributeName} as a
* boolean.
@ -266,33 +250,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
return getRequiredAttribute(attributeName, Class[].class);
}
/**
* Get the value stored under the specified {@code attributeName} as an
* array of classes, taking into account alias semantics defined via
* {@link AliasFor @AliasFor}.
* <p>If there is no value stored under the specified {@code attributeName}
* but the attribute has an alias declared via {@code @AliasFor}, the
* value of the alias will be returned.
* @param attributeName the name of the attribute to get; never
* {@code null} or empty
* @param annotationType the type of annotation represented by this
* {@code AnnotationAttributes} instance; never {@code null}
* @param annotationSource the source of the annotation represented by
* this {@code AnnotationAttributes} (e.g., the {@link AnnotatedElement});
* or {@code null} if unknown
* @return the array of classes
* @throws IllegalArgumentException if the attribute and its alias do
* not exist or are not of type {@code Class[]}
* @throws AnnotationConfigurationException if the attribute and its
* alias are both present with different non-empty values
* @since 4.2
*/
public Class<?>[] getAliasedClassArray(String attributeName, Class<? extends Annotation> annotationType,
Object annotationSource) {
return getRequiredAttributeWithAlias(attributeName, annotationType, annotationSource, Class[].class);
}
/**
* Get the {@link AnnotationAttributes} stored under the specified
* {@code attributeName}.
@ -378,7 +335,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
*/
@SuppressWarnings("unchecked")
private <T> T getRequiredAttribute(String attributeName, Class<T> expectedType) {
Assert.hasText(attributeName, "attributeName must not be null or empty");
Assert.hasText(attributeName, "'attributeName' must not be null or empty");
Object value = get(attributeName);
assertAttributePresence(attributeName, value);
assertNotException(attributeName, value);
@ -418,9 +375,9 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
private <T> T getRequiredAttributeWithAlias(String attributeName, Class<? extends Annotation> annotationType,
Object annotationSource, Class<T> expectedType) {
Assert.hasText(attributeName, "attributeName must not be null or empty");
Assert.notNull(annotationType, "annotationType must not be null");
Assert.notNull(expectedType, "expectedType must not be null");
Assert.hasText(attributeName, "'attributeName' must not be null or empty");
Assert.notNull(annotationType, "'annotationType' must not be null");
Assert.notNull(expectedType, "'expectedType' must not be null");
T attributeValue = getAttribute(attributeName, expectedType);
@ -433,8 +390,8 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
if (!attributeEmpty && !aliasEmpty && !ObjectUtils.nullSafeEquals(attributeValue, aliasValue)) {
String elementName = (annotationSource == null ? "unknown element" : annotationSource.toString());
String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] " +
"are present with values of [%s] and [%s], but only one is permitted.",
String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its " +
"alias [%s] are present with values of [%s] and [%s], but only one is permitted.",
annotationType.getName(), elementName, attributeName, aliasName,
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
throw new AnnotationConfigurationException(msg);

View File

@ -23,6 +23,7 @@ import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
@ -590,7 +591,7 @@ public abstract class AnnotationUtils {
static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
Boolean found = annotatedInterfaceCache.get(iface);
if (found != null) {
return found.booleanValue();
return found;
}
found = Boolean.FALSE;
for (Method ifcMethod : iface.getMethods()) {
@ -605,7 +606,7 @@ public abstract class AnnotationUtils {
}
}
annotatedInterfaceCache.put(iface, found);
return found.booleanValue();
return found;
}
/**
@ -857,14 +858,14 @@ public abstract class AnnotationUtils {
AnnotationCacheKey cacheKey = new AnnotationCacheKey(annotationType, metaAnnotationType);
Boolean metaPresent = metaPresentCache.get(cacheKey);
if (metaPresent != null) {
return metaPresent.booleanValue();
return metaPresent;
}
metaPresent = Boolean.FALSE;
if (findAnnotation(annotationType, metaAnnotationType, false) != null) {
metaPresent = Boolean.TRUE;
}
metaPresentCache.put(cacheKey, metaPresent);
return metaPresent.booleanValue();
return metaPresent;
}
/**
@ -987,6 +988,13 @@ public abstract class AnnotationUtils {
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
return getAnnotationAttributes(
(Object) annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap);
}
private static AnnotationAttributes getAnnotationAttributes(Object annotatedElement,
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
AnnotationAttributes attributes =
retrieveAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap);
postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, nestedAnnotationsAsMap);
@ -1021,7 +1029,7 @@ public abstract class AnnotationUtils {
* @since 4.2
* @see #postProcessAnnotationAttributes
*/
static AnnotationAttributes retrieveAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation,
static AnnotationAttributes retrieveAnnotationAttributes(Object annotatedElement, Annotation annotation,
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
Class<? extends Annotation> annotationType = annotation.annotationType();
@ -1065,14 +1073,14 @@ public abstract class AnnotationUtils {
* {@code Annotation} instances
* @return the adapted value, or the original value if no adaptation is needed
*/
static Object adaptValue(AnnotatedElement annotatedElement, Object value, boolean classValuesAsString,
static Object adaptValue(Object annotatedElement, Object value, boolean classValuesAsString,
boolean nestedAnnotationsAsMap) {
if (classValuesAsString) {
if (value instanceof Class) {
if (value instanceof Class<?>) {
return ((Class<?>) value).getName();
}
else if (value instanceof Class[]) {
else if (value instanceof Class<?>[]) {
Class<?>[] clazzArray = (Class<?>[]) value;
String[] classNames = new String[clazzArray.length];
for (int i = 0; i < clazzArray.length; i++) {
@ -1111,6 +1119,64 @@ public abstract class AnnotationUtils {
return value;
}
/**
* Register the annotation-declared default values for the given attributes,
* if available.
* @param attributes the annotation attributes to process
* @since 4.3.2
*/
public static void registerDefaultValues(AnnotationAttributes attributes) {
// Only do defaults scanning for public annotations; we'd run into
// IllegalAccessExceptions otherwise, and we don't want to mess with
// accessibility in a SecurityManager environment.
Class<?> annotationType = attributes.annotationType();
if (annotationType != null && Modifier.isPublic(annotationType.getModifiers())) {
// Check declared default values of attributes in the annotation type.
Method[] annotationAttributes = annotationType.getMethods();
for (Method annotationAttribute : annotationAttributes) {
String attributeName = annotationAttribute.getName();
Object defaultValue = annotationAttribute.getDefaultValue();
if (defaultValue != null && !attributes.containsKey(attributeName)) {
if (defaultValue instanceof Annotation) {
defaultValue = getAnnotationAttributes((Annotation) defaultValue, false, true);
}
else if (defaultValue instanceof Annotation[]) {
Annotation[] realAnnotations = (Annotation[]) defaultValue;
AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
for (int i = 0; i < realAnnotations.length; i++) {
mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], false, true);
}
defaultValue = mappedAnnotations;
}
attributes.put(attributeName, new DefaultValueHolder(defaultValue));
}
}
}
}
/**
* Post-process the supplied {@link AnnotationAttributes}, preserving nested
* annotations as {@code Annotation} instances.
* <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
* @since 4.3.2
* @see #postProcessAnnotationAttributes(Object, AnnotationAttributes, boolean, boolean)
* @see #getDefaultValue(Class, String)
*/
public static void postProcessAnnotationAttributes(Object annotatedElement,
AnnotationAttributes attributes, boolean classValuesAsString) {
postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, false);
}
/**
* Post-process the supplied {@link AnnotationAttributes}.
* <p>Specifically, this method enforces <em>attribute alias</em> semantics
@ -1128,10 +1194,10 @@ public abstract class AnnotationUtils {
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
* {@code Annotation} instances
* @since 4.2
* @see #retrieveAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
* @see #retrieveAnnotationAttributes(Object, Annotation, boolean, boolean)
* @see #getDefaultValue(Class, String)
*/
static void postProcessAnnotationAttributes(AnnotatedElement annotatedElement,
static void postProcessAnnotationAttributes(Object annotatedElement,
AnnotationAttributes attributes, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
// Abort?
@ -1145,51 +1211,55 @@ public abstract class AnnotationUtils {
// circuit the search algorithms.
Set<String> valuesAlreadyReplaced = new HashSet<>();
// 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)) {
if (!attributes.validated) {
// 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));
Object aliasedValue = attributes.get(aliasedAttributeName);
boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder));
for (String aliasedAttributeName : aliasMap.get(attributeName)) {
if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
continue;
}
// 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)));
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);
}
}
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);
}
}
}
attributes.validated = true;
}
// Replace any remaining placeholders with actual default values
@ -1329,8 +1399,12 @@ public abstract class AnnotationUtils {
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
* @see #synthesizeAnnotation(Class)
*/
@SuppressWarnings("unchecked")
public static <A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement) {
return synthesizeAnnotation(annotation, (Object) annotatedElement);
}
@SuppressWarnings("unchecked")
static <A extends Annotation> A synthesizeAnnotation(A annotation, Object annotatedElement) {
if (annotation == null) {
return null;
}
@ -1435,7 +1509,7 @@ public abstract class AnnotationUtils {
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
*/
public static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, AnnotatedElement annotatedElement) {
static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, Object annotatedElement) {
if (annotations == null) {
return null;
}
@ -1463,7 +1537,7 @@ public abstract class AnnotationUtils {
* {@code @AliasFor} is detected
* @since 4.2.1
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
* @see #synthesizeAnnotationArray(Annotation[], AnnotatedElement)
* @see #synthesizeAnnotationArray(Annotation[], Object)
*/
@SuppressWarnings("unchecked")
static <A extends Annotation> A[] synthesizeAnnotationArray(Map<String, Object>[] maps, Class<A> annotationType) {
@ -1551,7 +1625,7 @@ public abstract class AnnotationUtils {
private static boolean isSynthesizable(Class<? extends Annotation> annotationType) {
Boolean synthesizable = synthesizableCache.get(annotationType);
if (synthesizable != null) {
return synthesizable.booleanValue();
return synthesizable;
}
synthesizable = Boolean.FALSE;
@ -1579,7 +1653,7 @@ public abstract class AnnotationUtils {
}
synthesizableCache.put(annotationType, synthesizable);
return synthesizable.booleanValue();
return synthesizable;
}
/**

View File

@ -17,7 +17,6 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import org.springframework.util.ReflectionUtils;
@ -32,7 +31,7 @@ import org.springframework.util.ReflectionUtils;
* @see AliasFor
* @see AbstractAliasAwareAnnotationAttributeExtractor
* @see MapAnnotationAttributeExtractor
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
* @see AnnotationUtils#synthesizeAnnotation
*/
class DefaultAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttributeExtractor<Annotation> {
@ -42,7 +41,7 @@ class DefaultAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAt
* @param annotatedElement the element that is annotated with the supplied
* annotation; may be {@code null} if unknown
*/
DefaultAnnotationAttributeExtractor(Annotation annotation, AnnotatedElement annotatedElement) {
DefaultAnnotationAttributeExtractor(Annotation annotation, Object annotatedElement) {
super(annotation.annotationType(), annotatedElement, annotation);
}

View File

@ -27,8 +27,6 @@ import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import static org.springframework.core.annotation.AnnotationUtils.*;
/**
* Implementation of the {@link AnnotationAttributeExtractor} strategy that
* is backed by a {@link Map}.
@ -90,9 +88,9 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
Map<String, Object> originalAttributes, Class<? extends Annotation> annotationType) {
Map<String, Object> attributes = new LinkedHashMap<>(originalAttributes);
Map<String, List<String>> attributeAliasMap = getAttributeAliasMap(annotationType);
Map<String, List<String>> attributeAliasMap = AnnotationUtils.getAttributeAliasMap(annotationType);
for (Method attributeMethod : getAttributeMethods(annotationType)) {
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType)) {
String attributeName = attributeMethod.getName();
Object attributeValue = attributes.get(attributeName);
@ -113,7 +111,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
// if aliases not present, check default
if (attributeValue == null) {
Object defaultValue = getDefaultValue(annotationType, attributeName);
Object defaultValue = AnnotationUtils.getDefaultValue(annotationType, attributeName);
if (defaultValue != null) {
attributeValue = defaultValue;
attributes.put(attributeName, attributeValue);
@ -146,7 +144,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
Class<? extends Annotation> nestedAnnotationType =
(Class<? extends Annotation>) requiredReturnType;
Map<String, Object> map = (Map<String, Object>) attributeValue;
attributes.put(attributeName, synthesizeAnnotation(map, nestedAnnotationType, null));
attributes.put(attributeName, AnnotationUtils.synthesizeAnnotation(map, nestedAnnotationType, null));
converted = true;
}
@ -157,7 +155,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
Class<? extends Annotation> nestedAnnotationType =
(Class<? extends Annotation>) requiredReturnType.getComponentType();
Map<String, Object>[] maps = (Map<String, Object>[]) attributeValue;
attributes.put(attributeName, synthesizeAnnotationArray(maps, nestedAnnotationType));
attributes.put(attributeName, AnnotationUtils.synthesizeAnnotationArray(maps, nestedAnnotationType));
converted = true;
}

View File

@ -27,11 +27,9 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import static org.springframework.core.annotation.AnnotationUtils.*;
import static org.springframework.util.ReflectionUtils.*;
/**
* {@link InvocationHandler} for an {@link Annotation} that Spring has
* <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
@ -63,22 +61,21 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (isEqualsMethod(method)) {
if (ReflectionUtils.isEqualsMethod(method)) {
return annotationEquals(args[0]);
}
if (isHashCodeMethod(method)) {
if (ReflectionUtils.isHashCodeMethod(method)) {
return annotationHashCode();
}
if (isToStringMethod(method)) {
if (ReflectionUtils.isToStringMethod(method)) {
return annotationToString();
}
if (isAnnotationTypeMethod(method)) {
if (AnnotationUtils.isAnnotationTypeMethod(method)) {
return annotationType();
}
if (!isAttributeMethod(method)) {
String msg = String.format("Method [%s] is unsupported for synthesized annotation type [%s]",
method, annotationType());
throw new AnnotationConfigurationException(msg);
if (!AnnotationUtils.isAttributeMethod(method)) {
throw new AnnotationConfigurationException(String.format(
"Method [%s] is unsupported for synthesized annotation type [%s]", method, annotationType()));
}
return getAttributeValue(method);
}
@ -100,10 +97,10 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
// Synthesize nested annotations before returning them.
if (value instanceof Annotation) {
value = synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement());
value = AnnotationUtils.synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement());
}
else if (value instanceof Annotation[]) {
value = synthesizeAnnotationArray((Annotation[]) value, this.attributeExtractor.getAnnotatedElement());
value = AnnotationUtils.synthesizeAnnotationArray((Annotation[]) value, this.attributeExtractor.getAnnotatedElement());
}
this.valueCache.put(attributeName, value);
@ -164,9 +161,9 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
return false;
}
for (Method attributeMethod : getAttributeMethods(annotationType())) {
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType())) {
Object thisValue = getAttributeValue(attributeMethod);
Object otherValue = invokeMethod(attributeMethod, other);
Object otherValue = ReflectionUtils.invokeMethod(attributeMethod, other);
if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
return false;
}
@ -181,7 +178,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
private int annotationHashCode() {
int result = 0;
for (Method attributeMethod : getAttributeMethods(annotationType())) {
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType())) {
Object value = getAttributeValue(attributeMethod);
int hashCode;
if (value.getClass().isArray()) {
@ -239,7 +236,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
private String annotationToString() {
StringBuilder sb = new StringBuilder("@").append(annotationType().getName()).append("(");
Iterator<Method> iterator = getAttributeMethods(annotationType()).iterator();
Iterator<Method> iterator = AnnotationUtils.getAttributeMethods(annotationType()).iterator();
while (iterator.hasNext()) {
Method attributeMethod = iterator.next();
sb.append(attributeMethod.getName());

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2016 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.
@ -58,7 +58,7 @@ abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor {
@Override
public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
String annotationType = Type.getType(asmTypeDescriptor).getClassName();
AnnotationAttributes nestedAttributes = new AnnotationAttributes();
AnnotationAttributes nestedAttributes = new AnnotationAttributes(annotationType, this.classLoader);
this.attributes.put(attributeName, nestedAttributes);
return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
}

View File

@ -44,8 +44,6 @@ import org.springframework.util.ObjectUtils;
*/
final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {
private final String annotationType;
private final MultiValueMap<String, AnnotationAttributes> attributesMap;
private final Map<String, Set<String>> metaAnnotationMap;
@ -55,38 +53,41 @@ final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttrib
MultiValueMap<String, AnnotationAttributes> attributesMap, Map<String, Set<String>> metaAnnotationMap,
ClassLoader classLoader) {
super(annotationType, new AnnotationAttributes(), classLoader);
this.annotationType = annotationType;
super(annotationType, new AnnotationAttributes(annotationType, classLoader), classLoader);
this.attributesMap = attributesMap;
this.metaAnnotationMap = metaAnnotationMap;
}
@Override
public void doVisitEnd(Class<?> annotationClass) {
super.doVisitEnd(annotationClass);
List<AnnotationAttributes> attributes = this.attributesMap.get(this.annotationType);
if (attributes == null) {
this.attributesMap.add(this.annotationType, this.attributes);
}
else {
attributes.add(0, this.attributes);
}
Set<Annotation> visited = new LinkedHashSet<>();
Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass);
if (!ObjectUtils.isEmpty(metaAnnotations)) {
for (Annotation metaAnnotation : metaAnnotations) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) {
recursivelyCollectMetaAnnotations(visited, metaAnnotation);
public void visitEnd() {
super.visitEnd();
Class<?> annotationClass = this.attributes.annotationType();
if (annotationClass != null) {
List<AnnotationAttributes> attributeList = this.attributesMap.get(this.annotationType);
if (attributeList == null) {
this.attributesMap.add(this.annotationType, this.attributes);
}
else {
attributeList.add(0, this.attributes);
}
Set<Annotation> visited = new LinkedHashSet<>();
Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass);
if (!ObjectUtils.isEmpty(metaAnnotations)) {
for (Annotation metaAnnotation : metaAnnotations) {
if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) {
recursivelyCollectMetaAnnotations(visited, metaAnnotation);
}
}
}
}
if (this.metaAnnotationMap != null) {
Set<String> metaAnnotationTypeNames = new LinkedHashSet<>(visited.size());
for (Annotation ann : visited) {
metaAnnotationTypeNames.add(ann.annotationType().getName());
if (this.metaAnnotationMap != null) {
Set<String> metaAnnotationTypeNames = new LinkedHashSet<>(visited.size());
for (Annotation ann : visited) {
metaAnnotationTypeNames.add(ann.annotationType().getName());
}
this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
}
this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
}
}

View File

@ -131,7 +131,8 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
this.attributesMap, this.metaAnnotationMap, annotationName);
return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
return AnnotationReadingVisitorUtils.convertClassValues(
"class '" + getClassName() + "'", this.classLoader, raw, classValuesAsString);
}
@Override
@ -148,7 +149,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
}
for (AnnotationAttributes raw : attributes) {
for (Map.Entry<String, Object> entry : AnnotationReadingVisitorUtils.convertClassValues(
this.classLoader, raw, classValuesAsString).entrySet()) {
"class '" + getClassName() + "'", this.classLoader, raw, classValuesAsString).entrySet()) {
allAttributes.add(entry.getKey(), entry.getValue());
}
}

View File

@ -41,25 +41,29 @@ import org.springframework.util.ObjectUtils;
*/
abstract class AnnotationReadingVisitorUtils {
public static AnnotationAttributes convertClassValues(ClassLoader classLoader, AnnotationAttributes original,
boolean classValuesAsString) {
public static AnnotationAttributes convertClassValues(Object annotatedElement,
ClassLoader classLoader, AnnotationAttributes original, boolean classValuesAsString) {
if (original == null) {
return null;
}
AnnotationAttributes result = new AnnotationAttributes(original.size());
for (Map.Entry<String, Object> entry : original.entrySet()) {
AnnotationAttributes result = new AnnotationAttributes(original);
AnnotationUtils.postProcessAnnotationAttributes(annotatedElement, result, classValuesAsString);
for (Map.Entry<String, Object> entry : result.entrySet()) {
try {
Object value = entry.getValue();
if (value instanceof AnnotationAttributes) {
value = convertClassValues(classLoader, (AnnotationAttributes) value, classValuesAsString);
value = convertClassValues(
annotatedElement, classLoader, (AnnotationAttributes) value, classValuesAsString);
}
else if (value instanceof AnnotationAttributes[]) {
AnnotationAttributes[] values = (AnnotationAttributes[]) value;
for (int i = 0; i < values.length; i++) {
values[i] = convertClassValues(classLoader, values[i], classValuesAsString);
values[i] = convertClassValues(annotatedElement, classLoader, values[i], classValuesAsString);
}
value = values;
}
else if (value instanceof Type) {
value = (classValuesAsString ? ((Type) value).getClassName() :
@ -67,7 +71,8 @@ abstract class AnnotationReadingVisitorUtils {
}
else if (value instanceof Type[]) {
Type[] array = (Type[]) value;
Object[] convArray = (classValuesAsString ? new String[array.length] : new Class<?>[array.length]);
Object[] convArray =
(classValuesAsString ? new String[array.length] : new Class<?>[array.length]);
for (int i = 0; i < array.length; i++) {
convArray[i] = (classValuesAsString ? array[i].getClassName() :
classLoader.loadClass(array[i].getClassName()));
@ -75,11 +80,11 @@ abstract class AnnotationReadingVisitorUtils {
value = convArray;
}
else if (classValuesAsString) {
if (value instanceof Class) {
if (value instanceof Class<?>) {
value = ((Class<?>) value).getName();
}
else if (value instanceof Class[]) {
Class<?>[] clazzArray = (Class[]) value;
else if (value instanceof Class<?>[]) {
Class<?>[] clazzArray = (Class<?>[]) value;
String[] newValue = new String[clazzArray.length];
for (int i = 0; i < clazzArray.length; i++) {
newValue[i] = clazzArray[i].getName();
@ -87,13 +92,14 @@ abstract class AnnotationReadingVisitorUtils {
value = newValue;
}
}
result.put(entry.getKey(), value);
entry.setValue(value);
}
catch (Exception ex) {
// Class not found - can't resolve class reference in annotation attribute.
result.put(entry.getKey(), ex);
}
}
return result;
}
@ -123,13 +129,12 @@ abstract class AnnotationReadingVisitorUtils {
return null;
}
// To start with, we populate the results with a copy of all attribute
// values from the target annotation. A copy is necessary so that we do
// not inadvertently mutate the state of the metadata passed to this
// method.
AnnotationAttributes results = new AnnotationAttributes(attributesList.get(0));
// To start with, we populate the result with a copy of all attribute values
// from the target annotation. A copy is necessary so that we do not
// inadvertently mutate the state of the metadata passed to this method.
AnnotationAttributes result = new AnnotationAttributes(attributesList.get(0));
Set<String> overridableAttributeNames = new HashSet<>(results.keySet());
Set<String> overridableAttributeNames = new HashSet<>(result.keySet());
overridableAttributeNames.remove(AnnotationUtils.VALUE);
// Since the map is a LinkedMultiValueMap, we depend on the ordering of
@ -152,14 +157,14 @@ abstract class AnnotationReadingVisitorUtils {
if (value != null) {
// Store the value, potentially overriding a value from an attribute
// of the same name found higher in the annotation hierarchy.
results.put(overridableAttributeName, value);
result.put(overridableAttributeName, value);
}
}
}
}
}
return results;
return result;
}
}

View File

@ -122,7 +122,8 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho
public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
this.attributesMap, this.metaAnnotationMap, annotationName);
return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
return AnnotationReadingVisitorUtils.convertClassValues(
"method '" + getMethodName() + "'", this.classLoader, raw, classValuesAsString);
}
@Override
@ -137,8 +138,9 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho
}
MultiValueMap<String, Object> allAttributes = new LinkedMultiValueMap<>();
for (AnnotationAttributes annotationAttributes : this.attributesMap.get(annotationName)) {
for (Map.Entry<String, Object> entry : AnnotationReadingVisitorUtils.convertClassValues(
this.classLoader, annotationAttributes, classValuesAsString).entrySet()) {
AnnotationAttributes convertedAttributes = AnnotationReadingVisitorUtils.convertClassValues(
"method '" + getMethodName() + "'", this.classLoader, annotationAttributes, classValuesAsString);
for (Map.Entry<String, Object> entry : convertedAttributes.entrySet()) {
allAttributes.add(entry.getKey(), entry.getValue());
}
}

View File

@ -69,7 +69,7 @@ class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor
@Override
public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
String annotationType = Type.getType(asmTypeDescriptor).getClassName();
AnnotationAttributes nestedAttributes = new AnnotationAttributes();
AnnotationAttributes nestedAttributes = new AnnotationAttributes(annotationType, this.classLoader);
this.allNestedAttributes.add(nestedAttributes);
return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
}

View File

@ -16,10 +16,6 @@
package org.springframework.core.type.classreading;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
@ -30,7 +26,7 @@ import org.springframework.core.annotation.AnnotationUtils;
*/
class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor {
private final String annotationType;
protected final String annotationType;
public RecursiveAnnotationAttributesVisitor(
@ -42,49 +38,8 @@ class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVi
@Override
public final void visitEnd() {
try {
Class<?> annotationClass = this.classLoader.loadClass(this.annotationType);
doVisitEnd(annotationClass);
}
catch (ClassNotFoundException ex) {
logger.debug("Failed to class-load type while reading annotation metadata. " +
"This is a non-fatal error, but certain annotation metadata may be unavailable.", ex);
}
}
protected void doVisitEnd(Class<?> annotationClass) {
registerDefaultValues(annotationClass);
}
private void registerDefaultValues(Class<?> annotationClass) {
// Only do defaults scanning for public annotations; we'd run into
// IllegalAccessExceptions otherwise, and we don't want to mess with
// accessibility in a SecurityManager environment.
if (Modifier.isPublic(annotationClass.getModifiers())) {
// Check declared default values of attributes in the annotation type.
Method[] annotationAttributes = annotationClass.getMethods();
for (Method annotationAttribute : annotationAttributes) {
String attributeName = annotationAttribute.getName();
Object defaultValue = annotationAttribute.getDefaultValue();
if (defaultValue != null && !this.attributes.containsKey(attributeName)) {
if (defaultValue instanceof Annotation) {
defaultValue = AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes(
(Annotation) defaultValue, false, true));
}
else if (defaultValue instanceof Annotation[]) {
Annotation[] realAnnotations = (Annotation[]) defaultValue;
AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
for (int i = 0; i < realAnnotations.length; i++) {
mappedAnnotations[i] = AnnotationAttributes.fromMap(
AnnotationUtils.getAnnotationAttributes(realAnnotations[i], false, true));
}
defaultValue = mappedAnnotations;
}
this.attributes.put(attributeName, defaultValue);
}
}
}
public void visitEnd() {
AnnotationUtils.registerDefaultValues(this.attributes);
}
}

View File

@ -18,16 +18,11 @@ package org.springframework.core.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.annotation.AnnotationUtilsTests.ContextConfig;
import org.springframework.core.annotation.AnnotationUtilsTests.ImplicitAliasesContextConfig;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
@ -133,21 +128,21 @@ public class AnnotationAttributesTests {
@Test
public void getEnumWithNullAttributeName() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(containsString("attributeName must not be null or empty"));
exception.expectMessage("must not be null or empty");
attributes.getEnum(null);
}
@Test
public void getEnumWithEmptyAttributeName() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(containsString("attributeName must not be null or empty"));
exception.expectMessage("must not be null or empty");
attributes.getEnum("");
}
@Test
public void getEnumWithUnknownAttributeName() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(containsString("Attribute 'bogus' not found"));
exception.expectMessage("Attribute 'bogus' not found");
attributes.getEnum("bogus");
}
@ -159,337 +154,6 @@ public class AnnotationAttributesTests {
attributes.getEnum("color");
}
@Test
public void getAliasedString() {
final String value = "metaverse";
attributes.clear();
attributes.put("name", value);
assertEquals(value, getAliasedString("name"));
assertEquals(value, getAliasedString("value"));
attributes.clear();
attributes.put("value", value);
assertEquals(value, getAliasedString("name"));
assertEquals(value, getAliasedString("value"));
attributes.clear();
attributes.put("name", value);
attributes.put("value", value);
assertEquals(value, getAliasedString("name"));
assertEquals(value, getAliasedString("value"));
}
@Test
public void getAliasedStringWithImplicitAliases() {
final String value = "metaverse";
final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
attributes.put("value", value);
aliases.stream().forEach(alias -> assertEquals(value, getAliasedStringWithImplicitAliases(alias)));
attributes.clear();
attributes.put("location1", value);
aliases.stream().forEach(alias -> assertEquals(value, getAliasedStringWithImplicitAliases(alias)));
attributes.clear();
attributes.put("value", value);
attributes.put("location1", value);
attributes.put("xmlFile", value);
attributes.put("groovyScript", value);
aliases.stream().forEach(alias -> assertEquals(value, getAliasedStringWithImplicitAliases(alias)));
}
@Test
public void getAliasedStringWithImplicitAliasesWithMissingAliasedAttributes() {
final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Neither attribute 'value' nor one of its aliases ["));
aliases.stream().forEach(alias -> exception.expectMessage(containsString(alias)));
exception.expectMessage(endsWith("] was found in attributes for annotation [" + ImplicitAliasesContextConfig.class.getName() + "]"));
getAliasedStringWithImplicitAliases("value");
}
@Test
public void getAliasedStringFromSynthesizedAnnotationAttributes() {
Scope scope = ScopedComponent.class.getAnnotation(Scope.class);
AnnotationAttributes scopeAttributes = AnnotationUtils.getAnnotationAttributes(ScopedComponent.class, scope);
assertEquals("custom", getAliasedString(scopeAttributes, "name"));
assertEquals("custom", getAliasedString(scopeAttributes, "value"));
}
@Test
public void getAliasedStringWithMissingAliasedAttributes() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(equalTo("Neither attribute 'name' nor one of its aliases [value] was found in attributes for annotation [unknown]"));
getAliasedString("name");
}
@Test
public void getAliasedStringWithDifferentAliasedValues() {
attributes.put("name", "request");
attributes.put("value", "session");
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(containsString("In annotation [" + Scope.class.getName() + "]"));
exception.expectMessage(containsString("attribute [name] and its alias [value]"));
exception.expectMessage(containsString("[request] and [session]"));
exception.expectMessage(containsString("but only one is permitted"));
getAliasedString("name");
}
private String getAliasedString(String attributeName) {
return getAliasedString(this.attributes, attributeName);
}
private String getAliasedString(AnnotationAttributes attrs, String attributeName) {
return attrs.getAliasedString(attributeName, Scope.class, null);
}
private String getAliasedStringWithImplicitAliases(String attributeName) {
return this.attributes.getAliasedString(attributeName, ImplicitAliasesContextConfig.class, null);
}
@Test
public void getAliasedStringArray() {
final String[] INPUT = new String[] {"test.xml"};
final String[] EMPTY = new String[0];
attributes.clear();
attributes.put("location", INPUT);
assertArrayEquals(INPUT, getAliasedStringArray("location"));
assertArrayEquals(INPUT, getAliasedStringArray("value"));
attributes.clear();
attributes.put("value", INPUT);
assertArrayEquals(INPUT, getAliasedStringArray("location"));
assertArrayEquals(INPUT, getAliasedStringArray("value"));
attributes.clear();
attributes.put("location", INPUT);
attributes.put("value", INPUT);
assertArrayEquals(INPUT, getAliasedStringArray("location"));
assertArrayEquals(INPUT, getAliasedStringArray("value"));
attributes.clear();
attributes.put("location", INPUT);
attributes.put("value", EMPTY);
assertArrayEquals(INPUT, getAliasedStringArray("location"));
assertArrayEquals(INPUT, getAliasedStringArray("value"));
attributes.clear();
attributes.put("location", EMPTY);
attributes.put("value", INPUT);
assertArrayEquals(INPUT, getAliasedStringArray("location"));
assertArrayEquals(INPUT, getAliasedStringArray("value"));
attributes.clear();
attributes.put("location", EMPTY);
attributes.put("value", EMPTY);
assertArrayEquals(EMPTY, getAliasedStringArray("location"));
assertArrayEquals(EMPTY, getAliasedStringArray("value"));
}
@Test
public void getAliasedStringArrayWithImplicitAliases() {
final String[] INPUT = new String[] {"test.xml"};
final String[] EMPTY = new String[0];
final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
attributes.put("location1", INPUT);
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
attributes.clear();
attributes.put("value", INPUT);
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
attributes.clear();
attributes.put("location1", INPUT);
attributes.put("value", INPUT);
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
attributes.clear();
attributes.put("location1", INPUT);
attributes.put("value", EMPTY);
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
attributes.clear();
attributes.put("location1", EMPTY);
attributes.put("value", INPUT);
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
attributes.clear();
attributes.put("location1", EMPTY);
attributes.put("value", EMPTY);
aliases.stream().forEach(alias -> assertArrayEquals(EMPTY, getAliasedStringArrayWithImplicitAliases(alias)));
}
@Test
public void getAliasedStringArrayWithImplicitAliasesWithMissingAliasedAttributes() {
final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Neither attribute 'value' nor one of its aliases ["));
aliases.stream().forEach(alias -> exception.expectMessage(containsString(alias)));
exception.expectMessage(endsWith("] was found in attributes for annotation [" + ImplicitAliasesContextConfig.class.getName() + "]"));
getAliasedStringArrayWithImplicitAliases("value");
}
@Test
public void getAliasedStringArrayWithMissingAliasedAttributes() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(equalTo("Neither attribute 'location' nor one of its aliases [value] was found in attributes for annotation [unknown]"));
getAliasedStringArray("location");
}
@Test
public void getAliasedStringArrayWithDifferentAliasedValues() {
attributes.put("location", new String[] {"1.xml"});
attributes.put("value", new String[] {"2.xml"});
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(containsString("In annotation [" + ContextConfig.class.getName() + "]"));
exception.expectMessage(containsString("attribute [location] and its alias [value]"));
exception.expectMessage(containsString("[{1.xml}] and [{2.xml}]"));
exception.expectMessage(containsString("but only one is permitted"));
getAliasedStringArray("location");
}
private String[] getAliasedStringArray(String attributeName) {
// Note: even though the attributes we test against here are of type
// String instead of String[], it doesn't matter... since
// AnnotationAttributes does not validate the actual return type of
// attributes in the annotation.
return attributes.getAliasedStringArray(attributeName, ContextConfig.class, null);
}
private String[] getAliasedStringArrayWithImplicitAliases(String attributeName) {
// Note: even though the attributes we test against here are of type
// String instead of String[], it doesn't matter... since
// AnnotationAttributes does not validate the actual return type of
// attributes in the annotation.
return this.attributes.getAliasedStringArray(attributeName, ImplicitAliasesContextConfig.class, null);
}
@Test
public void getAliasedClassArray() {
final Class<?>[] INPUT = new Class<?>[] {String.class};
final Class<?>[] EMPTY = new Class<?>[0];
attributes.clear();
attributes.put("classes", INPUT);
assertArrayEquals(INPUT, getAliasedClassArray("classes"));
assertArrayEquals(INPUT, getAliasedClassArray("value"));
attributes.clear();
attributes.put("value", INPUT);
assertArrayEquals(INPUT, getAliasedClassArray("classes"));
assertArrayEquals(INPUT, getAliasedClassArray("value"));
attributes.clear();
attributes.put("classes", INPUT);
attributes.put("value", INPUT);
assertArrayEquals(INPUT, getAliasedClassArray("classes"));
assertArrayEquals(INPUT, getAliasedClassArray("value"));
attributes.clear();
attributes.put("classes", INPUT);
attributes.put("value", EMPTY);
assertArrayEquals(INPUT, getAliasedClassArray("classes"));
assertArrayEquals(INPUT, getAliasedClassArray("value"));
attributes.clear();
attributes.put("classes", EMPTY);
attributes.put("value", INPUT);
assertArrayEquals(INPUT, getAliasedClassArray("classes"));
assertArrayEquals(INPUT, getAliasedClassArray("value"));
attributes.clear();
attributes.put("classes", EMPTY);
attributes.put("value", EMPTY);
assertArrayEquals(EMPTY, getAliasedClassArray("classes"));
assertArrayEquals(EMPTY, getAliasedClassArray("value"));
}
@Test
public void getAliasedClassArrayWithImplicitAliases() {
final Class<?>[] INPUT = new Class<?>[] {String.class};
final Class<?>[] EMPTY = new Class<?>[0];
final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
attributes.put("location1", INPUT);
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
attributes.clear();
attributes.put("value", INPUT);
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
attributes.clear();
attributes.put("location1", INPUT);
attributes.put("value", INPUT);
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
attributes.clear();
attributes.put("location1", INPUT);
attributes.put("value", EMPTY);
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
attributes.clear();
attributes.put("location1", EMPTY);
attributes.put("value", INPUT);
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
attributes.clear();
attributes.put("location1", EMPTY);
attributes.put("value", EMPTY);
aliases.stream().forEach(alias -> assertArrayEquals(EMPTY, getAliasedClassArrayWithImplicitAliases(alias)));
}
@Test
public void getAliasedClassArrayWithMissingAliasedAttributes() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(equalTo("Neither attribute 'classes' nor one of its aliases [value] was found in attributes for annotation [unknown]"));
getAliasedClassArray("classes");
}
@Test
public void getAliasedClassArrayWithDifferentAliasedValues() {
attributes.put("classes", new Class<?>[] {String.class});
attributes.put("value", new Class<?>[] {Number.class});
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(containsString("In annotation [" + Filter.class.getName() + "]"));
exception.expectMessage(containsString("attribute [classes] and its alias [value]"));
exception.expectMessage(containsString("[{class java.lang.String}] and [{class java.lang.Number}]"));
exception.expectMessage(containsString("but only one is permitted"));
getAliasedClassArray("classes");
}
private Class<?>[] getAliasedClassArray(String attributeName) {
return attributes.getAliasedClassArray(attributeName, Filter.class, null);
}
private Class<?>[] getAliasedClassArrayWithImplicitAliases(String attributeName) {
// Note: even though the attributes we test against here are of type
// String instead of Class<?>[], it doesn't matter... since
// AnnotationAttributes does not validate the actual return type of
// attributes in the annotation.
return this.attributes.getAliasedClassArray(attributeName, ImplicitAliasesContextConfig.class, null);
}
enum Color {
@ -514,23 +178,4 @@ public class AnnotationAttributesTests {
static class FilteredClass {
}
/**
* Mock of {@code org.springframework.context.annotation.Scope}.
*/
@Retention(RetentionPolicy.RUNTIME)
@interface Scope {
@AliasFor(attribute = "name")
String value() default "singleton";
@AliasFor(attribute = "value")
String name() default "singleton";
}
@Scope(name = "custom")
static class ScopedComponent {
}
}

View File

@ -30,6 +30,7 @@ import java.util.Set;
import org.junit.Test;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
@ -291,6 +292,7 @@ public class AnnotationMetadataTests {
Set<MethodMetadata> methods = metadata.getAnnotatedMethods(DirectAnnotation.class.getName());
MethodMetadata method = methods.iterator().next();
assertEquals("direct", method.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"));
assertEquals("direct", method.getAnnotationAttributes(DirectAnnotation.class.getName()).get("myValue"));
List<Object> allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value");
assertThat(new HashSet<>(allMeta), is(equalTo(new HashSet<Object>(Arrays.asList("direct", "meta")))));
allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("additional");
@ -416,7 +418,11 @@ public class AnnotationMetadataTests {
@Retention(RetentionPolicy.RUNTIME)
public @interface DirectAnnotation {
String value();
@AliasFor("myValue")
String value() default "";
@AliasFor("value")
String myValue() default "";
String additional() default "direct";
}
@ -449,7 +455,7 @@ public class AnnotationMetadataTests {
}
// SPR-10914
public static enum SubclassEnum {
public enum SubclassEnum {
FOO {
/* Do not delete! This subclassing is intentional. */
},
@ -489,14 +495,14 @@ public class AnnotationMetadataTests {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public static @interface TestConfiguration {
public @interface TestConfiguration {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface TestComponentScan {
public @interface TestComponentScan {
String[] value() default {};
@ -509,7 +515,7 @@ public class AnnotationMetadataTests {
@TestComponentScan(basePackages = "bogus")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface ComposedConfigurationWithAttributeOverrides {
public @interface ComposedConfigurationWithAttributeOverrides {
String[] basePackages() default {};
}
@ -520,19 +526,19 @@ public class AnnotationMetadataTests {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface NamedAnnotation1 {
public @interface NamedAnnotation1 {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface NamedAnnotation2 {
public @interface NamedAnnotation2 {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface NamedAnnotation3 {
public @interface NamedAnnotation3 {
String name() default "";
}
@ -547,7 +553,7 @@ public class AnnotationMetadataTests {
@NamedAnnotation3(name = "name 3")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface NamedComposedAnnotation {
public @interface NamedComposedAnnotation {
}
@NamedComposedAnnotation