Consistent alias processing behind AnnotatedTypeMetadata abstraction (also for ASM)
Issue: SPR-14427
This commit is contained in:
parent
b1663585dc
commit
3d3407c789
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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();
|
ScopeMetadata metadata = new ScopeMetadata();
|
||||||
if (definition instanceof AnnotatedBeanDefinition) {
|
if (definition instanceof AnnotatedBeanDefinition) {
|
||||||
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
|
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
|
||||||
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(annDef.getMetadata(), this.scopeAnnotationType);
|
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
|
||||||
|
annDef.getMetadata(), this.scopeAnnotationType);
|
||||||
if (attributes != null) {
|
if (attributes != null) {
|
||||||
metadata.setScopeName(attributes.getAliasedString("value", this.scopeAnnotationType, definition.getSource()));
|
metadata.setScopeName(attributes.getString("value"));
|
||||||
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
|
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
|
||||||
if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
|
if (proxyMode == null || proxyMode == ScopedProxyMode.DEFAULT) {
|
||||||
proxyMode = this.defaultProxyMode;
|
proxyMode = this.defaultProxyMode;
|
||||||
|
|
|
@ -122,7 +122,7 @@ class ComponentScanAnnotationParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> basePackages = new LinkedHashSet<>();
|
Set<String> basePackages = new LinkedHashSet<>();
|
||||||
String[] basePackagesArray = componentScan.getAliasedStringArray("basePackages", ComponentScan.class, declaringClass);
|
String[] basePackagesArray = componentScan.getStringArray("basePackages");
|
||||||
for (String pkg : basePackagesArray) {
|
for (String pkg : basePackagesArray) {
|
||||||
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
|
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
|
||||||
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
|
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
|
||||||
|
@ -148,7 +148,7 @@ class ComponentScanAnnotationParser {
|
||||||
List<TypeFilter> typeFilters = new ArrayList<>();
|
List<TypeFilter> typeFilters = new ArrayList<>();
|
||||||
FilterType filterType = filterAttributes.getEnum("type");
|
FilterType filterType = filterAttributes.getEnum("type");
|
||||||
|
|
||||||
for (Class<?> filterClass : filterAttributes.getAliasedClassArray("classes", ComponentScan.Filter.class, null)) {
|
for (Class<?> filterClass : filterAttributes.getClassArray("classes")) {
|
||||||
switch (filterType) {
|
switch (filterType) {
|
||||||
case ANNOTATION:
|
case ANNOTATION:
|
||||||
Assert.isAssignable(Annotation.class, filterClass,
|
Assert.isAssignable(Annotation.class, filterClass,
|
||||||
|
|
|
@ -236,7 +236,7 @@ class ConfigurationClassBeanDefinitionReader {
|
||||||
ScopedProxyMode proxyMode = ScopedProxyMode.NO;
|
ScopedProxyMode proxyMode = ScopedProxyMode.NO;
|
||||||
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
|
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
|
||||||
if (attributes != null) {
|
if (attributes != null) {
|
||||||
beanDef.setScope(attributes.getAliasedString("value", Scope.class, configClass.getResource()));
|
beanDef.setScope(attributes.getString("value"));
|
||||||
proxyMode = attributes.getEnum("proxyMode");
|
proxyMode = attributes.getEnum("proxyMode");
|
||||||
if (proxyMode == ScopedProxyMode.DEFAULT) {
|
if (proxyMode == ScopedProxyMode.DEFAULT) {
|
||||||
proxyMode = ScopedProxyMode.NO;
|
proxyMode = ScopedProxyMode.NO;
|
||||||
|
|
|
@ -287,8 +287,9 @@ class ConfigurationClassParser {
|
||||||
|
|
||||||
// Process any @ImportResource annotations
|
// Process any @ImportResource annotations
|
||||||
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
|
if (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
|
||||||
AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
|
AnnotationAttributes importResource =
|
||||||
String[] resources = importResource.getAliasedStringArray("locations", ImportResource.class, sourceClass);
|
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
|
||||||
|
String[] resources = importResource.getStringArray("locations");
|
||||||
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
|
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
|
||||||
for (String resource : resources) {
|
for (String resource : resources) {
|
||||||
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
|
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
|
||||||
|
|
|
@ -955,7 +955,7 @@ public class ConfigurationClassPostProcessorTests {
|
||||||
@ComponentScan(basePackages = "org.springframework.context.annotation.componentscan.simple")
|
@ComponentScan(basePackages = "org.springframework.context.annotation.componentscan.simple")
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public static @interface ComposedConfiguration {
|
public @interface ComposedConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ComposedConfiguration
|
@ComposedConfiguration
|
||||||
|
@ -966,7 +966,7 @@ public class ConfigurationClassPostProcessorTests {
|
||||||
@ComponentScan
|
@ComponentScan
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public static @interface ComposedConfigurationWithAttributeOverrides {
|
public @interface ComposedConfigurationWithAttributeOverrides {
|
||||||
|
|
||||||
String[] basePackages() default {};
|
String[] basePackages() default {};
|
||||||
|
|
||||||
|
@ -985,7 +985,7 @@ public class ConfigurationClassPostProcessorTests {
|
||||||
@ComposedConfigurationWithAttributeOverrides
|
@ComposedConfigurationWithAttributeOverrides
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public static @interface ComposedComposedConfigurationWithAttributeOverrides {
|
public @interface ComposedComposedConfigurationWithAttributeOverrides {
|
||||||
|
|
||||||
String[] basePackages() default {};
|
String[] basePackages() default {};
|
||||||
}
|
}
|
||||||
|
@ -997,14 +997,14 @@ public class ConfigurationClassPostProcessorTests {
|
||||||
@ComponentScan
|
@ComponentScan
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public static @interface MetaComponentScan {
|
public @interface MetaComponentScan {
|
||||||
}
|
}
|
||||||
|
|
||||||
@MetaComponentScan
|
@MetaComponentScan
|
||||||
@Configuration
|
@Configuration
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public static @interface MetaComponentScanConfigurationWithAttributeOverrides {
|
public @interface MetaComponentScanConfigurationWithAttributeOverrides {
|
||||||
|
|
||||||
String[] basePackages() default {};
|
String[] basePackages() default {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* @param <S> the type of source supported by this extractor
|
||||||
* @see Annotation
|
* @see Annotation
|
||||||
* @see AliasFor
|
* @see AliasFor
|
||||||
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
|
* @see AnnotationUtils#synthesizeAnnotation(Annotation, Object)
|
||||||
*/
|
*/
|
||||||
abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements AnnotationAttributeExtractor<S> {
|
abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements AnnotationAttributeExtractor<S> {
|
||||||
|
|
||||||
private final Class<? extends Annotation> annotationType;
|
private final Class<? extends Annotation> annotationType;
|
||||||
|
|
||||||
private final AnnotatedElement annotatedElement;
|
private final Object annotatedElement;
|
||||||
|
|
||||||
private final S source;
|
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}
|
* @param source the underlying source of annotation attributes; never {@code null}
|
||||||
*/
|
*/
|
||||||
AbstractAliasAwareAnnotationAttributeExtractor(
|
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(annotationType, "annotationType must not be null");
|
||||||
Assert.notNull(source, "source must not be null");
|
Assert.notNull(source, "source must not be null");
|
||||||
|
@ -73,7 +73,7 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final AnnotatedElement getAnnotatedElement() {
|
public final Object getAnnotatedElement() {
|
||||||
return this.annotatedElement;
|
return this.annotatedElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,18 +89,18 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
|
||||||
|
|
||||||
List<String> aliasNames = this.attributeAliasMap.get(attributeName);
|
List<String> aliasNames = this.attributeAliasMap.get(attributeName);
|
||||||
if (aliasNames != null) {
|
if (aliasNames != null) {
|
||||||
Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName);
|
Object defaultValue = AnnotationUtils.getDefaultValue(this.annotationType, attributeName);
|
||||||
for (String aliasName : aliasNames) {
|
for (String aliasName : aliasNames) {
|
||||||
Object aliasValue = getRawAttributeValue(aliasName);
|
Object aliasValue = getRawAttributeValue(aliasName);
|
||||||
|
|
||||||
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
|
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
|
||||||
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
|
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
|
||||||
!ObjectUtils.nullSafeEquals(aliasValue, 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(
|
throw new AnnotationConfigurationException(String.format(
|
||||||
"In annotation [%s] declared on %s and synthesized from [%s], attribute '%s' and its " +
|
"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.",
|
"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)));
|
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,7 +17,6 @@
|
||||||
package org.springframework.core.annotation;
|
package org.springframework.core.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,7 +43,7 @@ interface AnnotationAttributeExtractor<S> {
|
||||||
* type supported by this extractor.
|
* type supported by this extractor.
|
||||||
* @return the annotated element, or {@code null} if unknown
|
* @return the annotated element, or {@code null} if unknown
|
||||||
*/
|
*/
|
||||||
AnnotatedElement getAnnotatedElement();
|
Object getAnnotatedElement();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the underlying source of annotation attributes.
|
* Get the underlying source of annotation attributes.
|
||||||
|
|
|
@ -50,30 +50,21 @@ import org.springframework.util.StringUtils;
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
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;
|
private final String displayName;
|
||||||
|
|
||||||
|
boolean validated = false;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new, empty {@link AnnotationAttributes} instance.
|
* Create a new, empty {@link AnnotationAttributes} instance.
|
||||||
*/
|
*/
|
||||||
public AnnotationAttributes() {
|
public AnnotationAttributes() {
|
||||||
this.annotationType = null;
|
this.annotationType = null;
|
||||||
this.displayName = "unknown";
|
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,20 +75,68 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
public AnnotationAttributes(int initialCapacity) {
|
public AnnotationAttributes(int initialCapacity) {
|
||||||
super(initialCapacity);
|
super(initialCapacity);
|
||||||
this.annotationType = null;
|
this.annotationType = null;
|
||||||
this.displayName = "unknown";
|
this.displayName = UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link AnnotationAttributes} instance, wrapping the
|
* Create a new, empty {@link AnnotationAttributes} instance for the
|
||||||
* provided map and all its <em>key-value</em> pairs.
|
* specified {@code annotationType}.
|
||||||
* @param map original source of annotation attribute <em>key-value</em>
|
* @param annotationType the type of annotation represented by this
|
||||||
* pairs
|
* {@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)
|
* @see #fromMap(Map)
|
||||||
*/
|
*/
|
||||||
public AnnotationAttributes(Map<String, Object> map) {
|
public AnnotationAttributes(Map<String, Object> map) {
|
||||||
super(map);
|
super(map);
|
||||||
this.annotationType = null;
|
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);
|
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
|
* Get the value stored under the specified {@code attributeName} as an
|
||||||
* array of strings.
|
* array of strings.
|
||||||
|
@ -168,33 +179,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
return getRequiredAttribute(attributeName, String[].class);
|
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
|
* Get the value stored under the specified {@code attributeName} as a
|
||||||
* boolean.
|
* boolean.
|
||||||
|
@ -266,33 +250,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
return getRequiredAttribute(attributeName, Class[].class);
|
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
|
* Get the {@link AnnotationAttributes} stored under the specified
|
||||||
* {@code attributeName}.
|
* {@code attributeName}.
|
||||||
|
@ -378,7 +335,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private <T> T getRequiredAttribute(String attributeName, Class<T> expectedType) {
|
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);
|
Object value = get(attributeName);
|
||||||
assertAttributePresence(attributeName, value);
|
assertAttributePresence(attributeName, value);
|
||||||
assertNotException(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,
|
private <T> T getRequiredAttributeWithAlias(String attributeName, Class<? extends Annotation> annotationType,
|
||||||
Object annotationSource, Class<T> expectedType) {
|
Object annotationSource, Class<T> expectedType) {
|
||||||
|
|
||||||
Assert.hasText(attributeName, "attributeName must not be null or empty");
|
Assert.hasText(attributeName, "'attributeName' must not be null or empty");
|
||||||
Assert.notNull(annotationType, "annotationType must not be null");
|
Assert.notNull(annotationType, "'annotationType' must not be null");
|
||||||
Assert.notNull(expectedType, "expectedType must not be null");
|
Assert.notNull(expectedType, "'expectedType' must not be null");
|
||||||
|
|
||||||
T attributeValue = getAttribute(attributeName, expectedType);
|
T attributeValue = getAttribute(attributeName, expectedType);
|
||||||
|
|
||||||
|
@ -433,8 +390,8 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
|
|
||||||
if (!attributeEmpty && !aliasEmpty && !ObjectUtils.nullSafeEquals(attributeValue, aliasValue)) {
|
if (!attributeEmpty && !aliasEmpty && !ObjectUtils.nullSafeEquals(attributeValue, aliasValue)) {
|
||||||
String elementName = (annotationSource == null ? "unknown element" : annotationSource.toString());
|
String elementName = (annotationSource == null ? "unknown element" : annotationSource.toString());
|
||||||
String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] " +
|
String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its " +
|
||||||
"are present with values of [%s] and [%s], but only one is permitted.",
|
"alias [%s] are present with values of [%s] and [%s], but only one is permitted.",
|
||||||
annotationType.getName(), elementName, attributeName, aliasName,
|
annotationType.getName(), elementName, attributeName, aliasName,
|
||||||
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
|
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
|
||||||
throw new AnnotationConfigurationException(msg);
|
throw new AnnotationConfigurationException(msg);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.lang.reflect.Array;
|
||||||
import java.lang.reflect.InvocationHandler;
|
import java.lang.reflect.InvocationHandler;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -590,7 +591,7 @@ public abstract class AnnotationUtils {
|
||||||
static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
|
static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
|
||||||
Boolean found = annotatedInterfaceCache.get(iface);
|
Boolean found = annotatedInterfaceCache.get(iface);
|
||||||
if (found != null) {
|
if (found != null) {
|
||||||
return found.booleanValue();
|
return found;
|
||||||
}
|
}
|
||||||
found = Boolean.FALSE;
|
found = Boolean.FALSE;
|
||||||
for (Method ifcMethod : iface.getMethods()) {
|
for (Method ifcMethod : iface.getMethods()) {
|
||||||
|
@ -605,7 +606,7 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
annotatedInterfaceCache.put(iface, found);
|
annotatedInterfaceCache.put(iface, found);
|
||||||
return found.booleanValue();
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -857,14 +858,14 @@ public abstract class AnnotationUtils {
|
||||||
AnnotationCacheKey cacheKey = new AnnotationCacheKey(annotationType, metaAnnotationType);
|
AnnotationCacheKey cacheKey = new AnnotationCacheKey(annotationType, metaAnnotationType);
|
||||||
Boolean metaPresent = metaPresentCache.get(cacheKey);
|
Boolean metaPresent = metaPresentCache.get(cacheKey);
|
||||||
if (metaPresent != null) {
|
if (metaPresent != null) {
|
||||||
return metaPresent.booleanValue();
|
return metaPresent;
|
||||||
}
|
}
|
||||||
metaPresent = Boolean.FALSE;
|
metaPresent = Boolean.FALSE;
|
||||||
if (findAnnotation(annotationType, metaAnnotationType, false) != null) {
|
if (findAnnotation(annotationType, metaAnnotationType, false) != null) {
|
||||||
metaPresent = Boolean.TRUE;
|
metaPresent = Boolean.TRUE;
|
||||||
}
|
}
|
||||||
metaPresentCache.put(cacheKey, metaPresent);
|
metaPresentCache.put(cacheKey, metaPresent);
|
||||||
return metaPresent.booleanValue();
|
return metaPresent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -987,6 +988,13 @@ public abstract class AnnotationUtils {
|
||||||
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
|
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
|
||||||
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
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 =
|
AnnotationAttributes attributes =
|
||||||
retrieveAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap);
|
retrieveAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap);
|
||||||
postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, nestedAnnotationsAsMap);
|
postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, nestedAnnotationsAsMap);
|
||||||
|
@ -1021,7 +1029,7 @@ public abstract class AnnotationUtils {
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
* @see #postProcessAnnotationAttributes
|
* @see #postProcessAnnotationAttributes
|
||||||
*/
|
*/
|
||||||
static AnnotationAttributes retrieveAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation,
|
static AnnotationAttributes retrieveAnnotationAttributes(Object annotatedElement, Annotation annotation,
|
||||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||||
|
|
||||||
Class<? extends Annotation> annotationType = annotation.annotationType();
|
Class<? extends Annotation> annotationType = annotation.annotationType();
|
||||||
|
@ -1065,14 +1073,14 @@ public abstract class AnnotationUtils {
|
||||||
* {@code Annotation} instances
|
* {@code Annotation} instances
|
||||||
* @return the adapted value, or the original value if no adaptation is needed
|
* @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) {
|
boolean nestedAnnotationsAsMap) {
|
||||||
|
|
||||||
if (classValuesAsString) {
|
if (classValuesAsString) {
|
||||||
if (value instanceof Class) {
|
if (value instanceof Class<?>) {
|
||||||
return ((Class<?>) value).getName();
|
return ((Class<?>) value).getName();
|
||||||
}
|
}
|
||||||
else if (value instanceof Class[]) {
|
else if (value instanceof Class<?>[]) {
|
||||||
Class<?>[] clazzArray = (Class<?>[]) value;
|
Class<?>[] clazzArray = (Class<?>[]) value;
|
||||||
String[] classNames = new String[clazzArray.length];
|
String[] classNames = new String[clazzArray.length];
|
||||||
for (int i = 0; i < clazzArray.length; i++) {
|
for (int i = 0; i < clazzArray.length; i++) {
|
||||||
|
@ -1111,6 +1119,64 @@ public abstract class AnnotationUtils {
|
||||||
return value;
|
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}.
|
* Post-process the supplied {@link AnnotationAttributes}.
|
||||||
* <p>Specifically, this method enforces <em>attribute alias</em> semantics
|
* <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
|
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
|
||||||
* {@code Annotation} instances
|
* {@code Annotation} instances
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
* @see #retrieveAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
|
* @see #retrieveAnnotationAttributes(Object, Annotation, boolean, boolean)
|
||||||
* @see #getDefaultValue(Class, String)
|
* @see #getDefaultValue(Class, String)
|
||||||
*/
|
*/
|
||||||
static void postProcessAnnotationAttributes(AnnotatedElement annotatedElement,
|
static void postProcessAnnotationAttributes(Object annotatedElement,
|
||||||
AnnotationAttributes attributes, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
AnnotationAttributes attributes, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||||
|
|
||||||
// Abort?
|
// Abort?
|
||||||
|
@ -1145,51 +1211,55 @@ public abstract class AnnotationUtils {
|
||||||
// circuit the search algorithms.
|
// circuit the search algorithms.
|
||||||
Set<String> valuesAlreadyReplaced = new HashSet<>();
|
Set<String> valuesAlreadyReplaced = new HashSet<>();
|
||||||
|
|
||||||
// Validate @AliasFor configuration
|
if (!attributes.validated) {
|
||||||
Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
|
// Validate @AliasFor configuration
|
||||||
for (String attributeName : aliasMap.keySet()) {
|
Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
|
||||||
if (valuesAlreadyReplaced.contains(attributeName)) {
|
for (String attributeName : aliasMap.keySet()) {
|
||||||
continue;
|
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||||
}
|
|
||||||
Object value = attributes.get(attributeName);
|
|
||||||
boolean valuePresent = (value != null && !(value instanceof DefaultValueHolder));
|
|
||||||
|
|
||||||
for (String aliasedAttributeName : aliasMap.get(attributeName)) {
|
|
||||||
if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
Object value = attributes.get(attributeName);
|
||||||
|
boolean valuePresent = (value != null && !(value instanceof DefaultValueHolder));
|
||||||
|
|
||||||
Object aliasedValue = attributes.get(aliasedAttributeName);
|
for (String aliasedAttributeName : aliasMap.get(attributeName)) {
|
||||||
boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder));
|
if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Something to validate or replace with an alias?
|
Object aliasedValue = attributes.get(aliasedAttributeName);
|
||||||
if (valuePresent || aliasPresent) {
|
boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder));
|
||||||
if (valuePresent && aliasPresent) {
|
|
||||||
// Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals().
|
// Something to validate or replace with an alias?
|
||||||
if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) {
|
if (valuePresent || aliasPresent) {
|
||||||
String elementAsString = (annotatedElement != null ? annotatedElement.toString() : "unknown element");
|
if (valuePresent && aliasPresent) {
|
||||||
throw new AnnotationConfigurationException(String.format(
|
// Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals().
|
||||||
"In AnnotationAttributes for annotation [%s] declared on %s, " +
|
if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) {
|
||||||
"attribute '%s' and its alias '%s' are declared with values of [%s] and [%s], " +
|
String elementAsString =
|
||||||
"but only one is permitted.", annotationType.getName(), elementAsString,
|
(annotatedElement != null ? annotatedElement.toString() : "unknown element");
|
||||||
attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value),
|
throw new AnnotationConfigurationException(String.format(
|
||||||
ObjectUtils.nullSafeToString(aliasedValue)));
|
"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
|
// Replace any remaining placeholders with actual default values
|
||||||
|
@ -1329,8 +1399,12 @@ public abstract class AnnotationUtils {
|
||||||
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
|
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
|
||||||
* @see #synthesizeAnnotation(Class)
|
* @see #synthesizeAnnotation(Class)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement) {
|
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) {
|
if (annotation == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1435,7 +1509,7 @@ public abstract class AnnotationUtils {
|
||||||
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
|
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
|
||||||
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
|
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
|
||||||
*/
|
*/
|
||||||
public static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, AnnotatedElement annotatedElement) {
|
static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, Object annotatedElement) {
|
||||||
if (annotations == null) {
|
if (annotations == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1463,7 +1537,7 @@ public abstract class AnnotationUtils {
|
||||||
* {@code @AliasFor} is detected
|
* {@code @AliasFor} is detected
|
||||||
* @since 4.2.1
|
* @since 4.2.1
|
||||||
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
|
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
|
||||||
* @see #synthesizeAnnotationArray(Annotation[], AnnotatedElement)
|
* @see #synthesizeAnnotationArray(Annotation[], Object)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
static <A extends Annotation> A[] synthesizeAnnotationArray(Map<String, Object>[] maps, Class<A> annotationType) {
|
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) {
|
private static boolean isSynthesizable(Class<? extends Annotation> annotationType) {
|
||||||
Boolean synthesizable = synthesizableCache.get(annotationType);
|
Boolean synthesizable = synthesizableCache.get(annotationType);
|
||||||
if (synthesizable != null) {
|
if (synthesizable != null) {
|
||||||
return synthesizable.booleanValue();
|
return synthesizable;
|
||||||
}
|
}
|
||||||
|
|
||||||
synthesizable = Boolean.FALSE;
|
synthesizable = Boolean.FALSE;
|
||||||
|
@ -1579,7 +1653,7 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
synthesizableCache.put(annotationType, synthesizable);
|
synthesizableCache.put(annotationType, synthesizable);
|
||||||
return synthesizable.booleanValue();
|
return synthesizable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package org.springframework.core.annotation;
|
package org.springframework.core.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
import java.lang.annotation.Annotation;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
@ -32,7 +31,7 @@ import org.springframework.util.ReflectionUtils;
|
||||||
* @see AliasFor
|
* @see AliasFor
|
||||||
* @see AbstractAliasAwareAnnotationAttributeExtractor
|
* @see AbstractAliasAwareAnnotationAttributeExtractor
|
||||||
* @see MapAnnotationAttributeExtractor
|
* @see MapAnnotationAttributeExtractor
|
||||||
* @see AnnotationUtils#synthesizeAnnotation(Annotation, AnnotatedElement)
|
* @see AnnotationUtils#synthesizeAnnotation
|
||||||
*/
|
*/
|
||||||
class DefaultAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttributeExtractor<Annotation> {
|
class DefaultAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttributeExtractor<Annotation> {
|
||||||
|
|
||||||
|
@ -42,7 +41,7 @@ class DefaultAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAt
|
||||||
* @param annotatedElement the element that is annotated with the supplied
|
* @param annotatedElement the element that is annotated with the supplied
|
||||||
* annotation; may be {@code null} if unknown
|
* annotation; may be {@code null} if unknown
|
||||||
*/
|
*/
|
||||||
DefaultAnnotationAttributeExtractor(Annotation annotation, AnnotatedElement annotatedElement) {
|
DefaultAnnotationAttributeExtractor(Annotation annotation, Object annotatedElement) {
|
||||||
super(annotation.annotationType(), annotatedElement, annotation);
|
super(annotation.annotationType(), annotatedElement, annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,6 @@ import java.util.Map;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
import static org.springframework.core.annotation.AnnotationUtils.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the {@link AnnotationAttributeExtractor} strategy that
|
* Implementation of the {@link AnnotationAttributeExtractor} strategy that
|
||||||
* is backed by a {@link Map}.
|
* 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> originalAttributes, Class<? extends Annotation> annotationType) {
|
||||||
|
|
||||||
Map<String, Object> attributes = new LinkedHashMap<>(originalAttributes);
|
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();
|
String attributeName = attributeMethod.getName();
|
||||||
Object attributeValue = attributes.get(attributeName);
|
Object attributeValue = attributes.get(attributeName);
|
||||||
|
|
||||||
|
@ -113,7 +111,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
|
||||||
|
|
||||||
// if aliases not present, check default
|
// if aliases not present, check default
|
||||||
if (attributeValue == null) {
|
if (attributeValue == null) {
|
||||||
Object defaultValue = getDefaultValue(annotationType, attributeName);
|
Object defaultValue = AnnotationUtils.getDefaultValue(annotationType, attributeName);
|
||||||
if (defaultValue != null) {
|
if (defaultValue != null) {
|
||||||
attributeValue = defaultValue;
|
attributeValue = defaultValue;
|
||||||
attributes.put(attributeName, attributeValue);
|
attributes.put(attributeName, attributeValue);
|
||||||
|
@ -146,7 +144,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
|
||||||
Class<? extends Annotation> nestedAnnotationType =
|
Class<? extends Annotation> nestedAnnotationType =
|
||||||
(Class<? extends Annotation>) requiredReturnType;
|
(Class<? extends Annotation>) requiredReturnType;
|
||||||
Map<String, Object> map = (Map<String, Object>) attributeValue;
|
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;
|
converted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +155,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
|
||||||
Class<? extends Annotation> nestedAnnotationType =
|
Class<? extends Annotation> nestedAnnotationType =
|
||||||
(Class<? extends Annotation>) requiredReturnType.getComponentType();
|
(Class<? extends Annotation>) requiredReturnType.getComponentType();
|
||||||
Map<String, Object>[] maps = (Map<String, Object>[]) attributeValue;
|
Map<String, Object>[] maps = (Map<String, Object>[]) attributeValue;
|
||||||
attributes.put(attributeName, synthesizeAnnotationArray(maps, nestedAnnotationType));
|
attributes.put(attributeName, AnnotationUtils.synthesizeAnnotationArray(maps, nestedAnnotationType));
|
||||||
converted = true;
|
converted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,11 +27,9 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
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
|
* {@link InvocationHandler} for an {@link Annotation} that Spring has
|
||||||
* <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
|
* <em>synthesized</em> (i.e., wrapped in a dynamic proxy) with additional
|
||||||
|
@ -63,22 +61,21 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||||
if (isEqualsMethod(method)) {
|
if (ReflectionUtils.isEqualsMethod(method)) {
|
||||||
return annotationEquals(args[0]);
|
return annotationEquals(args[0]);
|
||||||
}
|
}
|
||||||
if (isHashCodeMethod(method)) {
|
if (ReflectionUtils.isHashCodeMethod(method)) {
|
||||||
return annotationHashCode();
|
return annotationHashCode();
|
||||||
}
|
}
|
||||||
if (isToStringMethod(method)) {
|
if (ReflectionUtils.isToStringMethod(method)) {
|
||||||
return annotationToString();
|
return annotationToString();
|
||||||
}
|
}
|
||||||
if (isAnnotationTypeMethod(method)) {
|
if (AnnotationUtils.isAnnotationTypeMethod(method)) {
|
||||||
return annotationType();
|
return annotationType();
|
||||||
}
|
}
|
||||||
if (!isAttributeMethod(method)) {
|
if (!AnnotationUtils.isAttributeMethod(method)) {
|
||||||
String msg = String.format("Method [%s] is unsupported for synthesized annotation type [%s]",
|
throw new AnnotationConfigurationException(String.format(
|
||||||
method, annotationType());
|
"Method [%s] is unsupported for synthesized annotation type [%s]", method, annotationType()));
|
||||||
throw new AnnotationConfigurationException(msg);
|
|
||||||
}
|
}
|
||||||
return getAttributeValue(method);
|
return getAttributeValue(method);
|
||||||
}
|
}
|
||||||
|
@ -100,10 +97,10 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
||||||
|
|
||||||
// Synthesize nested annotations before returning them.
|
// Synthesize nested annotations before returning them.
|
||||||
if (value instanceof Annotation) {
|
if (value instanceof Annotation) {
|
||||||
value = synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement());
|
value = AnnotationUtils.synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement());
|
||||||
}
|
}
|
||||||
else if (value instanceof Annotation[]) {
|
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);
|
this.valueCache.put(attributeName, value);
|
||||||
|
@ -164,9 +161,9 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Method attributeMethod : getAttributeMethods(annotationType())) {
|
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType())) {
|
||||||
Object thisValue = getAttributeValue(attributeMethod);
|
Object thisValue = getAttributeValue(attributeMethod);
|
||||||
Object otherValue = invokeMethod(attributeMethod, other);
|
Object otherValue = ReflectionUtils.invokeMethod(attributeMethod, other);
|
||||||
if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
|
if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -181,7 +178,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
||||||
private int annotationHashCode() {
|
private int annotationHashCode() {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
|
|
||||||
for (Method attributeMethod : getAttributeMethods(annotationType())) {
|
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType())) {
|
||||||
Object value = getAttributeValue(attributeMethod);
|
Object value = getAttributeValue(attributeMethod);
|
||||||
int hashCode;
|
int hashCode;
|
||||||
if (value.getClass().isArray()) {
|
if (value.getClass().isArray()) {
|
||||||
|
@ -239,7 +236,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
||||||
private String annotationToString() {
|
private String annotationToString() {
|
||||||
StringBuilder sb = new StringBuilder("@").append(annotationType().getName()).append("(");
|
StringBuilder sb = new StringBuilder("@").append(annotationType().getName()).append("(");
|
||||||
|
|
||||||
Iterator<Method> iterator = getAttributeMethods(annotationType()).iterator();
|
Iterator<Method> iterator = AnnotationUtils.getAttributeMethods(annotationType()).iterator();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
Method attributeMethod = iterator.next();
|
Method attributeMethod = iterator.next();
|
||||||
sb.append(attributeMethod.getName());
|
sb.append(attributeMethod.getName());
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -58,7 +58,7 @@ abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor {
|
||||||
@Override
|
@Override
|
||||||
public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
|
public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
|
||||||
String annotationType = Type.getType(asmTypeDescriptor).getClassName();
|
String annotationType = Type.getType(asmTypeDescriptor).getClassName();
|
||||||
AnnotationAttributes nestedAttributes = new AnnotationAttributes();
|
AnnotationAttributes nestedAttributes = new AnnotationAttributes(annotationType, this.classLoader);
|
||||||
this.attributes.put(attributeName, nestedAttributes);
|
this.attributes.put(attributeName, nestedAttributes);
|
||||||
return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
|
return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,8 +44,6 @@ import org.springframework.util.ObjectUtils;
|
||||||
*/
|
*/
|
||||||
final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {
|
final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {
|
||||||
|
|
||||||
private final String annotationType;
|
|
||||||
|
|
||||||
private final MultiValueMap<String, AnnotationAttributes> attributesMap;
|
private final MultiValueMap<String, AnnotationAttributes> attributesMap;
|
||||||
|
|
||||||
private final Map<String, Set<String>> metaAnnotationMap;
|
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,
|
MultiValueMap<String, AnnotationAttributes> attributesMap, Map<String, Set<String>> metaAnnotationMap,
|
||||||
ClassLoader classLoader) {
|
ClassLoader classLoader) {
|
||||||
|
|
||||||
super(annotationType, new AnnotationAttributes(), classLoader);
|
super(annotationType, new AnnotationAttributes(annotationType, classLoader), classLoader);
|
||||||
this.annotationType = annotationType;
|
|
||||||
this.attributesMap = attributesMap;
|
this.attributesMap = attributesMap;
|
||||||
this.metaAnnotationMap = metaAnnotationMap;
|
this.metaAnnotationMap = metaAnnotationMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doVisitEnd(Class<?> annotationClass) {
|
public void visitEnd() {
|
||||||
super.doVisitEnd(annotationClass);
|
super.visitEnd();
|
||||||
List<AnnotationAttributes> attributes = this.attributesMap.get(this.annotationType);
|
|
||||||
if (attributes == null) {
|
Class<?> annotationClass = this.attributes.annotationType();
|
||||||
this.attributesMap.add(this.annotationType, this.attributes);
|
if (annotationClass != null) {
|
||||||
}
|
List<AnnotationAttributes> attributeList = this.attributesMap.get(this.annotationType);
|
||||||
else {
|
if (attributeList == null) {
|
||||||
attributes.add(0, this.attributes);
|
this.attributesMap.add(this.annotationType, this.attributes);
|
||||||
}
|
}
|
||||||
Set<Annotation> visited = new LinkedHashSet<>();
|
else {
|
||||||
Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass);
|
attributeList.add(0, this.attributes);
|
||||||
if (!ObjectUtils.isEmpty(metaAnnotations)) {
|
}
|
||||||
for (Annotation metaAnnotation : metaAnnotations) {
|
Set<Annotation> visited = new LinkedHashSet<>();
|
||||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) {
|
Annotation[] metaAnnotations = AnnotationUtils.getAnnotations(annotationClass);
|
||||||
recursivelyCollectMetaAnnotations(visited, metaAnnotation);
|
if (!ObjectUtils.isEmpty(metaAnnotations)) {
|
||||||
|
for (Annotation metaAnnotation : metaAnnotations) {
|
||||||
|
if (!AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotation)) {
|
||||||
|
recursivelyCollectMetaAnnotations(visited, metaAnnotation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (this.metaAnnotationMap != null) {
|
||||||
if (this.metaAnnotationMap != null) {
|
Set<String> metaAnnotationTypeNames = new LinkedHashSet<>(visited.size());
|
||||||
Set<String> metaAnnotationTypeNames = new LinkedHashSet<>(visited.size());
|
for (Annotation ann : visited) {
|
||||||
for (Annotation ann : visited) {
|
metaAnnotationTypeNames.add(ann.annotationType().getName());
|
||||||
metaAnnotationTypeNames.add(ann.annotationType().getName());
|
}
|
||||||
|
this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
|
||||||
}
|
}
|
||||||
this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,8 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
|
||||||
public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
|
public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
|
||||||
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
|
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
|
||||||
this.attributesMap, this.metaAnnotationMap, annotationName);
|
this.attributesMap, this.metaAnnotationMap, annotationName);
|
||||||
return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
|
return AnnotationReadingVisitorUtils.convertClassValues(
|
||||||
|
"class '" + getClassName() + "'", this.classLoader, raw, classValuesAsString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -148,7 +149,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
|
||||||
}
|
}
|
||||||
for (AnnotationAttributes raw : attributes) {
|
for (AnnotationAttributes raw : attributes) {
|
||||||
for (Map.Entry<String, Object> entry : AnnotationReadingVisitorUtils.convertClassValues(
|
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());
|
allAttributes.add(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,25 +41,29 @@ import org.springframework.util.ObjectUtils;
|
||||||
*/
|
*/
|
||||||
abstract class AnnotationReadingVisitorUtils {
|
abstract class AnnotationReadingVisitorUtils {
|
||||||
|
|
||||||
public static AnnotationAttributes convertClassValues(ClassLoader classLoader, AnnotationAttributes original,
|
public static AnnotationAttributes convertClassValues(Object annotatedElement,
|
||||||
boolean classValuesAsString) {
|
ClassLoader classLoader, AnnotationAttributes original, boolean classValuesAsString) {
|
||||||
|
|
||||||
if (original == null) {
|
if (original == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
AnnotationAttributes result = new AnnotationAttributes(original.size());
|
AnnotationAttributes result = new AnnotationAttributes(original);
|
||||||
for (Map.Entry<String, Object> entry : original.entrySet()) {
|
AnnotationUtils.postProcessAnnotationAttributes(annotatedElement, result, classValuesAsString);
|
||||||
|
|
||||||
|
for (Map.Entry<String, Object> entry : result.entrySet()) {
|
||||||
try {
|
try {
|
||||||
Object value = entry.getValue();
|
Object value = entry.getValue();
|
||||||
if (value instanceof AnnotationAttributes) {
|
if (value instanceof AnnotationAttributes) {
|
||||||
value = convertClassValues(classLoader, (AnnotationAttributes) value, classValuesAsString);
|
value = convertClassValues(
|
||||||
|
annotatedElement, classLoader, (AnnotationAttributes) value, classValuesAsString);
|
||||||
}
|
}
|
||||||
else if (value instanceof AnnotationAttributes[]) {
|
else if (value instanceof AnnotationAttributes[]) {
|
||||||
AnnotationAttributes[] values = (AnnotationAttributes[]) value;
|
AnnotationAttributes[] values = (AnnotationAttributes[]) value;
|
||||||
for (int i = 0; i < values.length; i++) {
|
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) {
|
else if (value instanceof Type) {
|
||||||
value = (classValuesAsString ? ((Type) value).getClassName() :
|
value = (classValuesAsString ? ((Type) value).getClassName() :
|
||||||
|
@ -67,7 +71,8 @@ abstract class AnnotationReadingVisitorUtils {
|
||||||
}
|
}
|
||||||
else if (value instanceof Type[]) {
|
else if (value instanceof Type[]) {
|
||||||
Type[] array = (Type[]) value;
|
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++) {
|
for (int i = 0; i < array.length; i++) {
|
||||||
convArray[i] = (classValuesAsString ? array[i].getClassName() :
|
convArray[i] = (classValuesAsString ? array[i].getClassName() :
|
||||||
classLoader.loadClass(array[i].getClassName()));
|
classLoader.loadClass(array[i].getClassName()));
|
||||||
|
@ -75,11 +80,11 @@ abstract class AnnotationReadingVisitorUtils {
|
||||||
value = convArray;
|
value = convArray;
|
||||||
}
|
}
|
||||||
else if (classValuesAsString) {
|
else if (classValuesAsString) {
|
||||||
if (value instanceof Class) {
|
if (value instanceof Class<?>) {
|
||||||
value = ((Class<?>) value).getName();
|
value = ((Class<?>) value).getName();
|
||||||
}
|
}
|
||||||
else if (value instanceof Class[]) {
|
else if (value instanceof Class<?>[]) {
|
||||||
Class<?>[] clazzArray = (Class[]) value;
|
Class<?>[] clazzArray = (Class<?>[]) value;
|
||||||
String[] newValue = new String[clazzArray.length];
|
String[] newValue = new String[clazzArray.length];
|
||||||
for (int i = 0; i < clazzArray.length; i++) {
|
for (int i = 0; i < clazzArray.length; i++) {
|
||||||
newValue[i] = clazzArray[i].getName();
|
newValue[i] = clazzArray[i].getName();
|
||||||
|
@ -87,13 +92,14 @@ abstract class AnnotationReadingVisitorUtils {
|
||||||
value = newValue;
|
value = newValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.put(entry.getKey(), value);
|
entry.setValue(value);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
// Class not found - can't resolve class reference in annotation attribute.
|
// Class not found - can't resolve class reference in annotation attribute.
|
||||||
result.put(entry.getKey(), ex);
|
result.put(entry.getKey(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,13 +129,12 @@ abstract class AnnotationReadingVisitorUtils {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// To start with, we populate the results with a copy of all attribute
|
// To start with, we populate the result with a copy of all attribute values
|
||||||
// values from the target annotation. A copy is necessary so that we do
|
// from the target annotation. A copy is necessary so that we do not
|
||||||
// not inadvertently mutate the state of the metadata passed to this
|
// inadvertently mutate the state of the metadata passed to this method.
|
||||||
// method.
|
AnnotationAttributes result = new AnnotationAttributes(attributesList.get(0));
|
||||||
AnnotationAttributes results = new AnnotationAttributes(attributesList.get(0));
|
|
||||||
|
|
||||||
Set<String> overridableAttributeNames = new HashSet<>(results.keySet());
|
Set<String> overridableAttributeNames = new HashSet<>(result.keySet());
|
||||||
overridableAttributeNames.remove(AnnotationUtils.VALUE);
|
overridableAttributeNames.remove(AnnotationUtils.VALUE);
|
||||||
|
|
||||||
// Since the map is a LinkedMultiValueMap, we depend on the ordering of
|
// Since the map is a LinkedMultiValueMap, we depend on the ordering of
|
||||||
|
@ -152,14 +157,14 @@ abstract class AnnotationReadingVisitorUtils {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
// Store the value, potentially overriding a value from an attribute
|
// Store the value, potentially overriding a value from an attribute
|
||||||
// of the same name found higher in the annotation hierarchy.
|
// of the same name found higher in the annotation hierarchy.
|
||||||
results.put(overridableAttributeName, value);
|
result.put(overridableAttributeName, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,8 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho
|
||||||
public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
|
public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
|
||||||
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
|
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
|
||||||
this.attributesMap, this.metaAnnotationMap, annotationName);
|
this.attributesMap, this.metaAnnotationMap, annotationName);
|
||||||
return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
|
return AnnotationReadingVisitorUtils.convertClassValues(
|
||||||
|
"method '" + getMethodName() + "'", this.classLoader, raw, classValuesAsString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -137,8 +138,9 @@ public class MethodMetadataReadingVisitor extends MethodVisitor implements Metho
|
||||||
}
|
}
|
||||||
MultiValueMap<String, Object> allAttributes = new LinkedMultiValueMap<>();
|
MultiValueMap<String, Object> allAttributes = new LinkedMultiValueMap<>();
|
||||||
for (AnnotationAttributes annotationAttributes : this.attributesMap.get(annotationName)) {
|
for (AnnotationAttributes annotationAttributes : this.attributesMap.get(annotationName)) {
|
||||||
for (Map.Entry<String, Object> entry : AnnotationReadingVisitorUtils.convertClassValues(
|
AnnotationAttributes convertedAttributes = AnnotationReadingVisitorUtils.convertClassValues(
|
||||||
this.classLoader, annotationAttributes, classValuesAsString).entrySet()) {
|
"method '" + getMethodName() + "'", this.classLoader, annotationAttributes, classValuesAsString);
|
||||||
|
for (Map.Entry<String, Object> entry : convertedAttributes.entrySet()) {
|
||||||
allAttributes.add(entry.getKey(), entry.getValue());
|
allAttributes.add(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor
|
||||||
@Override
|
@Override
|
||||||
public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
|
public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
|
||||||
String annotationType = Type.getType(asmTypeDescriptor).getClassName();
|
String annotationType = Type.getType(asmTypeDescriptor).getClassName();
|
||||||
AnnotationAttributes nestedAttributes = new AnnotationAttributes();
|
AnnotationAttributes nestedAttributes = new AnnotationAttributes(annotationType, this.classLoader);
|
||||||
this.allNestedAttributes.add(nestedAttributes);
|
this.allNestedAttributes.add(nestedAttributes);
|
||||||
return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
|
return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.core.type.classreading;
|
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.AnnotationAttributes;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
|
||||||
|
@ -30,7 +26,7 @@ import org.springframework.core.annotation.AnnotationUtils;
|
||||||
*/
|
*/
|
||||||
class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor {
|
class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor {
|
||||||
|
|
||||||
private final String annotationType;
|
protected final String annotationType;
|
||||||
|
|
||||||
|
|
||||||
public RecursiveAnnotationAttributesVisitor(
|
public RecursiveAnnotationAttributesVisitor(
|
||||||
|
@ -42,49 +38,8 @@ class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVi
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void visitEnd() {
|
public void visitEnd() {
|
||||||
try {
|
AnnotationUtils.registerDefaultValues(this.attributes);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,16 +18,11 @@ package org.springframework.core.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
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.hamcrest.CoreMatchers.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
@ -133,21 +128,21 @@ public class AnnotationAttributesTests {
|
||||||
@Test
|
@Test
|
||||||
public void getEnumWithNullAttributeName() {
|
public void getEnumWithNullAttributeName() {
|
||||||
exception.expect(IllegalArgumentException.class);
|
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);
|
attributes.getEnum(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getEnumWithEmptyAttributeName() {
|
public void getEnumWithEmptyAttributeName() {
|
||||||
exception.expect(IllegalArgumentException.class);
|
exception.expect(IllegalArgumentException.class);
|
||||||
exception.expectMessage(containsString("attributeName must not be null or empty"));
|
exception.expectMessage("must not be null or empty");
|
||||||
attributes.getEnum("");
|
attributes.getEnum("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getEnumWithUnknownAttributeName() {
|
public void getEnumWithUnknownAttributeName() {
|
||||||
exception.expect(IllegalArgumentException.class);
|
exception.expect(IllegalArgumentException.class);
|
||||||
exception.expectMessage(containsString("Attribute 'bogus' not found"));
|
exception.expectMessage("Attribute 'bogus' not found");
|
||||||
attributes.getEnum("bogus");
|
attributes.getEnum("bogus");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,337 +154,6 @@ public class AnnotationAttributesTests {
|
||||||
attributes.getEnum("color");
|
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 {
|
enum Color {
|
||||||
|
|
||||||
|
@ -514,23 +178,4 @@ public class AnnotationAttributesTests {
|
||||||
static class FilteredClass {
|
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 {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import java.util.Set;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.core.annotation.AnnotationAttributes;
|
import org.springframework.core.annotation.AnnotationAttributes;
|
||||||
import org.springframework.core.type.classreading.MetadataReader;
|
import org.springframework.core.type.classreading.MetadataReader;
|
||||||
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||||
|
@ -291,6 +292,7 @@ public class AnnotationMetadataTests {
|
||||||
Set<MethodMetadata> methods = metadata.getAnnotatedMethods(DirectAnnotation.class.getName());
|
Set<MethodMetadata> methods = metadata.getAnnotatedMethods(DirectAnnotation.class.getName());
|
||||||
MethodMetadata method = methods.iterator().next();
|
MethodMetadata method = methods.iterator().next();
|
||||||
assertEquals("direct", method.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"));
|
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");
|
List<Object> allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value");
|
||||||
assertThat(new HashSet<>(allMeta), is(equalTo(new HashSet<Object>(Arrays.asList("direct", "meta")))));
|
assertThat(new HashSet<>(allMeta), is(equalTo(new HashSet<Object>(Arrays.asList("direct", "meta")))));
|
||||||
allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("additional");
|
allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("additional");
|
||||||
|
@ -416,7 +418,11 @@ public class AnnotationMetadataTests {
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface DirectAnnotation {
|
public @interface DirectAnnotation {
|
||||||
|
|
||||||
String value();
|
@AliasFor("myValue")
|
||||||
|
String value() default "";
|
||||||
|
|
||||||
|
@AliasFor("value")
|
||||||
|
String myValue() default "";
|
||||||
|
|
||||||
String additional() default "direct";
|
String additional() default "direct";
|
||||||
}
|
}
|
||||||
|
@ -449,7 +455,7 @@ public class AnnotationMetadataTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SPR-10914
|
// SPR-10914
|
||||||
public static enum SubclassEnum {
|
public enum SubclassEnum {
|
||||||
FOO {
|
FOO {
|
||||||
/* Do not delete! This subclassing is intentional. */
|
/* Do not delete! This subclassing is intentional. */
|
||||||
},
|
},
|
||||||
|
@ -489,14 +495,14 @@ public class AnnotationMetadataTests {
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Component
|
@Component
|
||||||
public static @interface TestConfiguration {
|
public @interface TestConfiguration {
|
||||||
|
|
||||||
String value() default "";
|
String value() default "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public static @interface TestComponentScan {
|
public @interface TestComponentScan {
|
||||||
|
|
||||||
String[] value() default {};
|
String[] value() default {};
|
||||||
|
|
||||||
|
@ -509,7 +515,7 @@ public class AnnotationMetadataTests {
|
||||||
@TestComponentScan(basePackages = "bogus")
|
@TestComponentScan(basePackages = "bogus")
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public static @interface ComposedConfigurationWithAttributeOverrides {
|
public @interface ComposedConfigurationWithAttributeOverrides {
|
||||||
|
|
||||||
String[] basePackages() default {};
|
String[] basePackages() default {};
|
||||||
}
|
}
|
||||||
|
@ -520,19 +526,19 @@ public class AnnotationMetadataTests {
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public static @interface NamedAnnotation1 {
|
public @interface NamedAnnotation1 {
|
||||||
String name() default "";
|
String name() default "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public static @interface NamedAnnotation2 {
|
public @interface NamedAnnotation2 {
|
||||||
String name() default "";
|
String name() default "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public static @interface NamedAnnotation3 {
|
public @interface NamedAnnotation3 {
|
||||||
String name() default "";
|
String name() default "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,7 +553,7 @@ public class AnnotationMetadataTests {
|
||||||
@NamedAnnotation3(name = "name 3")
|
@NamedAnnotation3(name = "name 3")
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public static @interface NamedComposedAnnotation {
|
public @interface NamedComposedAnnotation {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NamedComposedAnnotation
|
@NamedComposedAnnotation
|
||||||
|
|
Loading…
Reference in New Issue