Restore retrieval of plain annotations through direct presence checks

Includes deprecation of several AnnotationUtils methods and nullability refinements for passed-in function arguments at the MergedAnnotation API level... also, MergedAnnotation.getType() returns a Class now.

Closes gh-22663
Closes gh-22685
This commit is contained in:
Juergen Hoeller 2019-03-26 16:13:41 +01:00
parent f7a4850675
commit 210b178922
16 changed files with 430 additions and 552 deletions

View File

@ -521,9 +521,10 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
* @param ann the Autowired annotation
* @return whether the annotation indicates that a dependency is required
*/
@SuppressWarnings("deprecation")
protected boolean determineRequiredStatus(MergedAnnotation<?> ann) {
return determineRequiredStatus(
ann.asMap(mergedAnnotation -> new AnnotationAttributes()));
return determineRequiredStatus((AnnotationAttributes)
ann.asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType())));
}
/**
@ -533,7 +534,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
* or method when no beans are found.
* @param ann the Autowired annotation
* @return whether the annotation indicates that a dependency is required
* @deprecated since 5.2 in favor of {@link #determineRequiredStatus(MergedAnnotation)}
* @deprecated since 5.2, in favor of {@link #determineRequiredStatus(MergedAnnotation)}
*/
@Deprecated
protected boolean determineRequiredStatus(AnnotationAttributes ann) {

View File

@ -18,7 +18,6 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Predicate;
@ -30,6 +29,7 @@ import org.springframework.util.Assert;
* Abstract base class for {@link MergedAnnotation} implementations.
*
* @author Phillip Webb
* @author Juergen Hoeller
* @since 5.2
* @param <A> the annotation type
*/
@ -167,19 +167,10 @@ abstract class AbstractMergedAnnotation<A extends Annotation> implements MergedA
}
@Override
public Map<String, Object> asMap(MapValues... options) {
return asMap(null, options);
}
@Override
public Optional<A> synthesize(
@Nullable Predicate<? super MergedAnnotation<A>> condition)
public Optional<A> synthesize(Predicate<? super MergedAnnotation<A>> condition)
throws NoSuchElementException {
if (condition == null || condition.test(this)) {
return Optional.of(synthesize());
}
return Optional.empty();
return (condition.test(this) ? Optional.of(synthesize()) : Optional.empty());
}
@Override
@ -199,7 +190,7 @@ abstract class AbstractMergedAnnotation<A extends Annotation> implements MergedA
T value = getAttributeValue(attributeName, type);
if (value == null) {
throw new NoSuchElementException("No attribute named '" + attributeName +
"' present in merged annotation " + getType());
"' present in merged annotation " + getType().getName());
}
return value;
}

View File

