Skip to content

Commit 8c2ccfe

Browse files
philwebbjhoeller
authored andcommitted
Add MergedAnnotations support to meta-data classes
Add `AnnotatedTypeMetaData.getAnnotations()` that can be used to access annotation details using the `MergedAnnotations` interface. Where possible, the existing annotation methods have been migrated to call `getAnnotation()`, rather than needing their own implementation. The existing ASM based meta-data implementations have not been updated since they will be deprecated and replaced in a subsequent commit. See gh-22884
1 parent 30ba80a commit 8c2ccfe

10 files changed

+194
-70
lines changed

spring-core/src/main/java/org/springframework/core/type/AnnotatedTypeMetadata.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,8 +16,15 @@
1616

1717
package org.springframework.core.type;
1818

19+
import java.lang.annotation.Annotation;
1920
import java.util.Map;
2021

22+
import org.springframework.core.annotation.MergedAnnotation;
23+
import org.springframework.core.annotation.MergedAnnotation.Adapt;
24+
import org.springframework.core.annotation.MergedAnnotationCollectors;
25+
import org.springframework.core.annotation.MergedAnnotationPredicates;
26+
import org.springframework.core.annotation.MergedAnnotationSelectors;
27+
import org.springframework.core.annotation.MergedAnnotations;
2128
import org.springframework.lang.Nullable;
2229
import org.springframework.util.MultiValueMap;
2330

@@ -38,6 +45,13 @@
3845
*/
3946
public interface AnnotatedTypeMetadata {
4047

48+
/**
49+
* Return annotation details based on the direct annotations of the
50+
* underlying element.
51+
* @return merged annotations based on the direct annotations
52+
*/
53+
MergedAnnotations getAnnotations();
54+
4155
/**
4256
* Determine whether the underlying element has an annotation or meta-annotation
4357
* of the given type defined.
@@ -47,7 +61,9 @@ public interface AnnotatedTypeMetadata {
4761
* type to look for
4862
* @return whether a matching annotation is defined
4963
*/
50-
boolean isAnnotated(String annotationName);
64+
default boolean isAnnotated(String annotationName) {
65+
return getAnnotations().isPresent(annotationName);
66+
}
5167

5268
/**
5369
* Retrieve the attributes of the annotation of the given type, if any (i.e. if
@@ -78,7 +94,16 @@ default Map<String, Object> getAnnotationAttributes(String annotationName) {
7894
* {@code null} if no matching annotation is defined.
7995
*/
8096
@Nullable
81-
Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
97+
default Map<String, Object> getAnnotationAttributes(String annotationName,
98+
boolean classValuesAsString) {
99+
100+
MergedAnnotation<Annotation> annotation = getAnnotations().get(annotationName,
101+
null, MergedAnnotationSelectors.firstDirectlyDeclared());
102+
if (!annotation.isPresent()) {
103+
return null;
104+
}
105+
return annotation.asAnnotationAttributes(Adapt.values(classValuesAsString, true));
106+
}
82107

83108
/**
84109
* Retrieve all attributes of all annotations of the given type, if any (i.e. if
@@ -109,6 +134,15 @@ default MultiValueMap<String, Object> getAllAnnotationAttributes(String annotati
109134
* @see #getAllAnnotationAttributes(String)
110135
*/
111136
@Nullable
112-
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
137+
default MultiValueMap<String, Object> getAllAnnotationAttributes(
138+
String annotationName, boolean classValuesAsString) {
139+
140+
Adapt[] adaptations = Adapt.values(classValuesAsString, true);
141+
return getAnnotations().stream(annotationName)
142+
.filter(MergedAnnotationPredicates.unique(MergedAnnotation::getTypeHierarchy))
143+
.map(MergedAnnotation::withNonMergedAttributes)
144+
.collect(MergedAnnotationCollectors.toMultiValueMap(map ->
145+
map.isEmpty() ? null : map, adaptations));
146+
}
113147

114148
}

spring-core/src/main/java/org/springframework/core/type/AnnotationMetadata.java

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,7 +16,14 @@
1616

1717
package org.springframework.core.type;
1818

19+
import java.util.Collections;
20+
import java.util.LinkedHashSet;
1921
import java.util.Set;
22+
import java.util.stream.Collectors;
23+
24+
import org.springframework.core.annotation.MergedAnnotation;
25+
import org.springframework.core.annotation.MergedAnnotations;
26+
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
2027

2128
/**
2229
* Interface that defines abstract access to the annotations of a specific
@@ -38,7 +45,12 @@ public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata
3845
* are <em>present</em> on the underlying class.
3946
* @return the annotation type names
4047
*/
41-
Set<String> getAnnotationTypes();
48+
default Set<String> getAnnotationTypes() {
49+
return getAnnotations().stream()
50+
.filter(MergedAnnotation::isDirectlyPresent)
51+
.map(annotation -> annotation.getType().getName())
52+
.collect(Collectors.toCollection(LinkedHashSet::new));
53+
}
4254

