Skip to content

Commit cc6de8f

Browse files
committed
Hide MergedAnnotation Implementation Details
Issue gh-15286
1 parent 095929f commit cc6de8f

File tree

4 files changed

+103
-102
lines changed

4 files changed

+103
-102
lines changed

core/src/main/java/org/springframework/security/core/annotation/AnnotationSynthesizer.java

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,30 @@
1717
package org.springframework.security.core.annotation;
1818

1919
import java.lang.annotation.Annotation;
20-
import java.lang.reflect.AnnotatedElement;
2120
import java.lang.reflect.Method;
2221
import java.lang.reflect.Parameter;
2322

24-
import org.springframework.core.annotation.MergedAnnotation;
2523
import org.springframework.lang.Nullable;
26-
import org.springframework.util.Assert;
2724

2825
/**
29-
* A strategy for synthesizing an annotation from an {@link AnnotatedElement}.
26+
* An interface to search for and synthesize an annotation on a type, method, or method
27+
* parameter into an annotation of type {@code <A>}.
28+
*
29+
* <p>
30+
* Implementations should support meta-annotations. This is usually by way of the
31+
* {@link org.springframework.core.annotation.MergedAnnotations} API.
3032
*
3133
* <p>
3234
* Synthesis generally refers to the process of taking an annotation's meta-annotations
3335
* and placeholders, resolving them, and then combining these elements into a facade of
3436
* the raw annotation instance.
35-
* </p>
3637
*
3738
* <p>
38-
* Since the process of synthesizing an annotation can be expensive, it is recommended to
39+
* Since the process of synthesizing an annotation can be expensive, it's recommended to
3940
* cache the synthesized annotation to prevent multiple computations.
4041
* </p>
4142
*
42-
* @param <A> the annotation type
43+
* @param <A> the annotation to search for and synthesize
4344
* @author Josh Cummings
4445
* @since 6.4
4546
* @see UniqueMergedAnnotationSynthesizer
@@ -48,41 +49,36 @@
4849
public interface AnnotationSynthesizer<A extends Annotation> {
4950

5051
/**
51-
* Synthesize an annotation of type {@code A} from the given {@link AnnotatedElement}.
52+
* Synthesize an annotation of type {@code A} from the given method.
5253
*
5354
* <p>
5455
* Implementations should fail if they encounter more than one annotation of that type
55-
* on the element.
56-
* </p>
56+
* attributable to the method.
5757
*
5858
* <p>
5959
* Implementations should describe their strategy for searching the element and any
6060
* surrounding class, interfaces, or super-class.
61-
* </p>
62-
* @param element the element to search
61+
* @param method the method to search from
62+
* @param targetClass the target class for the method
6363
* @return the synthesized annotation or {@code null} if not found
6464
*/
6565
@Nullable
66-
default A synthesize(AnnotatedElement element, Class<?> targetClass) {
67-
Assert.notNull(targetClass, "targetClass cannot be null");
68-
MergedAnnotation<A> annotation = merge(element, targetClass);
69-
if (annotation == null) {
70-
return null;
71-
}
72-
return annotation.synthesize();
73-
}
66+
A synthesize(Method method, Class<?> targetClass);
7467

68+
/**
69+
* Synthesize an annotation of type {@code A} from the given method parameter.
70+
*
71+
* <p>
72+
* Implementations should fail if they encounter more than one annotation of that type
73+
* attributable to the parameter.
74+
*
75+
* <p>
76+
* Implementations should describe their strategy for searching the element and any
77+
* surrounding class, interfaces, or super-class.
78+
* @param element the element to search
79+
* @return the synthesized annotation or {@code null} if not found
80+
*/
7581
@Nullable
76-
default A synthesize(AnnotatedElement element) {
77-
if (element instanceof Method method) {
78-
return synthesize(element, method.getDeclaringClass());
79-
}
80-
if (element instanceof Parameter parameter) {
81-
return synthesize(parameter, parameter.getDeclaringExecutable().getDeclaringClass());
82-
}
83-
throw new UnsupportedOperationException("Unsupported element of type " + element.getClass());
84-
}
85-
86-
MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass);
82+
A synthesize(Parameter parameter);
8783