@ -21,7 +21,6 @@ import java.lang.reflect.AnnotatedElement;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@ -137,9 +136,7 @@ public abstract class AnnotatedElementUtils {
* @see #getMetaAnnotationTypes(AnnotatedElement, Class)
* @see #hasMetaAnnotationTypes
*/
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element,
String annotationName) {
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationName) {
for (Annotation annotation : element.getAnnotations()) {
if (annotation.annotationType().getName().equals(annotationName)) {
return getMetaAnnotationTypes(element, annotation);
@ -148,14 +145,12 @@ public abstract class AnnotatedElementUtils {
return Collections.emptySet();
}
private static Set<String> getMetaAnnotationTypes(AnnotatedElement element,
@Nullable Annotation annotation) {
private static Set<String> getMetaAnnotationTypes(AnnotatedElement element, @Nullable Annotation annotation) {
if (annotation == null) {
return Collections.emptySet();
}
return getAnnotations(annotation.annotationType()).stream()
.map(MergedAnnotation::getType)
.map(mergedAnnotation -> mergedAnnotation.getType().getName())
.collect(Collectors.toCollection(LinkedHashSet::new));
}
@ -171,11 +166,8 @@ public abstract class AnnotatedElementUtils {
* @since 4.2.3
* @see #getMetaAnnotationTypes
*/
public static boolean hasMetaAnnotationTypes(AnnotatedElement element,
Class<? extends Annotation> annotationType) {
return getAnnotations(element).stream(annotationType)
.anyMatch(MergedAnnotation::isMetaPresent);
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
return getAnnotations(element).stream(annotationType).anyMatch(MergedAnnotation::isMetaPresent);
}
/**
@ -190,11 +182,8 @@ public abstract class AnnotatedElementUtils {
* @return {@code true} if a matching meta-annotation is present
* @see #getMetaAnnotationTypes
*/
public static boolean hasMetaAnnotationTypes(AnnotatedElement element,
String annotationName) {
return getAnnotations(element).stream(annotationName)
.anyMatch(MergedAnnotation::isMetaPresent);
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationName) {
return getAnnotations(element).stream(annotationName).anyMatch(MergedAnnotation::isMetaPresent);
}
/**
@ -211,9 +200,16 @@ public abstract class AnnotatedElementUtils {
* @since 4.2.3
* @see #hasAnnotation(AnnotatedElement, Class)
*/
public static boolean isAnnotated(AnnotatedElement element,
Class<? extends Annotation> annotationType) {
public static boolean isAnnotated(AnnotatedElement element,Class<? extends Annotation> annotationType) {
// Shortcut: directly present on the element, with no processing needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return element.isAnnotationPresent(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return false;
}
// Exhaustive retrieval of merged annotations...
return getAnnotations(element).isPresent(annotationType);
}
@ -278,8 +274,8 @@ public abstract class AnnotatedElementUtils {
* @see #getAllAnnotationAttributes(AnnotatedElement, String)
*/
@Nullable
public static AnnotationAttributes getMergedAnnotationAttributes(
AnnotatedElement element, String annotationName) {
public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element,
String annotationName) {
return getMergedAnnotationAttributes(element, annotationName, false, false);
}
@ -311,9 +307,8 @@ public abstract class AnnotatedElementUtils {
* @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/
@Nullable
public static AnnotationAttributes getMergedAnnotationAttributes(
AnnotatedElement element, String annotationName, boolean classValuesAsString,
boolean nestedAnnotationsAsMap) {
public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element,
String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
MergedAnnotation<?> mergedAnnotation = getAnnotations(element)
.get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared());
@ -328,30 +323,23 @@ public abstract class AnnotatedElementUtils {
* the result back into an annotation of the specified {@code annotationType}.
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both
* within a single annotation and within the annotation hierarchy.
* <p>This method delegates to {@link #getMergedAnnotationAttributes(AnnotatedElement, Class)}
* and {@link AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}.
* @param element the annotated element
* @param annotationType the annotation type to find
* @return the merged, synthesized {@code Annotation}, or {@code null} if not found
* @since 4.2
* @see #getMergedAnnotationAttributes(AnnotatedElement, Class)
* @see #findMergedAnnotation(AnnotatedElement, Class)
* @see AnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)
*/
@Nullable
public static <A extends Annotation> A getMergedAnnotation(AnnotatedElement element,
Class<A> annotationType) {
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return null;
}
A annotation = AnnotationsScanner.getDeclaredAnnotation(element, annotationType);
if (annotation != null) {
return AnnotationUtils.synthesizeAnnotation(annotation, element);
}
public static <A extends Annotation> A getMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
// Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return element.getDeclaredAnnotation(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return null;
}
// Exhaustive retrieval of merged annotations...
return getAnnotations(element)
.get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared())
.synthesize(MergedAnnotation::isPresent).orElse(null);
@ -515,9 +503,8 @@ public abstract class AnnotatedElementUtils {
* attributes from all annotations found, or {@code null} if not found
*/
@Nullable
public static MultiValueMap<String, Object> getAllAnnotationAttributes(
AnnotatedElement element, String annotationName,
final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
String annotationName, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
MapValues[] mapValues = MapValues.of(classValuesAsString, nestedAnnotationsAsMap);
return getAnnotations(element).stream(annotationName)
@ -540,16 +527,16 @@ public abstract class AnnotatedElementUtils {
* @since 4.3
* @see #isAnnotated(AnnotatedElement, Class)
*/
public static boolean hasAnnotation(AnnotatedElement element,
Class<? extends Annotation> annotationType) {
public static boolean hasAnnotation(AnnotatedElement element, Class<? extends Annotation> annotationType) {
// Shortcut: directly present on the element, with no processing needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return element.isAnnotationPresent(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return false;
}
if (AnnotationsScanner.getDeclaredAnnotation(element, annotationType) != null) {
return true;
}
// Exhaustive retrieval of merged annotations...
return findAnnotations(element).isPresent(annotationType);
}
@ -581,9 +568,8 @@ public abstract class AnnotatedElementUtils {
* @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/
@Nullable
public static AnnotationAttributes findMergedAnnotationAttributes(
AnnotatedElement element, Class<? extends Annotation> annotationType,
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element,
Class<? extends Annotation> annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
MergedAnnotation<?> mergedAnnotation = findAnnotations(element)
.get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared());
@ -618,9 +604,8 @@ public abstract class AnnotatedElementUtils {
* @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)
*/
@Nullable
public static AnnotationAttributes findMergedAnnotationAttributes(
AnnotatedElement element, String annotationName, boolean classValuesAsString,
boolean nestedAnnotationsAsMap) {
public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element,
String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
MergedAnnotation<?> mergedAnnotation = findAnnotations(element)
.get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared());
@ -646,16 +631,16 @@ public abstract class AnnotatedElementUtils {
* @see #getMergedAnnotationAttributes(AnnotatedElement, Class)
*/
@Nullable
public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element,
Class<A> annotationType) {
A annotation = AnnotationsScanner.getDeclaredAnnotation(element, annotationType);
if (annotation != null) {
return AnnotationUtils.synthesizeAnnotation(annotation, element);
public static <A extends Annotation> A findMergedAnnotation(AnnotatedElement element, Class<A> annotationType) {
// Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return element.getDeclaredAnnotation(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return null;
}
// Exhaustive retrieval of merged annotations...
return findAnnotations(element)
.get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared())
.synthesize(MergedAnnotation::isPresent).orElse(null);
@ -680,9 +665,7 @@ public abstract class AnnotatedElementUtils {
* @see #findMergedAnnotation(AnnotatedElement, Class)
* @see #getAllMergedAnnotations(AnnotatedElement, Class)
*/
public static <A extends Annotation> Set<A> findAllMergedAnnotations(
AnnotatedElement element, Class<A> annotationType) {
public static <A extends Annotation> Set<A> findAllMergedAnnotations(AnnotatedElement element, Class<A> annotationType) {
return findAnnotations(element).stream(annotationType)
.sorted(highAggregateIndexesFirst())
.collect(MergedAnnotationCollectors.toAnnotationSet());
@ -706,9 +689,7 @@ public abstract class AnnotatedElementUtils {
* @since 5.1
* @see #findAllMergedAnnotations(AnnotatedElement, Class)
*/
public static Set<Annotation> findAllMergedAnnotations(AnnotatedElement element,
Set<Class<? extends Annotation>> annotationTypes) {
public static Set<Annotation> findAllMergedAnnotations(AnnotatedElement element, Set<Class<? extends Annotation>> annotationTypes) {
return findAnnotations(element).stream()
.filter(MergedAnnotationPredicates.typeIn(annotationTypes))
.sorted(highAggregateIndexesFirst())
@ -739,8 +720,8 @@ public abstract class AnnotatedElementUtils {
* @see #findAllMergedAnnotations(AnnotatedElement, Class)
* @see #findMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
*/
public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(
AnnotatedElement element, Class<A> annotationType) {
public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(AnnotatedElement element,
Class<A> annotationType) {
return findMergedRepeatableAnnotations(element, annotationType, null);
}
@ -771,9 +752,8 @@ public abstract class AnnotatedElementUtils {
* @see #findMergedAnnotation(AnnotatedElement, Class)
* @see #findAllMergedAnnotations(AnnotatedElement, Class)
*/
public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(
AnnotatedElement element, Class<A> annotationType,
@Nullable Class<? extends Annotation> containerType) {
public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(AnnotatedElement element,
Class<A> annotationType, @Nullable Class<? extends Annotation> containerType) {
return findRepeatableAnnotations(element, containerType, annotationType)
.stream(annotationType)
@ -783,43 +763,40 @@ public abstract class AnnotatedElementUtils {
private static MergedAnnotations getAnnotations(AnnotatedElement element) {
return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS,
RepeatableContainers.none(), AnnotationUtils.JAVA_LANG_ANNOTATION_FILTER);
RepeatableContainers.none(), AnnotationFilter.PLAIN);
}
private static MergedAnnotations getRepeatableAnnotations(AnnotatedElement element,
@Nullable Class<? extends Annotation> containerType,
Class<? extends Annotation> annotationType) {
@Nullable Class<? extends Annotation> containerType, Class<? extends Annotation> annotationType) {
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS,
repeatableContainers, AnnotationUtils.JAVA_LANG_ANNOTATION_FILTER);
repeatableContainers, AnnotationFilter.PLAIN);
}
private static MergedAnnotations findAnnotations(AnnotatedElement element) {
return MergedAnnotations.from(element, SearchStrategy.EXHAUSTIVE,
RepeatableContainers.none(), AnnotationUtils.JAVA_LANG_ANNOTATION_FILTER);
RepeatableContainers.none(), AnnotationFilter.PLAIN);
}
private static MergedAnnotations findRepeatableAnnotations(AnnotatedElement element,
@Nullable Class<? extends Annotation> containerType,
Class<? extends Annotation> annotationType) {
@Nullable Class<? extends Annotation> containerType, Class<? extends Annotation> annotationType) {
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
return MergedAnnotations.from(element, SearchStrategy.EXHAUSTIVE,
repeatableContainers, AnnotationUtils.JAVA_LANG_ANNOTATION_FILTER);
repeatableContainers, AnnotationFilter.PLAIN);
}
private static Object parentAndType(MergedAnnotation<Annotation> annotation) {
if (annotation.getParent() == null) {
return annotation.getType();
return annotation.getType().getName();
}
return annotation.getParent().getType() + ":" + annotation.getParent().getType();
return annotation.getParent().getType().getName() + ":" + annotation.getParent().getType().getName();
}
@Nullable
private static MultiValueMap<String, Object> nullIfEmpty(
MultiValueMap<String, Object> map) {
return map.isEmpty() ? null : map;
private static MultiValueMap<String, Object> nullIfEmpty(MultiValueMap<String, Object> map) {
return (map.isEmpty() ? null : map);
}
private static <A extends Annotation> Comparator<MergedAnnotation<A>> highAggregateIndexesFirst() {
@ -834,7 +811,7 @@ public abstract class AnnotatedElementUtils {
if (!annotation.isPresent()) {
return null;
}
return annotation.asMap(mergedAnnotation -> new AnnotationAttributes(),
return annotation.asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType()),
MapValues.of(classValuesAsString, nestedAnnotationsAsMap));
}

View File

@ -17,10 +17,6 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import org.springframework.util.Assert;
/**
* Callback interface that can be used to filter specific annotation types.
@ -96,52 +92,4 @@ public interface AnnotationFilter {
return new PackagesAnnotationFilter(packages);
}
/**
* Return an {@link AnnotationFilter} that is the most appropriate for, and
* will always match the given annotation type. Whenever possible,
* {@link AnnotationFilter#PLAIN} will be returned.
* @param annotationType the annotation type to check
* @return the most appropriate annotation filter
*/
static AnnotationFilter mostAppropriateFor(Class<?> annotationType) {
return (PLAIN.matches(annotationType) ? NONE : PLAIN);
}
/**
* Return an {@link AnnotationFilter} that is the most appropriate for, and
* will always match all the given annotation types. Whenever possible,
* {@link AnnotationFilter#PLAIN} will be returned.
* @param annotationTypes the annotation types to check
* @return the most appropriate annotation filter
*/
static AnnotationFilter mostAppropriateFor(Class<?>... annotationTypes) {
return mostAppropriateFor(Arrays.asList(annotationTypes));
}
/**
* Return an {@link AnnotationFilter} that is the most appropriate for, and
* will always match all the given annotation types. Whenever possible,
* {@link AnnotationFilter#PLAIN} will be returned.
* @param annotationTypes the annotation types to check (may be class names
* or class types)
* @return the most appropriate annotation filter
*/
@SuppressWarnings("unchecked")
static AnnotationFilter mostAppropriateFor(Collection<?> annotationTypes) {
for (Object annotationType : annotationTypes) {
if (annotationType == null) {
continue;
}
Assert.isTrue(annotationType instanceof Class || annotationType instanceof String,
"AnnotationType must be a Class or String");
if (annotationType instanceof Class && PLAIN.matches((Class<Annotation>) annotationType)) {
return NONE;
}
if (annotationType instanceof String && PLAIN.matches((String) annotationType)) {
return NONE;
}
}
return PLAIN;
}
}

View File

@ -55,8 +55,7 @@ final class AnnotationTypeMappings {
private final List<AnnotationTypeMapping> mappings;
private AnnotationTypeMappings(AnnotationFilter filter,
Class<? extends Annotation> annotationType) {
private AnnotationTypeMappings(AnnotationFilter filter, Class<? extends Annotation> annotationType) {
this.filter = filter;
this.mappings = new ArrayList<>();
addAllMappings(annotationType);
@ -97,27 +96,23 @@ final class AnnotationTypeMappings {
}
}
private void addIfPossible(Deque<AnnotationTypeMapping> queue,
AnnotationTypeMapping parent, Annotation annotation) {
addIfPossible(queue, parent, annotation.annotationType(), annotation);
private void addIfPossible(Deque<AnnotationTypeMapping> queue, AnnotationTypeMapping parent, Annotation ann) {
addIfPossible(queue, parent, ann.annotationType(), ann);
}
private void addIfPossible(Deque<AnnotationTypeMapping> queue,
@Nullable AnnotationTypeMapping parent,
Class<? extends Annotation> annotationType, @Nullable Annotation annotation) {
private void addIfPossible(Deque<AnnotationTypeMapping> queue, @Nullable AnnotationTypeMapping parent,
Class<? extends Annotation> annotationType, @Nullable Annotation ann) {
try {
queue.addLast(new AnnotationTypeMapping(parent, annotationType, annotation));
queue.addLast(new AnnotationTypeMapping(parent, annotationType, ann));
}
catch (Exception ex) {
if (ex instanceof AnnotationConfigurationException) {
throw (AnnotationConfigurationException) ex;
}
if (failureLogger.isEnabled()) {
failureLogger.log(
"Failed to introspect meta-annotation "
+ annotationType.getName(),
(parent != null) ? parent.getAnnotationType() : null, ex);
failureLogger.log("Failed to introspect meta-annotation " + annotationType.getName(),
(parent != null ? parent.getAnnotationType() : null), ex);
}
}
}
@ -167,7 +162,7 @@ final class AnnotationTypeMappings {
* @return type mappings for the annotation type
*/
static AnnotationTypeMappings forAnnotationType(Class<? extends Annotation> annotationType) {
return forAnnotationType(annotationType, AnnotationFilter.mostAppropriateFor(annotationType));
return forAnnotationType(annotationType, AnnotationFilter.PLAIN);
}
/**
@ -197,7 +192,6 @@ final class AnnotationTypeMappings {
private final Map<Class<? extends Annotation>, AnnotationTypeMappings> mappings;
/**
* Create a cache instance with the specified filter.
* @param filter the annotation filter
@ -207,7 +201,6 @@ final class AnnotationTypeMappings {
this.mappings = new ConcurrentReferenceHashMap<>();
}
/**
* Return or create {@link AnnotationTypeMappings} for the specified
* annotation type.
@ -218,8 +211,7 @@ final class AnnotationTypeMappings {
return this.mappings.computeIfAbsent(annotationType, this::createMappings);
}
AnnotationTypeMappings createMappings(
Class<? extends Annotation> annotationType) {
AnnotationTypeMappings createMappings(Class<? extends Annotation> annotationType) {
return new AnnotationTypeMappings(this.filter, annotationType);
}

View File

@ -36,7 +36,6 @@ import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.Mirr
import org.springframework.core.annotation.MergedAnnotation.MapValues;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
@ -51,12 +50,12 @@ import org.springframework.util.StringUtils;
*
* <p>As a general rule for runtime-retained annotations (e.g. for transaction
* control, authorization, or service exposure), always use the lookup methods
* on this class (e.g., {@link #findAnnotation(Method, Class)},
* {@link #getAnnotation(Method, Class)}, and {@link #getAnnotations(Method)})
* instead of the plain annotation lookup methods in the JDK. You can still
* explicitly choose between a <em>get</em> lookup on the given class level only
* ({@link #getAnnotation(Method, Class)}) and a <em>find</em> lookup in the entire
* inheritance hierarchy of the given method ({@link #findAnnotation(Method, Class)}).
* on this class (e.g., {@link #findAnnotation(Method, Class)} and
* {@link #getAnnotation(Method, Class)}) instead of the plain annotation lookup
* methods in the JDK. You can still explicitly choose between a <em>get</em>
* lookup on the given class level only ({@link #getAnnotation(Method, Class)})
* and a <em>find</em> lookup in the entire inheritance hierarchy of the given
* method ({@link #findAnnotation(Method, Class)}).
*
* <h3>Terminology</h3>
* The terms <em>directly present</em>, <em>indirectly present</em>, and
@ -111,14 +110,13 @@ public abstract class AnnotationUtils {
*/
public static final String VALUE = MergedAnnotation.VALUE;
static final AnnotationFilter JAVA_LANG_ANNOTATION_FILTER =
private static final AnnotationFilter JAVA_LANG_ANNOTATION_FILTER =
AnnotationFilter.packages("java.lang.annotation");
private static Map<Class<? extends Annotation>, Map<String, DefaultValueHolder>> defaultValuesCache =
new ConcurrentReferenceHashMap<>();
/**
* Determine whether the given class is a candidate for carrying one of the specified
* annotations (at type, method or field level).
@ -180,10 +178,18 @@ public abstract class AnnotationUtils {
* @return the first matching annotation, or {@code null} if not found
* @since 4.0
*/
@SuppressWarnings("unchecked")
@Nullable
public static <A extends Annotation> A getAnnotation(Annotation annotation,
Class<A> annotationType) {
public static <A extends Annotation> A getAnnotation(Annotation annotation, Class<A> annotationType) {
// Shortcut: directly present on the element, with no merging needed?
if (annotationType.isInstance(annotation)) {
return synthesizeAnnotation((A) annotation, annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotation)) {
return null;
}
// Exhaustive retrieval of merged annotations...
return MergedAnnotations.from(annotation)
.get(annotationType).withNonMergedAttributes()
.synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null);
@ -202,15 +208,27 @@ public abstract class AnnotationUtils {
* @since 3.1
*/
@Nullable
public static <A extends Annotation> A getAnnotation(
AnnotatedElement annotatedElement, Class<A> annotationType) {
public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
// Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return annotatedElement.getAnnotation(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement)) {
return null;
}
// Exhaustive retrieval of merged annotations...
return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS,
RepeatableContainers.none(), AnnotationFilter.PLAIN)
.get(annotationType).withNonMergedAttributes()
.synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null);
}
private static <A extends Annotation> boolean isSingleLevelPresent(MergedAnnotation<A> mergedAnnotation) {
int depth = mergedAnnotation.getDepth();
return (depth == 0 || depth == 1);
}
/**
* Get a single {@link Annotation} of {@code annotationType} from the
* supplied {@link Method}, where the annotation is either <em>present</em>
@ -226,13 +244,9 @@ public abstract class AnnotationUtils {
* @see #getAnnotation(AnnotatedElement, Class)
*/
@Nullable
public static <A extends Annotation> A getAnnotation(Method method,
Class<A> annotationType) {
return MergedAnnotations.from(method, SearchStrategy.INHERITED_ANNOTATIONS,
RepeatableContainers.none(), AnnotationFilter.PLAIN)
.get(annotationType).withNonMergedAttributes()
.synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null);
public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
return getAnnotation((AnnotatedElement) resolvedMethod, annotationType);
}
/**
@ -245,14 +259,18 @@ public abstract class AnnotationUtils {
* failed to resolve at runtime)
* @since 4.0.8
* @see AnnotatedElement#getAnnotations()
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/
@Deprecated
@Nullable
public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) {
return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS,
RepeatableContainers.none(), AnnotationFilter.NONE).stream()
.filter(MergedAnnotation::isDirectlyPresent)
.map(MergedAnnotation::withNonMergedAttributes)
.collect(MergedAnnotationCollectors.toAnnotationArray());
try {
return synthesizeAnnotationArray(annotatedElement.getAnnotations(), annotatedElement);
}
catch (Throwable ex) {
handleIntrospectionFailure(annotatedElement, ex);
return null;
}
}
/**
@ -266,14 +284,18 @@ public abstract class AnnotationUtils {
* failed to resolve at runtime)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
* @see AnnotatedElement#getAnnotations()
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/
@Deprecated
@Nullable
public static Annotation[] getAnnotations(Method method) {
return MergedAnnotations.from(method, SearchStrategy.INHERITED_ANNOTATIONS,
RepeatableContainers.none(), AnnotationFilter.NONE).stream()
.filter(MergedAnnotation::isDirectlyPresent)
.map(MergedAnnotation::withNonMergedAttributes)
.collect(MergedAnnotationCollectors.toAnnotationArray());
try {
return synthesizeAnnotationArray(BridgeMethodResolver.findBridgedMethod(method).getAnnotations(), method);
}
catch (Throwable ex) {
handleIntrospectionFailure(method, ex);
return null;
}
}
/**
@ -302,9 +324,11 @@ public abstract class AnnotationUtils {
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getAnnotationsByType
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/
public static <A extends Annotation> Set<A> getRepeatableAnnotations(
AnnotatedElement annotatedElement, Class<A> annotationType) {
@Deprecated
public static <A extends Annotation> Set<A> getRepeatableAnnotations(AnnotatedElement annotatedElement,
Class<A> annotationType) {
return getRepeatableAnnotations(annotatedElement, annotationType, null);
}
@ -338,17 +362,17 @@ public abstract class AnnotationUtils {
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getAnnotationsByType
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/
public static <A extends Annotation> Set<A> getRepeatableAnnotations(
AnnotatedElement annotatedElement, Class<A> annotationType,
@Nullable Class<? extends Annotation> containerAnnotationType) {
@Deprecated
public static <A extends Annotation> Set<A> getRepeatableAnnotations(AnnotatedElement annotatedElement,
Class<A> annotationType, @Nullable Class<? extends Annotation> containerAnnotationType) {
RepeatableContainers repeatableContainers = containerAnnotationType != null ?
RepeatableContainers repeatableContainers = (containerAnnotationType != null ?
RepeatableContainers.of(annotationType, containerAnnotationType) :
RepeatableContainers.standardRepeatables();
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType);
RepeatableContainers.standardRepeatables());
return MergedAnnotations.from(annotatedElement, SearchStrategy.SUPER_CLASS,
repeatableContainers, annotationFilter)
repeatableContainers, AnnotationFilter.PLAIN)
.stream(annotationType)
.filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex))
.map(MergedAnnotation::withNonMergedAttributes)
@ -382,9 +406,11 @@ public abstract class AnnotationUtils {
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/
public static <A extends Annotation> Set<A> getDeclaredRepeatableAnnotations(
AnnotatedElement annotatedElement, Class<A> annotationType) {
@Deprecated
public static <A extends Annotation> Set<A> getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement,
Class<A> annotationType) {
return getDeclaredRepeatableAnnotations(annotatedElement, annotationType, null);
}
@ -418,18 +444,17 @@ public abstract class AnnotationUtils {
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/
public static <A extends Annotation> Set<A> getDeclaredRepeatableAnnotations(
AnnotatedElement annotatedElement, Class<A> annotationType,
@Nullable Class<? extends Annotation> containerAnnotationType) {
@Deprecated
public static <A extends Annotation> Set<A> getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement,
Class<A> annotationType, @Nullable Class<? extends Annotation> containerAnnotationType) {
RepeatableContainers repeatableContainers = containerAnnotationType != null ?
RepeatableContainers.of(annotationType, containerAnnotationType) :
RepeatableContainers.standardRepeatables();
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(
annotationType, containerAnnotationType);
return MergedAnnotations.from(annotatedElement, SearchStrategy.DIRECT,
repeatableContainers, annotationFilter).stream(annotationType)
repeatableContainers, AnnotationFilter.PLAIN).stream(annotationType)
.map(MergedAnnotation::withNonMergedAttributes)
.collect(MergedAnnotationCollectors.toAnnotationSet());
}
@ -452,11 +477,21 @@ public abstract class AnnotationUtils {
*/
@Nullable
public static <A extends Annotation> A findAnnotation(
AnnotatedElement annotatedElement, Class<A> annotationType) {
AnnotatedElement annotatedElement, @Nullable Class<A> annotationType) {
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType);
return MergedAnnotations.from(annotatedElement, SearchStrategy.DIRECT,
RepeatableContainers.none(), annotationFilter)
if (annotationType == null) {
return null;
}
// Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return annotatedElement.getDeclaredAnnotation(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement)) {
return null;
}
// Exhaustive retrieval of merged annotations...
return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS)
.get(annotationType).withNonMergedAttributes()
.synthesize(MergedAnnotation::isPresent).orElse(null);
}
@ -481,10 +516,16 @@ public abstract class AnnotationUtils {
if (annotationType == null) {
return null;
}
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType);
return MergedAnnotations.from(method, SearchStrategy.EXHAUSTIVE,
RepeatableContainers.none(), annotationFilter)
// Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return method.getDeclaredAnnotation(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(method)) {
return null;
}
// Exhaustive retrieval of merged annotations...
return MergedAnnotations.from(method, SearchStrategy.EXHAUSTIVE)
.get(annotationType).withNonMergedAttributes()
.synthesize(MergedAnnotation::isPresent).orElse(null);
}
@ -512,10 +553,20 @@ public abstract class AnnotationUtils {
* @return the first matching annotation, or {@code null} if not found
*/
@Nullable
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType);
return MergedAnnotations.from(clazz, SearchStrategy.EXHAUSTIVE,
RepeatableContainers.none(), annotationFilter)
public static <A extends Annotation> A findAnnotation(Class<?> clazz, @Nullable Class<A> annotationType) {
if (annotationType == null) {
return null;
}
// Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType)) {
return clazz.getDeclaredAnnotation(annotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(clazz)) {
return null;
}
// Exhaustive retrieval of merged annotations...
return MergedAnnotations.from(clazz, SearchStrategy.EXHAUSTIVE)
.get(annotationType).withNonMergedAttributes()
.synthesize(MergedAnnotation::isPresent).orElse(null);
}
@ -539,9 +590,9 @@ public abstract class AnnotationUtils {
* or {@code null} if not found
* @see Class#isAnnotationPresent(Class)
* @see Class#getDeclaredAnnotations()
* @see #findAnnotationDeclaringClassForTypes(List, Class)
* @see #isAnnotationDeclaredLocally(Class, Class)
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/
@Deprecated
@Nullable
public static Class<?> findAnnotationDeclaringClass(
Class<? extends Annotation> annotationType, @Nullable Class<?> clazz) {
@ -550,9 +601,7 @@ public abstract class AnnotationUtils {
return null;
}
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationType);
return (Class<?>) MergedAnnotations.from(clazz, SearchStrategy.SUPER_CLASS,
RepeatableContainers.none(), annotationFilter)
return (Class<?>) MergedAnnotations.from(clazz, SearchStrategy.SUPER_CLASS)
.get(annotationType, MergedAnnotation::isDirectlyPresent)
.getSource();
}
@ -578,9 +627,9 @@ public abstract class AnnotationUtils {
* @since 3.2.2
* @see Class#isAnnotationPresent(Class)
* @see Class#getDeclaredAnnotations()
* @see #findAnnotationDeclaringClass(Class, Class)
* @see #isAnnotationDeclaredLocally(Class, Class)
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/
@Deprecated
@Nullable
public static Class<?> findAnnotationDeclaringClassForTypes(
List<Class<? extends Annotation>> annotationTypes, @Nullable Class<?> clazz) {
@ -589,9 +638,7 @@ public abstract class AnnotationUtils {
return null;
}
AnnotationFilter annotationFilter = AnnotationFilter.mostAppropriateFor(annotationTypes);
return (Class<?>) MergedAnnotations.from(clazz, SearchStrategy.SUPER_CLASS,
RepeatableContainers.none(), annotationFilter)
return (Class<?>) MergedAnnotations.from(clazz, SearchStrategy.SUPER_CLASS)
.stream()
.filter(MergedAnnotationPredicates.typeIn(annotationTypes).and(MergedAnnotation::isDirectlyPresent))
.map(MergedAnnotation::getSource)
@ -605,16 +652,13 @@ public abstract class AnnotationUtils {
* <p>The supplied {@link Class} may represent any type.
* <p>Meta-annotations will <em>not</em> be searched.
* <p>Note: This method does <strong>not</strong> determine if the annotation
* is {@linkplain java.lang.annotation.Inherited inherited}. For greater
* clarity regarding inherited annotations, consider using
* {@link #isAnnotationInherited(Class, Class)} instead.
* is {@linkplain java.lang.annotation.Inherited inherited}.
* @param annotationType the annotation type to look for
* @param clazz the class to check for the annotation on
* @return {@code true} if an annotation of the specified {@code annotationType}
* is <em>directly present</em>
* @see java.lang.Class#getDeclaredAnnotations()
* @see java.lang.Class#getDeclaredAnnotation(Class)
* @see #isAnnotationInherited(Class, Class)
*/
public static boolean isAnnotationDeclaredLocally(Class<? extends Annotation> annotationType, Class<?> clazz) {
return MergedAnnotations.from(clazz).get(annotationType).isDirectlyPresent();
@ -638,7 +682,9 @@ public abstract class AnnotationUtils {
* is <em>present</em> and <em>inherited</em>
* @see Class#isAnnotationPresent(Class)
* @see #isAnnotationDeclaredLocally(Class, Class)
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/
@Deprecated
public static boolean isAnnotationInherited(Class<? extends Annotation> annotationType, Class<?> clazz) {
return MergedAnnotations.from(clazz, SearchStrategy.INHERITED_ANNOTATIONS)
.stream(annotationType)
@ -654,11 +700,27 @@ public abstract class AnnotationUtils {
* @param metaAnnotationType the type of meta-annotation to search for
* @return {@code true} if such an annotation is meta-present
* @since 4.2.1
* @deprecated as of 5.2 since it is superseded by the {@link MergedAnnotations} API
*/
@Deprecated
public static boolean isAnnotationMetaPresent(Class<? extends Annotation> annotationType,
@Nullable Class<? extends Annotation> metaAnnotationType) {
return MergedAnnotations.from(annotationType, SearchStrategy.EXHAUSTIVE).isPresent(annotationType);
if (metaAnnotationType == null) {
return false;
}
// Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType) ||
AnnotationFilter.PLAIN.matches(metaAnnotationType)) {
return annotationType.isAnnotationPresent(metaAnnotationType);
}
// Shortcut: no searchable annotations to be found on plain Java classes and core Spring types...
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotationType)) {
return false;
}
// Exhaustive retrieval of merged annotations...
return (MergedAnnotations.from(
annotationType, SearchStrategy.INHERITED_ANNOTATIONS).isPresent(metaAnnotationType));
}
/**
@ -731,8 +793,8 @@ public abstract class AnnotationUtils {
* corresponding attribute values as values (never {@code null})
* @see #getAnnotationAttributes(Annotation, boolean, boolean)
*/
public static Map<String, Object> getAnnotationAttributes(Annotation annotation,
boolean classValuesAsString) {
public static Map<String, Object> getAnnotationAttributes(
Annotation annotation, boolean classValuesAsString) {
return getAnnotationAttributes(annotation, classValuesAsString, false);
}
@ -753,11 +815,10 @@ public abstract class AnnotationUtils {
* and corresponding attribute values as values (never {@code null})
* @since 3.1.1
*/
public static AnnotationAttributes getAnnotationAttributes(Annotation annotation,
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
public static AnnotationAttributes getAnnotationAttributes(
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
return getAnnotationAttributes(null, annotation, classValuesAsString,
nestedAnnotationsAsMap);
return getAnnotationAttributes(null, annotation, classValuesAsString, nestedAnnotationsAsMap);
}
/**
@ -801,22 +862,14 @@ public abstract class AnnotationUtils {
@Nullable AnnotatedElement annotatedElement, Annotation annotation,
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
ClassLoader classLoader = annotation.annotationType().getClassLoader();
MapValues[] mapValues = MapValues.of(classValuesAsString, nestedAnnotationsAsMap);
return MergedAnnotation.from(annotatedElement, annotation)
.withNonMergedAttributes()
.asMap(getAnnotationAttributesFactory(classLoader), mapValues);
.asMap(getAnnotationAttributesFactory(), mapValues);
}
@SuppressWarnings("unchecked")
private static Function<MergedAnnotation<?>, AnnotationAttributes> getAnnotationAttributesFactory(
ClassLoader classLoader) {
return annotation -> {
Class<Annotation> type = (Class<Annotation>) ClassUtils
.resolveClassName(annotation.getType(), classLoader);
return new AnnotationAttributes(type, true);
};
private static Function<MergedAnnotation<?>, AnnotationAttributes> getAnnotationAttributesFactory() {
return (annotation -> new AnnotationAttributes(annotation.getType(), true));
}
/**
@ -861,9 +914,8 @@ public abstract class AnnotationUtils {
}
else {
// If we have nested annotations, we need them as nested maps
AnnotationAttributes attributes = MergedAnnotation.from(annotationType).asMap(
getAnnotationAttributesFactory(annotationType.getClassLoader()),
MapValues.ANNOTATION_TO_MAP);
AnnotationAttributes attributes = MergedAnnotation.from(annotationType)
.asMap(getAnnotationAttributesFactory(), MapValues.ANNOTATION_TO_MAP);
for (Map.Entry<String, Object> element : attributes.entrySet()) {
result.put(element.getKey(), new DefaultValueHolder(element.getValue()));
}
@ -927,18 +979,14 @@ public abstract class AnnotationUtils {
}
}
private static Object getAttributeValueForMirrorResolution(Method attribute,
Object attributes) {
private static Object getAttributeValueForMirrorResolution(Method attribute, Object attributes) {
Object result = ((AnnotationAttributes) attributes).get(attribute.getName());
return result instanceof DefaultValueHolder ?
((DefaultValueHolder) result).defaultValue :
result;
return (result instanceof DefaultValueHolder ? ((DefaultValueHolder) result).defaultValue : result);
}
@Nullable
private static Object adaptValue(@Nullable Object annotatedElement,
@Nullable Object value, boolean classValuesAsString) {
private static Object adaptValue(
@Nullable Object annotatedElement, @Nullable Object value, boolean classValuesAsString) {
if (classValuesAsString) {
if (value instanceof Class) {
@ -962,8 +1010,7 @@ public abstract class AnnotationUtils {
Annotation[] synthesized = (Annotation[]) Array.newInstance(
annotations.getClass().getComponentType(), annotations.length);
for (int i = 0; i < annotations.length; i++) {
synthesized[i] = MergedAnnotation.from(annotatedElement, annotations[i])
.synthesize();
synthesized[i] = MergedAnnotation.from(annotatedElement, annotations[i]).synthesize();
}
return synthesized;
}
@ -994,9 +1041,7 @@ public abstract class AnnotationUtils {
* @see #getValue(Annotation)
*/
@Nullable
public static Object getValue(@Nullable Annotation annotation,
@Nullable String attributeName) {
public static Object getValue(@Nullable Annotation annotation, @Nullable String attributeName) {
if (annotation == null || !StringUtils.hasText(attributeName)) {
return null;
}
@ -1047,8 +1092,7 @@ public abstract class AnnotationUtils {
* @param ex the exception that we encountered
* @see #rethrowAnnotationConfigurationException
*/
private static void handleIntrospectionFailure(@Nullable AnnotatedElement element,
Throwable ex) {
private static void handleIntrospectionFailure(@Nullable AnnotatedElement element, Throwable ex) {
rethrowAnnotationConfigurationException(ex);
IntrospectionFailureLogger logger = IntrospectionFailureLogger.INFO;
boolean meta = false;
@ -1085,12 +1129,8 @@ public abstract class AnnotationUtils {
* @see #getDefaultValue(Class, String)
*/
@Nullable
public static Object getDefaultValue(@Nullable Annotation annotation,
@Nullable String attributeName) {
return annotation != null ?
getDefaultValue(annotation.annotationType(), attributeName) :
null;
public static Object getDefaultValue(@Nullable Annotation annotation, @Nullable String attributeName) {
return (annotation != null ? getDefaultValue(annotation.annotationType(), attributeName) : null);
}
/**
@ -1115,14 +1155,12 @@ public abstract class AnnotationUtils {
*/
@Nullable
public static Object getDefaultValue(
@Nullable Class<? extends Annotation> annotationType,
@Nullable String attributeName) {
@Nullable Class<? extends Annotation> annotationType, @Nullable String attributeName) {
if (annotationType == null || !StringUtils.hasText(attributeName)) {
return null;
}
return MergedAnnotation.from(annotationType)
.getDefaultValue(attributeName).orElse(null);
return MergedAnnotation.from(annotationType).getDefaultValue(attributeName).orElse(null);
}
/**
@ -1145,7 +1183,29 @@ public abstract class AnnotationUtils {
public static <A extends Annotation> A synthesizeAnnotation(
A annotation, @Nullable AnnotatedElement annotatedElement) {
return synthesizeAnnotation(annotation, (Object) annotatedElement);
if (annotation instanceof SynthesizedAnnotation || AnnotationFilter.PLAIN.matches(annotation)) {
return annotation;
}
return MergedAnnotation.from(annotatedElement, annotation).synthesize();
}
/**
* <em>Synthesize</em> an annotation from its default attributes values.
* <p>This method simply delegates to
* {@link #synthesizeAnnotation(Map, Class, AnnotatedElement)},
* supplying an empty map for the source attribute values and {@code null}
* for the {@link AnnotatedElement}.
* @param annotationType the type of annotation to synthesize
* @return the synthesized annotation
* @throws IllegalArgumentException if a required attribute is missing
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
* @since 4.2
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
*/
public static <A extends Annotation> A synthesizeAnnotation(Class<A> annotationType) {
return synthesizeAnnotation(Collections.emptyMap(), annotationType, null);
}
/**
@ -1177,38 +1237,17 @@ public abstract class AnnotationUtils {
* @see #getAnnotationAttributes(AnnotatedElement, Annotation)
* @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
*/
public static <A extends Annotation> A synthesizeAnnotation(
Map<String, Object> attributes, Class<A> annotationType,
@Nullable AnnotatedElement annotatedElement) {
public static <A extends Annotation> A synthesizeAnnotation(Map<String, Object> attributes,
Class<A> annotationType, @Nullable AnnotatedElement annotatedElement) {
try {
return MergedAnnotation.from(annotatedElement, annotationType, attributes)
.synthesize();
return MergedAnnotation.from(annotatedElement, annotationType, attributes).synthesize();
}
catch (NoSuchElementException | IllegalStateException ex) {
throw new IllegalArgumentException(ex);
}
}
/**
* <em>Synthesize</em> an annotation from its default attributes values.
* <p>This method simply delegates to
* {@link #synthesizeAnnotation(Map, Class, AnnotatedElement)},
* supplying an empty map for the source attribute values and {@code null}
* for the {@link AnnotatedElement}.
* @param annotationType the type of annotation to synthesize
* @return the synthesized annotation
* @throws IllegalArgumentException if a required attribute is missing
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
* @since 4.2
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
*/
public static <A extends Annotation> A synthesizeAnnotation(Class<A> annotationType) {
return synthesizeAnnotation(Collections.emptyMap(), annotationType, null);
}
/**
* <em>Synthesize</em> an array of annotations from the supplied array
* of {@code annotations} by creating a new array of the same size and
@ -1226,9 +1265,7 @@ public abstract class AnnotationUtils {
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
*/
static Annotation[] synthesizeAnnotationArray(Annotation[] annotations,
@Nullable Object annotatedElement) {
static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, AnnotatedElement annotatedElement) {
if (AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement)) {
return annotations;
}
@ -1240,16 +1277,6 @@ public abstract class AnnotationUtils {
return synthesized;
}
private static <A extends Annotation> A synthesizeAnnotation(A annotation,
@Nullable Object annotatedElement) {
if (annotation instanceof SynthesizedAnnotation ||
AnnotationsScanner.hasPlainJavaAnnotationsOnly(annotatedElement) ||
AnnotationFilter.PLAIN.matches(annotation)) {
return annotation;
}
return MergedAnnotation.from(annotatedElement, annotation).synthesize();
}
/**
* Clear the internal annotation metadata cache.
* @since 4.3.15
@ -1259,11 +1286,6 @@ public abstract class AnnotationUtils {
AnnotationsScanner.clearCache();
}
private static <A extends Annotation> boolean isSingleLevelPresent(
MergedAnnotation<A> mergedAnnotation) {
int depth = mergedAnnotation.getDepth();
return depth == 0 || depth == 1;
}
/**
* Internal holder used to wrap default values.
@ -1280,7 +1302,6 @@ public abstract class AnnotationUtils {
public String toString() {
return "*" + this.defaultValue;
}
}
}

View File

@ -51,6 +51,7 @@ import org.springframework.lang.Nullable;
* synthesized} back into an actual {@link java.lang.annotation.Annotation}.
*
* @author Phillip Webb
* @author Juergen Hoeller
* @since 5.2
* @param <A> the annotation type
* @see MergedAnnotations
@ -65,10 +66,10 @@ public interface MergedAnnotation<A extends Annotation> {
/**
* Return the class name of the actual annotation type.
* Return the {@code Class} reference for the actual annotation type.
* @return the annotation type
*/
String getType();
Class<A> getType();
/**
* Return if the annotation is present on the source. Considers
@ -323,8 +324,7 @@ public interface MergedAnnotation<A extends Annotation> {
* @return the value as a enum
* @throws NoSuchElementException if there is no matching attribute
*/
<E extends Enum<E>> E getEnum(String attributeName, Class<E> type)
throws NoSuchElementException;
<E extends Enum<E>> E getEnum(String attributeName, Class<E> type) throws NoSuchElementException;
/**
* Return a required enum array attribute value from the annotation.
@ -333,8 +333,7 @@ public interface MergedAnnotation<A extends Annotation> {
* @return the value as a enum array
* @throws NoSuchElementException if there is no matching attribute
*/
<E extends Enum<E>> E[] getEnumArray(String attributeName, Class<E> type)
throws NoSuchElementException;
<E extends Enum<E>> E[] getEnumArray(String attributeName, Class<E> type) throws NoSuchElementException;
/**
* Return a required annotation attribute value from the annotation.
@ -353,8 +352,8 @@ public interface MergedAnnotation<A extends Annotation> {
* @return the value as a {@link MergedAnnotation} array
* @throws NoSuchElementException if there is no matching attribute
*/
<T extends Annotation> MergedAnnotation<T>[] getAnnotationArray(String attributeName,
Class<T> type) throws NoSuchElementException;
<T extends Annotation> MergedAnnotation<T>[] getAnnotationArray(String attributeName, Class<T> type)
throws NoSuchElementException;
/**
* Return an optional attribute value from the annotation.
@ -422,24 +421,21 @@ public interface MergedAnnotation<A extends Annotation> {
MergedAnnotation<A> withNonMergedAttributes();
/**
* Return an immutable {@link Map} that contains all the annotation
* attributes. The {@link MapValues} options may be used to change the way
* that values are added.
* Return an immutable {@link Map} that contains all the annotation attributes.
* The {@link MapValues} options may be used to change the way that values are added.
* @param options map value options
* @return a map containing the attributes and values
* @return an immutable map containing the attributes and values
*/
Map<String, Object> asMap(MapValues... options);
/**
* Return a {@link Map} of the supplied type that contains all the annotation
* attributes. The {@link MapValues} options may be used to change the way
* that values are added.
* @param factory a map factory or {@code null} to return an immutable map.
* Return a {@link Map} of the given type that contains all the annotation attributes.
* The {@link MapValues} options may be used to change the way that values are added.
* @param factory a map factory
* @param options map value options
* @return a map containing the attributes and values
*/
<T extends Map<String, Object>> T asMap(
@Nullable Function<MergedAnnotation<?>, T> factory, MapValues... options);
<T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, MapValues... options);
/**
* Return a type-safe synthesized version of this annotation that can be
@ -460,8 +456,7 @@ public interface MergedAnnotation<A extends Annotation> {
* @throws NoSuchElementException on a missing annotation
* @see MergedAnnotationPredicates
*/
Optional<A> synthesize(@Nullable Predicate<? super MergedAnnotation<A>> condition)
throws NoSuchElementException;
Optional<A> synthesize(Predicate<? super MergedAnnotation<A>> condition) throws NoSuchElementException;
/**
@ -511,10 +506,9 @@ public interface MergedAnnotation<A extends Annotation> {
* Create a new {@link MergedAnnotations} instance from the specified
* annotation type and attributes maps.
* @param annotationType the annotation type
* @param attributes the annotation attributes or {@code null} if just
* default values should be used
* @return a {@link MergedAnnotation} instance for the annotation and
* attributes
* @param attributes the annotation attributes or {@code null} if just default
* values should be used
* @return a {@link MergedAnnotation} instance for the annotation and attributes
* @see #from(AnnotatedElement, Class, Map)
*/
static <A extends Annotation> MergedAnnotation<A> from(
@ -530,10 +524,9 @@ public interface MergedAnnotation<A extends Annotation> {
* information and logging. It does not need to <em>actually</em> contain
* the specified annotations and it will not be searched.
* @param annotationType the annotation type
* @param attributes the annotation attributes or {@code null} if just
* default values should be used
* @return a {@link MergedAnnotation} instance for the annotation and
* attributes
* @param attributes the annotation attributes or {@code null} if just default
* values should be used
* @return a {@link MergedAnnotation} instance for the annotation and attributes
*/
static <A extends Annotation> MergedAnnotation<A> from(
@Nullable AnnotatedElement source, Class<A> annotationType, @Nullable Map<String, ?> attributes) {

View File

@ -17,7 +17,6 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@ -50,7 +49,7 @@ public abstract class MergedAnnotationPredicates {
* @return a {@link Predicate} to test the annotation type
*/
public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(String... typeNames) {
return annotation -> ObjectUtils.containsElement(typeNames, annotation.getType());
return annotation -> ObjectUtils.containsElement(typeNames, annotation.getType().getName());
}
/**
@ -62,7 +61,7 @@ public abstract class MergedAnnotationPredicates {
* @return a {@link Predicate} to test the annotation type
*/
public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(Class<?>... types) {
return annotation -> Arrays.stream(types).anyMatch(type -> type.getName().equals(annotation.getType()));
return annotation -> ObjectUtils.containsElement(types, annotation.getType());
}
/**
@ -76,7 +75,7 @@ public abstract class MergedAnnotationPredicates {
public static <A extends Annotation> Predicate<MergedAnnotation<? extends A>> typeIn(Collection<?> types) {
return annotation -> types.stream()
.map(type -> type instanceof Class ? ((Class<?>) type).getName() : type.toString())
.anyMatch(typeName -> typeName.equals(annotation.getType()));
.anyMatch(typeName -> typeName.equals(annotation.getType().getName()));
}
/**
@ -141,7 +140,6 @@ public abstract class MergedAnnotationPredicates {
return ObjectUtils.nullSafeEquals(value, this.lastValue);
}
}
@ -165,7 +163,6 @@ public abstract class MergedAnnotationPredicates {
K key = this.keyExtractor.apply(annotation);
return this.seen.add(key);
}
}
}

View File

@ -31,6 +31,7 @@ import org.springframework.lang.Nullable;
* {@link MergedAnnotation#missing()}.
*
* @author Phillip Webb
* @author Juergen Hoeller
* @since 5.2
* @param <A> the annotation type
*/
@ -44,7 +45,7 @@ final class MissingMergedAnnotation<A extends Annotation> extends AbstractMerged
@Override
public String getType() {
public Class<A> getType() {
throw new NoSuchElementException("Unable to get type for missing annotation");
}
@ -112,13 +113,8 @@ final class MissingMergedAnnotation<A extends Annotation> extends AbstractMerged
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T extends Map<String, Object>> T asMap(
@Nullable Function<MergedAnnotation<?>, T> factory, MapValues... options) {
if (factory != null) {
return factory.apply(this);
}
return (T) ((Map) Collections.emptyMap());
public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, MapValues... options) {
return factory.apply(this);
}
@Override

View File

@ -176,7 +176,7 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
Class<?> type = ClassUtils.resolvePrimitiveIfNecessary(method.getReturnType());
return this.annotation.getValue(name, type).orElseThrow(
() -> new NoSuchElementException("No value found for attribute named '" + name +
"' in merged annotation " + this.annotation.getType()));
"' in merged annotation " + this.annotation.getType().getName()));
}
@SuppressWarnings("unchecked")

View File

@ -49,7 +49,7 @@ import org.springframework.util.ReflectionUtils;
* return type, namely:
*
* <p><table border="1">
* <tr><th >Return Type</th><th >Extracted Type</th></tr>
* <tr><th>Return Type</th><th >Extracted Type</th></tr>
* <tr><td>Class</td><td>Class or String</td></tr>
* <tr><td>Class[]</td><td>Class[] or String[]</td></tr>
* <tr><td>Annotation</td><td>Annotation, Map or Object compatible with the value
@ -60,6 +60,7 @@ import org.springframework.util.ReflectionUtils;
* </table>
*
* @author Phillip Webb
* @author Juergen Hoeller
* @since 5.2
* @param <A> the annotation type
* @see TypeMappedAnnotations
@ -91,17 +92,16 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
private String string;
private TypeMappedAnnotation(AnnotationTypeMapping mapping,
@Nullable Object source, @Nullable Object rootAttributes,
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex) {
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object source,
@Nullable Object rootAttributes, BiFunction<Method, Object, Object> valueExtractor,
int aggregateIndex) {
this(mapping, source, rootAttributes, valueExtractor, aggregateIndex, null);
}
private TypeMappedAnnotation(AnnotationTypeMapping mapping,
@Nullable Object source, @Nullable Object rootAttributes,
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex,
@Nullable int[] resolvedRootMirrors) {
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object source,
@Nullable Object rootAttributes, BiFunction<Method, Object, Object> valueExtractor,
int aggregateIndex, @Nullable int[] resolvedRootMirrors) {
this.source = source;
this.rootAttributes = rootAttributes;
@ -110,18 +110,15 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
this.aggregateIndex = aggregateIndex;
this.useMergedValues = true;
this.attributeFilter = null;
this.resolvedRootMirrors = resolvedRootMirrors != null ? resolvedRootMirrors
: mapping.getRoot().getMirrorSets().resolve(source, rootAttributes,
this.valueExtractor);
this.resolvedMirrors = getDepth() == 0 ? this.resolvedRootMirrors
: mapping.getMirrorSets().resolve(source, this,
this::getValueForMirrorResolution);
this.resolvedRootMirrors = (resolvedRootMirrors != null ? resolvedRootMirrors :
mapping.getRoot().getMirrorSets().resolve(source, rootAttributes, this.valueExtractor));
this.resolvedMirrors = (getDepth() == 0 ? this.resolvedRootMirrors :
mapping.getMirrorSets().resolve(source, this, this::getValueForMirrorResolution));
}
private TypeMappedAnnotation(AnnotationTypeMapping mapping,
@Nullable Object source, @Nullable Object rootAnnotation,
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex,
boolean useMergedValues, @Nullable Predicate<String> attributeFilter,
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object source,
@Nullable Object rootAnnotation, BiFunction<Method, Object, Object> valueExtractor,
int aggregateIndex, boolean useMergedValues, @Nullable Predicate<String> attributeFilter,
int[] resolvedRootMirrors, int[] resolvedMirrors) {
this.source = source;
@ -136,16 +133,10 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
}
@Nullable
private Object getValueForMirrorResolution(Method attribute, Object annotation) {
int attributeIndex = this.mapping.getAttributes().indexOf(attribute);
boolean valueAttribute = VALUE.equals(attribute.getName());
return getValue(attributeIndex, !valueAttribute, true);
}
@Override
public String getType() {
return getAnnotationType().getName();
@SuppressWarnings("unchecked")
public Class<A> getType() {
return (Class<A>) this.mapping.getAnnotationType();
}
@Override
@ -184,8 +175,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
public boolean hasDefaultValue(String attributeName) {
int attributeIndex = getAttributeIndex(attributeName, true);
Object value = getValue(attributeIndex, true, false);
return value == null || this.mapping.isEquivalentToDefaultValue(attributeIndex, value,
this.valueExtractor);
return (value == null || this.mapping.isEquivalentToDefaultValue(attributeIndex, value, this.valueExtractor));
}
@Override
@ -197,8 +187,8 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
Method attribute = this.mapping.getAttributes().get(attributeIndex);
Assert.notNull(type, "Type must not be null");
Assert.isAssignable(type, attribute.getReturnType(),
"Attribute " + attributeName + " type mismatch:");
return (MergedAnnotation<T>) getRequiredValue(attributeIndex, Object.class);
() -> "Attribute " + attributeName + " type mismatch:");
return (MergedAnnotation<T>) getRequiredValue(attributeIndex);
}
@Override
@ -212,7 +202,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
Assert.notNull(type, "Type must not be null");
Assert.notNull(componentType, () -> "Attribute " + attributeName + " is not an array");
Assert.isAssignable(type, componentType, () -> "Attribute " + attributeName + " component type mismatch:");
return (MergedAnnotation<T>[]) getRequiredValue(attributeIndex, Object.class);
return (MergedAnnotation<T>[]) getRequiredValue(attributeIndex);
}
@Override
@ -243,55 +233,48 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
}
@Override
@SuppressWarnings("unchecked")
public <T extends Map<String, Object>> T asMap(
@Nullable Function<MergedAnnotation<?>, T> factory, MapValues... options) {
public Map<String, Object> asMap(MapValues... options) {
return Collections.unmodifiableMap(asMap(mergedAnnotation -> new LinkedHashMap<>(), options));
}
T map = (factory != null ? factory.apply(this) : (T) new LinkedHashMap<String, Object>());
@Override
public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, MapValues... options) {
T map = factory.apply(this);
Assert.state(map != null, "Factory used to create MergedAnnotation Map must not return null");
AttributeMethods attributes = this.mapping.getAttributes();
for (int i = 0; i < attributes.size(); i++) {
Method attribute = attributes.get(i);
Object value = isFiltered(attribute.getName()) ?
null :
getValue(i, getTypeForMapOptions(attribute, options));
Object value = (isFiltered(attribute.getName()) ? null :
getValue(i, getTypeForMapOptions(attribute, options)));
if (value != null) {
map.put(attribute.getName(),
adaptValueForMapOptions(attribute, value, factory, options));
adaptValueForMapOptions(attribute, value, map.getClass(), factory, options));
}
}
return (factory != null) ? map : (T) Collections.unmodifiableMap(map);
return map;
}
private Class<?> getTypeForMapOptions(Method attribute, MapValues[] options) {
Class<?> attributeType = attribute.getReturnType();
Class<?> componentType = attributeType.isArray() ?
attributeType.getComponentType() :
attributeType;
Class<?> componentType = (attributeType.isArray() ? attributeType.getComponentType() : attributeType);
if (MapValues.CLASS_TO_STRING.isIn(options) && componentType == Class.class) {
return attributeType.isArray() ? String[].class : String.class;
return (attributeType.isArray() ? String[].class : String.class);
}
return Object.class;
}
private <T extends Map<String, Object>> Object adaptValueForMapOptions(
Method attribute, Object value,
@Nullable Function<MergedAnnotation<?>, T> factory, MapValues[] options) {
private <T extends Map<String, Object>> Object adaptValueForMapOptions(Method attribute, Object value,
Class<?> mapType, Function<MergedAnnotation<?>, T> factory, MapValues[] options) {
if (value instanceof MergedAnnotation) {
MergedAnnotation<?> annotation = (MergedAnnotation<?>) value;
return MapValues.ANNOTATION_TO_MAP.isIn(options) ?
annotation.asMap(factory, options) :
annotation.synthesize();
return (MapValues.ANNOTATION_TO_MAP.isIn(options) ?
annotation.asMap(factory, options) : annotation.synthesize());
}
if (value instanceof MergedAnnotation[]) {
MergedAnnotation<?>[] annotations = (MergedAnnotation<?>[]) value;
if (MapValues.ANNOTATION_TO_MAP.isIn(options)) {
Class<?> componentType = Map.class;
if (factory != null) {
componentType = factory.apply(this).getClass();
}
Object result = Array.newInstance(componentType, annotations.length);
Object result = Array.newInstance(mapType, annotations.length);
for (int i = 0; i < annotations.length; i++) {
Array.set(result, i, annotations[i].asMap(factory, options));
}
@ -309,7 +292,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
@Override
protected A createSynthesized() {
return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getAnnotationType());
return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getType());
}
@Override
@ -318,7 +301,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
if (string == null) {
StringBuilder builder = new StringBuilder();
builder.append("@");
builder.append(getType());
builder.append(getType().getName());
builder.append("(");
for (int i = 0; i < this.mapping.getAttributes().size(); i++) {
Method attribute = this.mapping.getAttributes().get(i);
@ -360,8 +343,8 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
return (attributeIndex != -1 ? getValue(attributeIndex, type) : null);
}
protected final <T> T getRequiredValue(int attributeIndex, Class<T> type) {
T value = getValue(attributeIndex, type);
private final Object getRequiredValue(int attributeIndex) {
Object value = getValue(attributeIndex, Object.class);
if (value == null) {
throw new NoSuchElementException("No element at attribute index " + attributeIndex);
}
@ -379,9 +362,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
}
@Nullable
private Object getValue(int attributeIndex, boolean useConventionMapping,
boolean forMirrorResolution) {
private Object getValue(int attributeIndex, boolean useConventionMapping, boolean forMirrorResolution) {
AnnotationTypeMapping mapping = this.mapping;
if (this.useMergedValues) {
int mappedIndex = this.mapping.getAliasMapping(attributeIndex);
@ -419,6 +400,13 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
return ReflectionUtils.invokeMethod(attribute, this.mapping.getAnnotation());
}
@Nullable
private Object getValueForMirrorResolution(Method attribute, Object annotation) {
int attributeIndex = this.mapping.getAttributes().indexOf(attribute);
boolean valueAttribute = VALUE.equals(attribute.getName());
return getValue(attributeIndex, !valueAttribute, true);
}
@SuppressWarnings("unchecked")
@Nullable
private <T> T adapt(Method attribute, @Nullable Object value, Class<T> type) {
@ -485,21 +473,16 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
}
if (!attributeType.isInstance(value)) {
throw new IllegalStateException("Attribute '" + attribute.getName() +
"' in annotation " + getType() + " should be compatible with " +
"' in annotation " + getType().getName() + " should be compatible with " +
attributeType.getName() + " but a " + value.getClass().getName() +
" value was returned");
}
return value;
}
private MergedAnnotation<?> adaptToMergedAnnotation(Object value,
Class<? extends Annotation> annotationType) {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(annotationType);
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(
annotationType, filter).get(0);
return new TypeMappedAnnotation<>(mapping, this.source, value,
this.getValueExtractor(value), this.aggregateIndex);
private MergedAnnotation<?> adaptToMergedAnnotation(Object value, Class<? extends Annotation> annotationType) {
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
return new TypeMappedAnnotation<>(mapping, this.source, value, getValueExtractor(value), this.aggregateIndex);
}
private BiFunction<Method, Object, Object> getValueExtractor(Object value) {
@ -529,7 +512,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
this.mapping.getAttributes().indexOf(attributeName));
if (attributeIndex == -1 && required) {
throw new NoSuchElementException("No attribute named '" + attributeName +
"' present in merged annotation " + getType());
"' present in merged annotation " + getType().getName());
}
return attributeIndex;
}
@ -541,10 +524,6 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
return false;
}
@SuppressWarnings("unchecked")
private Class<A> getAnnotationType() {
return (Class<A>) this.mapping.getAnnotationType();
}
static <A extends Annotation> MergedAnnotation<A> from(@Nullable Object source, A annotation) {
Assert.notNull(annotation, "Annotation must not be null");
@ -557,8 +536,8 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
Assert.notNull(annotationType, "Annotation type must not be null");
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotationType);
return new TypeMappedAnnotation<>(mappings.get(0), source, attributes,
TypeMappedAnnotation::extractFromMap, 0);
return new TypeMappedAnnotation<>(
mappings.get(0), source, attributes, TypeMappedAnnotation::extractFromMap, 0);
}
@Nullable

View File

@ -23,7 +23,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.annotation.Resource;
@ -34,9 +36,9 @@ import org.junit.Test;
import org.junit.internal.ArrayComparisonFailure;
import org.junit.rules.ExpectedException;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Indexed;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import static java.util.Arrays.*;
@ -631,7 +633,6 @@ public class AnnotatedElementUtilsTests {
}
private AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, Class<? extends Annotation> annotationType) {
Assert.notNull(annotationType, "annotationType must not be null");
return AnnotatedElementUtils.findMergedAnnotationAttributes(element, annotationType.getName(), false, false);
}
@ -699,6 +700,15 @@ public class AnnotatedElementUtilsTests {
assertArrayEquals("path attribute: ", asArray("/test"), webMapping.path());
}
@Test
public void javaLangAnnotationTypeViaFindMergedAnnotation() throws Exception {
Constructor<?> deprecatedCtor = Date.class.getConstructor(String.class);
assertEquals(deprecatedCtor.getAnnotation(Deprecated.class),
findMergedAnnotation(deprecatedCtor, Deprecated.class));
assertEquals(Date.class.getAnnotation(Deprecated.class),
findMergedAnnotation(Date.class, Deprecated.class));
}
@Test
public void javaxAnnotationTypeViaFindMergedAnnotation() throws Exception {
assertEquals(ResourceHolder.class.getAnnotation(Resource.class),
@ -707,17 +717,26 @@ public class AnnotatedElementUtilsTests {
findMergedAnnotation(SpringAppConfigClass.class, Resource.class));
}
@Test
public void nullableAnnotationTypeViaFindMergedAnnotation() throws Exception {
Method method = TransactionalServiceImpl.class.getMethod("doIt");
assertEquals(method.getAnnotation(Resource.class),
findMergedAnnotation(method, Resource.class));
assertEquals(method.getAnnotation(Resource.class),
findMergedAnnotation(method, Resource.class));
}
@Test
public void getAllMergedAnnotationsOnClassWithInterface() throws Exception {
Method m = TransactionalServiceImpl.class.getMethod("doIt");
Set<Transactional> allMergedAnnotations = getAllMergedAnnotations(m, Transactional.class);
Method method = TransactionalServiceImpl.class.getMethod("doIt");
Set<Transactional> allMergedAnnotations = getAllMergedAnnotations(method, Transactional.class);
assertTrue(allMergedAnnotations.isEmpty());
}
@Test
public void findAllMergedAnnotationsOnClassWithInterface() throws Exception {
Method m = TransactionalServiceImpl.class.getMethod("doIt");
Set<Transactional> allMergedAnnotations = findAllMergedAnnotations(m, Transactional.class);
Method method = TransactionalServiceImpl.class.getMethod("doIt");
Set<Transactional> allMergedAnnotations = findAllMergedAnnotations(method, Transactional.class);
assertEquals(1, allMergedAnnotations.size());
}
@ -1299,20 +1318,22 @@ public class AnnotatedElementUtilsTests {
interface TransactionalService {
@Transactional
void doIt();
@Nullable
Object doIt();
}
class TransactionalServiceImpl implements TransactionalService {
@Override
public void doIt() {
@Nullable
public Object doIt() {
return null;
}
}
@Deprecated
@ComponentScan
class ForAnnotationsClass {
}
}

View File

@ -99,39 +99,6 @@ public class AnnotationFilterTests {
assertThat(AnnotationFilter.NONE.matches(TestAnnotation.class)).isFalse();
}
@Test
public void pacakgesReturnsPackagesAnnotationFilter() {
assertThat(AnnotationFilter.packages("com.example")).isInstanceOf(PackagesAnnotationFilter.class);
}
@Test
public void mostAppropriateForCollectionReturnsPlainWhenPossible() {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(
Arrays.asList(TestAnnotation.class, OtherAnnotation.class));
assertThat(filter).isSameAs(AnnotationFilter.PLAIN);
}
@Test
public void mostAppropriateForCollectionWhenCantUsePlainReturnsNone() {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(Arrays.asList(
TestAnnotation.class, OtherAnnotation.class, Nullable.class));
assertThat(filter).isSameAs(AnnotationFilter.NONE);
}
@Test
public void mostAppropriateForArrayReturnsPlainWhenPossible() {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(
TestAnnotation.class, OtherAnnotation.class);
assertThat(filter).isSameAs(AnnotationFilter.PLAIN);
}
@Test
public void mostAppropriateForArrayWhenCantUsePlainReturnsNone() {
AnnotationFilter filter = AnnotationFilter.mostAppropriateFor(
TestAnnotation.class, OtherAnnotation.class, Nullable.class);
assertThat(filter).isSameAs(AnnotationFilter.NONE);
}
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation {

View File

@ -17,6 +17,7 @@
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
@ -36,7 +37,7 @@ import org.junit.rules.ExpectedException;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
import org.springframework.lang.Nullable;
import org.springframework.lang.NonNullApi;
import org.springframework.stereotype.Component;
import static java.util.Arrays.*;
@ -55,6 +56,7 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
* @author Phillip Webb
* @author Oleg Zhurakousky
*/
@SuppressWarnings("deprecation")
public class AnnotationUtilsTests {
@Rule
@ -396,7 +398,7 @@ public class AnnotationUtilsTests {
}
@Test
public void isAnnotationDeclaredLocallyForAllScenarios() throws Exception {
public void isAnnotationDeclaredLocallyForAllScenarios() {
// no class-level annotation
assertFalse(isAnnotationDeclaredLocally(Transactional.class, NonAnnotatedInterface.class));
assertFalse(isAnnotationDeclaredLocally(Transactional.class, NonAnnotatedClass.class));
@ -435,6 +437,12 @@ public class AnnotationUtilsTests {
assertFalse(isAnnotationInherited(Order.class, SubNonInheritedAnnotationClass.class));
}
@Test
public void isAnnotationMetaPresentForJavaLangType() {
assertTrue(isAnnotationMetaPresent(Order.class, Documented.class));
assertTrue(isAnnotationMetaPresent(NonNullApi.class, Documented.class));
}
@Test
public void getAnnotationAttributesWithoutAttributeAliases() {
Component component = WebController.class.getAnnotation(Component.class);
@ -540,6 +548,13 @@ public class AnnotationUtilsTests {
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class));
}
@Test
public void findRepeatableAnnotation() {
Repeatable repeatable = findAnnotation(MyRepeatable.class, Repeatable.class);
assertNotNull(repeatable);
assertEquals(MyRepeatableContainer.class, repeatable.value());
}
@Test
public void getRepeatableAnnotationsDeclaredOnMethod() throws Exception {
Method method = InterfaceWithRepeated.class.getMethod("foo");
@ -924,10 +939,8 @@ public class AnnotationUtilsTests {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(either(allOf(startsWith("Attributes map"),
containsString("returned null for required attribute 'text'"),
containsString("defined by annotation type ["
+ AnnotationWithoutDefaults.class.getName() + "]"))).or(
containsString(
"No value found for attribute named 'text' in merged annotation")));
containsString("defined by annotation type [" + AnnotationWithoutDefaults.class.getName() + "]"))).or(
containsString("No value found for attribute named 'text' in merged annotation")));
synthesizeAnnotation(attributes, AnnotationWithoutDefaults.class, null);
}
@ -936,8 +949,8 @@ public class AnnotationUtilsTests {
Map<String, Object> map = Collections.singletonMap(VALUE, 42L);
exception.expect(IllegalArgumentException.class);
exception.expectMessage(containsString(
"Attribute 'value' in annotation org.springframework.stereotype.Component "
+ "should be compatible with java.lang.String but a java.lang.Long value was returned"));
"Attribute 'value' in annotation org.springframework.stereotype.Component " +
"should be compatible with java.lang.String but a java.lang.Long value was returned"));
synthesizeAnnotation(map, Component.class, null);
}
@ -1055,12 +1068,6 @@ public class AnnotationUtilsTests {
void fromInterfaceImplementedByRoot();
}
public interface NullableAnnotatedInterface {
@Nullable
void fromInterfaceImplementedByRoot();
}
public static class Root implements AnnotatedInterface {
@Order(27)

View File

@ -77,18 +77,17 @@ public class MergedAnnotationsTests {
@Test
public void streamWhenFromClassWithMetaDepth1() {
Stream<String> names = MergedAnnotations.from(TransactionalComponent.class)
Stream<Class<?>> classes = MergedAnnotations.from(TransactionalComponent.class)
.stream().map(MergedAnnotation::getType);
assertThat(names).containsExactly(Transactional.class.getName(),
Component.class.getName(), Indexed.class.getName());
assertThat(classes).containsExactly(Transactional.class, Component.class, Indexed.class);
}
@Test
public void streamWhenFromClassWithMetaDepth2() {
Stream<String> names = MergedAnnotations.from(ComposedTransactionalComponent.class)
Stream<Class<?>> classes = MergedAnnotations.from(ComposedTransactionalComponent.class)
.stream().map(MergedAnnotation::getType);
assertThat(names).containsExactly(TransactionalComponent.class.getName(),
Transactional.class.getName(), Component.class.getName(), Indexed.class.getName());
assertThat(classes).containsExactly(TransactionalComponent.class,
Transactional.class, Component.class, Indexed.class);
}
@Test
@ -1149,17 +1148,16 @@ public class MergedAnnotationsTests {
@Test
public void getDirectWithoutAttributeAliases() {
MergedAnnotation<?> annotation = MergedAnnotations.from(WebController.class).get(
Component.class);
MergedAnnotation<?> annotation = MergedAnnotations.from(WebController.class)
.get(Component.class);
assertThat(annotation.getString("value")).isEqualTo("webController");
}
@Test
public void getDirectWithNestedAnnotations() {
MergedAnnotation<?> annotation = MergedAnnotations.from(
ComponentScanClass.class).get(ComponentScan.class);
MergedAnnotation<Filter>[] filters = annotation.getAnnotationArray(
"excludeFilters", Filter.class);
MergedAnnotation<?> annotation = MergedAnnotations.from(ComponentScanClass.class)
.get(ComponentScan.class);
MergedAnnotation<Filter>[] filters = annotation.getAnnotationArray("excludeFilters", Filter.class);
assertThat(Arrays.stream(filters).map(
filter -> filter.getString("pattern"))).containsExactly("*Foo", "*Bar");
}
@ -1167,8 +1165,7 @@ public class MergedAnnotationsTests {
@Test
public void getDirectWithAttributeAliases1() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
MergedAnnotation<?> annotation = MergedAnnotations.from(method).get(
RequestMapping.class);
MergedAnnotation<?> annotation = MergedAnnotations.from(method).get(RequestMapping.class);
assertThat(annotation.getString("name")).isEqualTo("foo");
assertThat(annotation.getStringArray("value")).containsExactly("/test");
assertThat(annotation.getStringArray("path")).containsExactly("/test");
@ -1177,8 +1174,7 @@ public class MergedAnnotationsTests {
@Test
public void getDirectWithAttributeAliases2() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithPathAttribute");
MergedAnnotation<?> annotation = MergedAnnotations.from(method).get(
RequestMapping.class);
MergedAnnotation<?> annotation = MergedAnnotations.from(method).get(RequestMapping.class);
assertThat(annotation.getString("name")).isEqualTo("bar");
assertThat(annotation.getStringArray("value")).containsExactly("/test");
assertThat(annotation.getStringArray("path")).containsExactly("/test");
@ -1186,8 +1182,7 @@ public class MergedAnnotationsTests {
@Test
public void getDirectWithAttributeAliasesWithDifferentValues() throws Exception {
Method method = WebController.class.getMethod(
"handleMappedWithDifferentPathAndValueAttributes");
Method method = WebController.class.getMethod("handleMappedWithDifferentPathAndValueAttributes");
assertThatExceptionOfType(AnnotationConfigurationException.class).isThrownBy(
() -> MergedAnnotations.from(method).get(
RequestMapping.class)).withMessageContaining(
@ -1197,10 +1192,9 @@ public class MergedAnnotationsTests {
@Test
public void getValueFromAnnotation() throws Exception {
Method method = TransactionalStringGeneric.class.getMethod("something",
Object.class);
MergedAnnotation<?> annotation = MergedAnnotations.from(method,
SearchStrategy.EXHAUSTIVE).get(Order.class);
Method method = TransactionalStringGeneric.class.getMethod("something", Object.class);
MergedAnnotation<?> annotation = MergedAnnotations.from(method, SearchStrategy.EXHAUSTIVE)
.get(Order.class);
assertThat(annotation.getInt("value")).isEqualTo(1);
}
@ -1210,21 +1204,17 @@ public class MergedAnnotationsTests {
assertThat(declaredAnnotations).hasSize(1);
Annotation annotation = declaredAnnotations[0];
MergedAnnotation<Annotation> mergedAnnotation = MergedAnnotation.from(annotation);
assertThat(mergedAnnotation.getType()).contains("NonPublicAnnotation");
assertThat(
mergedAnnotation.synthesize().annotationType().getSimpleName()).isEqualTo(
"NonPublicAnnotation");
assertThat(mergedAnnotation.getType().getSimpleName()).isEqualTo("NonPublicAnnotation");
assertThat(mergedAnnotation.synthesize().annotationType().getSimpleName()).isEqualTo("NonPublicAnnotation");
assertThat(mergedAnnotation.getInt("value")).isEqualTo(42);
}
@Test
public void getDefaultValueFromAnnotation() throws Exception {
Method method = TransactionalStringGeneric.class.getMethod("something",
Object.class);
MergedAnnotation<Order> annotation = MergedAnnotations.from(method,
SearchStrategy.EXHAUSTIVE).get(Order.class);
assertThat(annotation.getDefaultValue("value")).contains(
Ordered.LOWEST_PRECEDENCE);
Method method = TransactionalStringGeneric.class.getMethod("something", Object.class);
MergedAnnotation<Order> annotation = MergedAnnotations.from(method, SearchStrategy.EXHAUSTIVE)
.get(Order.class);
assertThat(annotation.getDefaultValue("value")).contains(Ordered.LOWEST_PRECEDENCE);
}
@Test
@ -1233,7 +1223,7 @@ public class MergedAnnotationsTests {
assertThat(declaredAnnotations).hasSize(1);
Annotation declaredAnnotation = declaredAnnotations[0];
MergedAnnotation<?> annotation = MergedAnnotation.from(declaredAnnotation);
assertThat(annotation.getType()).isEqualTo(
assertThat(annotation.getType().getName()).isEqualTo(
"org.springframework.core.annotation.subpackage.NonPublicAnnotation");
assertThat(annotation.getDefaultValue("value")).contains(-1);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -78,7 +78,6 @@ public abstract class MetaAnnotationUtils {
* @param annotationType the type of annotation to look for
* @return the corresponding annotation descriptor if the annotation was found;
* otherwise {@code null}
* @see AnnotationUtils#findAnnotationDeclaringClass(Class, Class)
* @see #findAnnotationDescriptorForTypes(Class, Class...)
*/
@Nullable
@ -164,7 +163,6 @@ public abstract class MetaAnnotationUtils {
* @param annotationTypes the types of annotations to look for
* @return the corresponding annotation descriptor if one of the annotations
* was found; otherwise {@code null}
* @see AnnotationUtils#findAnnotationDeclaringClassForTypes(java.util.List, Class)
* @see #findAnnotationDescriptor(Class, Class)
*/
@SuppressWarnings("unchecked")