4355
/**
4456
* Get the fully qualified class names of all meta-annotation types that
@@ -47,7 +59,15 @@ public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata
4759
* type to look for
4860
* @return the meta-annotation type names, or an empty set if none found
4961
*/
50-
Set<String> getMetaAnnotationTypes(String annotationName);
62+
default Set<String> getMetaAnnotationTypes(String annotationName) {
63+
MergedAnnotation<?> annotation = getAnnotations().get(annotationName, MergedAnnotation::isDirectlyPresent);
64+
if (!annotation.isPresent()) {
65+
return Collections.emptySet();
66+
}
67+
return MergedAnnotations.from(annotation.getType(), SearchStrategy.INHERITED_ANNOTATIONS).stream()
68+
.map(mergedAnnotation -> mergedAnnotation.getType().getName())
69+
.collect(Collectors.toCollection(LinkedHashSet::new));
70+
}
5171

5272
/**
5373
* Determine whether an annotation of the given type is <em>present</em> on
@@ -57,7 +77,7 @@ public interface AnnotationMetadata extends ClassMetadata, AnnotatedTypeMetadata
5777
* @return {@code true} if a matching annotation is present
5878
*/
5979
default boolean hasAnnotation(String annotationName) {
60-
return getAnnotationTypes().contains(annotationName);
80+
return getAnnotations().isDirectlyPresent(annotationName);
6181
}
6282

6383
/**
@@ -67,15 +87,20 @@ default boolean hasAnnotation(String annotationName) {
6787
* meta-annotation type to look for
6888
* @return {@code true} if a matching meta-annotation is present
6989
*/
70-
boolean hasMetaAnnotation(String metaAnnotationName);
90+
default boolean hasMetaAnnotation(String metaAnnotationName) {
91+
return getAnnotations().get(metaAnnotationName,
92+
MergedAnnotation::isMetaPresent).isPresent();
93+
}
7194

7295
/**
7396
* Determine whether the underlying class has any methods that are
7497
* annotated (or meta-annotated) with the given annotation type.
7598
* @param annotationName the fully qualified class name of the annotation
7699
* type to look for
77100
*/
78-
boolean hasAnnotatedMethods(String annotationName);
101+
default boolean hasAnnotatedMethods(String annotationName) {
102+
return !getAnnotatedMethods(annotationName).isEmpty();
103+
}
79104

80105
/**
81106
* Retrieve the method metadata for all methods that are annotated

spring-core/src/main/java/org/springframework/core/type/StandardAnnotationMetadata.java

Lines changed: 43 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
import java.util.Set;
2525

2626
import org.springframework.core.annotation.AnnotatedElementUtils;
27+
import org.springframework.core.annotation.AnnotationFilter;
2728
import org.springframework.core.annotation.AnnotationUtils;
29+
import org.springframework.core.annotation.MergedAnnotations;
30+
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
31+
import org.springframework.core.annotation.RepeatableContainers;
2832
import org.springframework.lang.Nullable;
2933
import org.springframework.util.MultiValueMap;
3034
import org.springframework.util.ReflectionUtils;
@@ -42,10 +46,11 @@
4246
*/
4347
public class StandardAnnotationMetadata extends StandardClassMetadata implements AnnotationMetadata {
4448

45-
private final Annotation[] annotations;
49+
private final MergedAnnotations mergedAnnotations;
4650

4751
private final boolean nestedAnnotationsAsMap;
4852

53+
private Set<String> annotationTypes;
4954

5055
/**
5156
* Create a new {@code StandardAnnotationMetadata} wrapper for the given Class.
@@ -69,72 +74,49 @@ public StandardAnnotationMetadata(Class<?> introspectedClass) {
6974
*/
7075
public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) {
7176
super(introspectedClass);
72-
this.annotations = introspectedClass.getDeclaredAnnotations();
77+
this.mergedAnnotations = MergedAnnotations.from(introspectedClass,
78+
SearchStrategy.DIRECT, RepeatableContainers.none(),
79+
AnnotationFilter.NONE);
7380
this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
7481
}
7582

7683