8884
}

core/src/main/java/org/springframework/security/core/annotation/ExpressionTemplateAnnotationSynthesizer.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,16 @@
3030
import org.springframework.util.PropertyPlaceholderHelper;
3131

3232
/**
33-
* A strategy for synthesizing an annotation from an {@link AnnotatedElement} that
34-
* supports meta-annotations with placeholders, like the following:
33+
* Searches for and synthesizes an annotation on a type, method, or method parameter into
34+
* an annotation of type {@code <A>}, resolving any placeholders in the annotation value.
35+
*
36+
* <p>
37+
* Note that in all cases, Spring Security does not allow for repeatable annotations. So
38+
* this class delegates to {@link UniqueMergedAnnotationSynthesizer} in order to error if
39+
* a repeat is discovered.
40+
*
41+
* <p>
42+
* It supports meta-annotations with placeholders, like the following:
3543
*
3644
* <pre>
3745
* &#64;PreAuthorize("hasRole({role})")
@@ -46,19 +54,14 @@
4654
* {@code @HasRole} annotation found on a given {@link AnnotatedElement}.
4755
*
4856
* <p>
49-
* Note that in all cases, Spring Security does not allow for repeatable annotations. So
50-
* this class delegates to {@link UniqueMergedAnnotationSynthesizer} in order to error if
51-
* a repeat is discovered.
52-
*
53-
* <p>
5457
* Since the process of synthesis is expensive, it is recommended to cache the synthesized
5558
* result to prevent multiple computations.
5659
*
57-
* @param <A> the annotation type
60+
* @param <A> the annotation to search for and synthesize
5861
* @author Josh Cummings
5962
* @since 6.4
6063
*/
61-
final class ExpressionTemplateAnnotationSynthesizer<A extends Annotation> implements AnnotationSynthesizer<A> {
64+
final class ExpressionTemplateAnnotationSynthesizer<A extends Annotation> extends AbstractAnnotationSynthesizer<A> {
6265

6366
private final Class<A> type;
6467

@@ -79,7 +82,7 @@ final class ExpressionTemplateAnnotationSynthesizer<A extends Annotation> implem
7982
}
8083

8184
@Override
82-
public MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
85+
MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
8386
if (element instanceof Parameter parameter) {
8487
MergedAnnotation<A> annotation = this.uniqueParameterAnnotationCache.computeIfAbsent(parameter,
8588
(p) -> this.unique.merge(p, targetClass));

core/src/main/java/org/springframework/security/core/annotation/UniqueMergedAnnotationSynthesizer.java

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,24 @@
3434
import org.springframework.util.ClassUtils;
3535

3636
/**
37-
* A strategy for synthesizing an annotation from an {@link AnnotatedElement} that
38-
* supports meta-annotations, like the following:
39-
*
40-
* <pre>
41-
* &#64;PreAuthorize("hasRole('ROLE_ADMIN')")
42-
* public @annotation HasRole {
43-
* }
44-
* </pre>
45-
*
46-
* <p>
47-
* In that case, you can use an {@link UniqueMergedAnnotationSynthesizer} of type
48-
* {@link org.springframework.security.access.prepost.PreAuthorize} to synthesize any
49-
* {@code @HasRole} annotation found on a given {@link AnnotatedElement}.
37+
* Searches for and synthesizes annotations found on types, methods, or method parameters
38+
* into an annotation of type {@code <A>}, ensuring that there is a unique match.
5039
*
5140
* <p>
5241
* Note that in all cases, Spring Security does not allow for repeatable annotations. As
5342
* such, this class errors if a repeat is discovered.
5443
*
5544
* <p>
56-
* If the given annotation can be applied to types, this class will search for annotations
57-
* across the entire {@link MergedAnnotations.SearchStrategy type hierarchy}; otherwise,
58-
* it will only look for annotations {@link MergedAnnotations.SearchStrategy directly}
59-
* attributed to the element.
45+
* For example, if a class extends two interfaces, and each interface is annotated with
46+
* `@PreAuthorize("hasRole('ADMIN')")` and `@PreAuthorize("hasRole('USER')")`
47+
* respectively, it's not clear which of these should apply, and so this class will throw
48+
* an exception.
49+
*
50+
* <p>
51+
* If the given annotation can be applied to types or methods, this class will traverse
52+
* the type hierarchy, starting from the target class and method; in case of a method
53+
* parameter, it will only consider annotations on the parameter. In all cases, it will
54+
* consider meta-annotations in its traversal.
6055
*
6156
* <p>
6257
* When traversing the type hierarchy, this class will first look for annotations on the
@@ -65,14 +60,29 @@
6560
* extends and on any interfaces that class implements.
6661
*
6762
* <p>
68-
* Since the process of synthesis is expensive, it is recommended to cache the synthesized
63+
* It supports meta-annotations, like the following:
64+
*
65+
* <pre>
66+
* &#64;PreAuthorize("hasRole('ROLE_ADMIN')")
67+
* public @annotation HasRole {
68+
* }
69+
* </pre>
70+
*
71+
* <p>
72+
* In that case, you can use an {@link UniqueMergedAnnotationSynthesizer} of type
73+
* {@link org.springframework.security.access.prepost.PreAuthorize} to synthesize any
74+
* {@code @HasRole} annotation found on a given method or class into its
75+
* {@link org.springframework.security.access.prepost.PreAuthorize} meta-annotation.
76+
*
77+
* <p>
78+
* Since the process of synthesis is expensive, it's recommended to cache the synthesized
6979
* result to prevent multiple computations.
7080
*
71-
* @param <A> the annotation type
81+
* @param <A> the annotation to search for and synthesize
7282
* @author Josh Cummings
7383
* @since 6.4
7484
*/
75-
final class UniqueMergedAnnotationSynthesizer<A extends Annotation> implements AnnotationSynthesizer<A> {
85+
final class UniqueMergedAnnotationSynthesizer<A extends Annotation> extends AbstractAnnotationSynthesizer<A> {
7686

7787
private final List<Class<A>> types;
7888

@@ -87,26 +97,18 @@ final class UniqueMergedAnnotationSynthesizer<A extends Annotation> implements A
8797
}
8898

8999
@Override
90-
public MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
100+
MergedAnnotation<A> merge(AnnotatedElement element, Class<?> targetClass) {
91101
if (element instanceof Parameter parameter) {
92-
return handleParameterElement(parameter);
102+
List<MergedAnnotation<A>> annotations = findDirectAnnotations(parameter);
103+
return requireUnique(parameter, annotations);
93104
}
94105
if (element instanceof Method method) {
95-
return handleMethodElement(method, targetClass);
106+
List<MergedAnnotation<A>> annotations = findMethodAnnotations(method, targetClass);
107+
return requireUnique(method, annotations);
96108
}
97109
throw new AnnotationConfigurationException("Unsupported element of type " + element.getClass());
98110
}
99111

100-
private MergedAnnotation<A> handleParameterElement(Parameter parameter) {
101-
List<MergedAnnotation<A>> annotations = findDirectAnnotations(parameter);
102-
return requireUnique(parameter, annotations);
103-
}
104-
105-
private MergedAnnotation<A> handleMethodElement(Method method, Class<?> targetClass) {
106-
List<MergedAnnotation<A>> annotations = findMethodAnnotations(method, targetClass);
107-
return requireUnique(method, annotations);
108-
}
109-
110112
private MergedAnnotation<A> requireUnique(AnnotatedElement element, List<MergedAnnotation<A>> annotations) {
111113
return switch (annotations.size()) {
112114
case 0 -> null;

0 commit comments

Comments
 (0)