7784
@Override
78-
public Set<String> getAnnotationTypes() {
79-
Set<String> types = new LinkedHashSet<>();
80-
for (Annotation ann : this.annotations) {
81-
if (!AnnotationUtils.isInJavaLangAnnotationPackage(ann.annotationType().getName())) {
82-
types.add(ann.annotationType().getName());
83-
}
84-
}
85-
return types;
86-
}
87-
88-
@Override
89-
public Set<String> getMetaAnnotationTypes(String annotationName) {
90-
if (AnnotationUtils.isInJavaLangAnnotationPackage(annotationName)) {
91-
return Collections.emptySet();
92-
}
93-
return (this.annotations.length > 0 ?
94-
AnnotatedElementUtils.getMetaAnnotationTypes(getIntrospectedClass(), annotationName) :
95-
Collections.emptySet());
85+
public MergedAnnotations getAnnotations() {
86+
return this.mergedAnnotations;
9687
}
9788

9889
@Override
99-
public boolean hasAnnotation(String annotationName) {
100-
if (AnnotationUtils.isInJavaLangAnnotationPackage(annotationName)) {
101-
return false;
102-
}
103-
for (Annotation ann : this.annotations) {
104-
if (ann.annotationType().getName().equals(annotationName)) {
105-
return true;
106-
}
107-
}
108-
return false;
109-
}
110-
111-
@Override
112-
public boolean hasMetaAnnotation(String annotationName) {
113-
if (AnnotationUtils.isInJavaLangAnnotationPackage(annotationName)) {
114-
return false;
90+
public Set<String> getAnnotationTypes() {
91+
Set<String> annotationTypes = this.annotationTypes;
92+
if (annotationTypes == null) {
93+
annotationTypes = Collections.unmodifiableSet(
94+
AnnotationMetadata.super.getAnnotationTypes());
95+
this.annotationTypes = annotationTypes;
11596
}
116-
return (this.annotations.length > 0 &&
117-
AnnotatedElementUtils.hasMetaAnnotationTypes(getIntrospectedClass(), annotationName));
118-
}
119-
120-
@Override
121-
public boolean isAnnotated(String annotationName) {
122-
return (this.annotations.length > 0 &&
123-
AnnotatedElementUtils.isAnnotated(getIntrospectedClass(), annotationName));
97+
return annotationTypes;
12498
}
12599

126100
@Override
127101
@Nullable
128102
public Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
129-
return (this.annotations.length > 0 ? AnnotatedElementUtils.getMergedAnnotationAttributes(
130-
getIntrospectedClass(), annotationName, classValuesAsString, this.nestedAnnotationsAsMap) : null);
103+
if (this.nestedAnnotationsAsMap) {
104+
return AnnotationMetadata.super.getAnnotationAttributes(annotationName,
105+
classValuesAsString);
106+
}
107+
return AnnotatedElementUtils.getMergedAnnotationAttributes(
108+
getIntrospectedClass(), annotationName, classValuesAsString, this.nestedAnnotationsAsMap);
131109
}
132110

133111
@Override
134112
@Nullable
135113
public MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) {
136-
return (this.annotations.length > 0 ? AnnotatedElementUtils.getAllAnnotationAttributes(
137-
getIntrospectedClass(), annotationName, classValuesAsString, this.nestedAnnotationsAsMap) : null);
114+
if (this.nestedAnnotationsAsMap) {
115+
return AnnotationMetadata.super.getAllAnnotationAttributes(annotationName,
116+
classValuesAsString);
117+
}
118+
return AnnotatedElementUtils.getAllAnnotationAttributes(
119+
getIntrospectedClass(), annotationName, classValuesAsString, this.nestedAnnotationsAsMap);
138120
}
139121

140122
@Override
@@ -143,8 +125,7 @@ public boolean hasAnnotatedMethods(String annotationName) {
143125
try {
144126
Method[] methods = ReflectionUtils.getDeclaredMethods(getIntrospectedClass());
145127
for (Method method : methods) {
146-
if (!method.isBridge() && method.getAnnotations().length > 0 &&
147-
AnnotatedElementUtils.isAnnotated(method, annotationName)) {
128+
if (isAnnotatedMethod(method, annotationName)) {
148129
return true;
149130
}
150131
}
@@ -158,13 +139,15 @@ public boolean hasAnnotatedMethods(String annotationName) {
158139

159140
@Override
160141
public Set<MethodMetadata> getAnnotatedMethods(String annotationName) {
161-
Set<MethodMetadata> annotatedMethods = new LinkedHashSet<>(4);
142+
Set<MethodMetadata> annotatedMethods = null;
162143
if (AnnotationUtils.isCandidateClass(getIntrospectedClass(), annotationName)) {
163144
try {
164-
Method[] methods = getIntrospectedClass().getDeclaredMethods();
145+
Method[] methods = ReflectionUtils.getDeclaredMethods(getIntrospectedClass());
165146
for (Method method : methods) {
166-
if (!method.isBridge() && method.getAnnotations().length > 0 &&
167-
AnnotatedElementUtils.isAnnotated(method, annotationName)) {
147+
if (isAnnotatedMethod(method, annotationName)) {
148+
if (annotatedMethods == null) {
149+
annotatedMethods = new LinkedHashSet<>(4);
150+
}
168151
annotatedMethods.add(new StandardMethodMetadata(method, this.nestedAnnotationsAsMap));
169152
}
170153
}
@@ -173,7 +156,12 @@ public Set<MethodMetadata> getAnnotatedMethods(String annotationName) {
173156
throw new IllegalStateException("Failed to introspect annotated methods on " + getIntrospectedClass(), ex);
174157
}
175158
}
176-
return annotatedMethods;
159+
return annotatedMethods != null ? annotatedMethods : Collections.emptySet();
160+
}
161+
162+
private boolean isAnnotatedMethod(Method method, String annotationName) {
163+
return !method.isBridge() && method.getAnnotations().length > 0 &&
164+
AnnotatedElementUtils.isAnnotated(method, annotationName);
177165
}
178166

179167
}

0 commit comments

Comments
 (0)