Skip to content

Commit 6fa09e1

Browse files
committed
Extract AnnotatedMethod base class for consistent annotation exposure
As a consequence, the spring-messaging HandlerMethod detects interface parameter annotations as well, and the same is available for other HandlerMethod variants. Closes gh-30801
1 parent ae23f5a commit 6fa09e1

File tree

6 files changed

+440
-531
lines changed

6 files changed

+440
-531
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.annotation;
18+
19+
import java.lang.annotation.Annotation;
20+
import java.lang.reflect.Method;
21+
import java.util.ArrayList;
22+
import java.util.Arrays;
23+
import java.util.List;
24+
25+
import org.springframework.core.BridgeMethodResolver;
26+
import org.springframework.core.MethodParameter;
27+
import org.springframework.core.ResolvableType;
28+
import org.springframework.lang.NonNull;
29+
import org.springframework.lang.Nullable;
30+
import org.springframework.util.Assert;
31+
import org.springframework.util.ClassUtils;
32+
import org.springframework.util.ObjectUtils;
33+
import org.springframework.util.ReflectionUtils;
34+
import org.springframework.util.StringUtils;
35+
36+
/**
37+
* A convenient wrapper for a {@link Method} handle, providing deep annotation
38+
* introspection on methods and method parameters, including the exposure of
39+
* interface-declared parameter annotations from the concrete target method.
40+
*
41+
* @author Juergen Hoeller
42+
* @since 6.1
43+
* @see #getMethodAnnotation(Class)
44+
* @see #getMethodParameters()
45+
* @see AnnotatedElementUtils
46+
* @see SynthesizingMethodParameter
47+
*/
48+
public class AnnotatedMethod {
49+
50+
private final Method method;
51+
52+
private final Method bridgedMethod;
53+
54+
private final MethodParameter[] parameters;
55+
56+
@Nullable
57+
private volatile List<Annotation[][]> interfaceParameterAnnotations;
58+
59+
60+
/**
61+
* Create an instance that wraps the given {@link Method}.
62+
* @param method the Method handle to wrap
63+
*/
64+
public AnnotatedMethod(Method method) {
65+
Assert.notNull(method, "Method is required");
66+
this.method = method;
67+
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
68+
ReflectionUtils.makeAccessible(this.bridgedMethod);
69+
this.parameters = initMethodParameters();
70+
}
71+
72+
/**
73+
* Copy constructor for use in subclasses.
74+
*/
75+
protected AnnotatedMethod(AnnotatedMethod annotatedMethod) {
76+
Assert.notNull(annotatedMethod, "AnnotatedMethod is required");
77+
this.method = annotatedMethod.method;
78+
this.bridgedMethod = annotatedMethod.bridgedMethod;
79+
this.parameters = annotatedMethod.parameters;
80+
this.interfaceParameterAnnotations = annotatedMethod.interfaceParameterAnnotations;
81+
}
82+
83+
84+
/**
85+
* Return the method for this handler method.
86+
*/
87+
public final Method getMethod() {
88+
return this.method;
89+
}
90+
91+
/**
92+
* If the bean method is a bridge method, this method returns the bridged
93+
* (user-defined) method. Otherwise, it returns the same method as {@link #getMethod()}.
94+
*/
95+
protected final Method getBridgedMethod() {
96+
return this.bridgedMethod;
97+
}
98+
99+
/**
100+
* Expose the containing class for method parameters.
101+
* @see MethodParameter#getContainingClass()
102+
*/
103+
protected Class<?> getContainingClass() {
104+
return this.method.getDeclaringClass();
105+
}
106+
107+
/**
108+
* Return the method parameters for this handler method.
109+
*/
110+
public final MethodParameter[] getMethodParameters() {
111+
return this.parameters;
112+
}
113+
114+
private MethodParameter[] initMethodParameters() {
115+
int count = this.bridgedMethod.getParameterCount();
116+
MethodParameter[] result = new MethodParameter[count];
117+
for (int i = 0; i < count; i++) {
118+
result[i] = new AnnotatedMethodParameter(i);
119+
}
120+
return result;
121+
}
122+
123+
/**
124+
* Return a MethodParameter for the declared return type.
125+
*/
126+
public MethodParameter getReturnType() {
127+
return new AnnotatedMethodParameter(-1);
128+
}
129+
130+
/**
131+
* Return a MethodParameter for the actual return value type.
132+
*/
133+
public MethodParameter getReturnValueType(@Nullable Object returnValue) {
134+
return new ReturnValueMethodParameter(returnValue);
135+
}
136+
137+
/**
138+
* Return {@code true} if the method return type is void, {@code false} otherwise.
139+
*/
140+
public boolean isVoid() {
141+
return Void.TYPE.equals(getReturnType().getParameterType());
142+
}
143+
144+
/**
145+
* Return a single annotation on the underlying method traversing its super methods
146+
* if no annotation can be found on the given method itself.
147+
* <p>Also supports <em>merged</em> composed annotations with attribute
148+
* overrides as of Spring Framework 4.2.2.
149+
* @param annotationType the type of annotation to introspect the method for
150+
* @return the annotation, or {@code null} if none found
151+
* @see AnnotatedElementUtils#findMergedAnnotation
152+
*/
153+
@Nullable
154+
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
155+
return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType);
156+
}
157+
158+
/**
159+
* Return whether the parameter is declared with the given annotation type.
160+
* @param annotationType the annotation type to look for
161+
* @since 4.3
162+
* @see AnnotatedElementUtils#hasAnnotation
163+
*/
164+
public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) {
165+
return AnnotatedElementUtils.hasAnnotation(this.method, annotationType);
166+
}
167+
168+
private List<Annotation[][]> getInterfaceParameterAnnotations() {
169+
List<Annotation[][]> parameterAnnotations = this.interfaceParameterAnnotations;
170+
if (parameterAnnotations == null) {
171+
parameterAnnotations = new ArrayList<>();
172+
for (Class<?> ifc : ClassUtils.getAllInterfacesForClassAsSet(this.method.getDeclaringClass())) {
173+
for (Method candidate : ifc.getMethods()) {
174+
if (isOverrideFor(candidate)) {
175+
parameterAnnotations.add(candidate.getParameterAnnotations());
176+
}
177+
}
178+
}
179+
this.interfaceParameterAnnotations = parameterAnnotations;
180+
}
181+
return parameterAnnotations;
182+
}
183+
184+
private boolean isOverrideFor(Method candidate) {
185+
if (!candidate.getName().equals(this.method.getName()) ||
186+
candidate.getParameterCount() != this.method.getParameterCount()) {
187+
return false;
188+
}
189+
Class<?>[] paramTypes = this.method.getParameterTypes();
190+
if (Arrays.equals(candidate.getParameterTypes(), paramTypes)) {
191+
return true;
192+
}
193+
for (int i = 0; i < paramTypes.length; i++) {
194+
if (paramTypes[i] !=
195+
ResolvableType.forMethodParameter(candidate, i, this.method.getDeclaringClass()).resolve()) {
196+
return false;
197+
}
198+
}
199+
return true;
200+
}
201+
202+
203+
@Override
204+
public boolean equals(@Nullable Object other) {
205+
return (this == other || (other != null && getClass() == other.getClass() &&
206+
this.method.equals(((AnnotatedMethod) other).method)));
207+
}
208+
209+
@Override
210+
public int hashCode() {
211+
return (this.method.hashCode());
212+
}
213+
214+
@Override
215+
public String toString() {
216+
return this.method.toGenericString();
217+
}
218+
219+
220+
// Support methods for use in subclass variants
221+
222+
@Nullable
223+
protected static Object findProvidedArgument(MethodParameter parameter, @Nullable Object... providedArgs) {
224+
if (!ObjectUtils.isEmpty(providedArgs)) {
225+
for (Object providedArg : providedArgs) {
226+
if (parameter.getParameterType().isInstance(providedArg)) {
227+
return providedArg;
228+
}
229+
}
230+
}
231+
return null;
232+
}
233+
234+
protected static String formatArgumentError(MethodParameter param, String message) {
235+
return "Could not resolve parameter [" + param.getParameterIndex() + "] in " +
236+
param.getExecutable().toGenericString() + (StringUtils.hasText(message) ? ": " + message : "");
237+
}
238+
239+
240+
/**
241+
* A MethodParameter with AnnotatedMethod-specific behavior.
242+
*/
243+
protected class AnnotatedMethodParameter extends SynthesizingMethodParameter {
244+
245+
@Nullable
246+
private volatile Annotation[] combinedAnnotations;
247+
248+
public AnnotatedMethodParameter(int index) {
249+
super(AnnotatedMethod.this.getBridgedMethod(), index);
250+
}
251+
252+
protected AnnotatedMethodParameter(AnnotatedMethodParameter original) {
253+
super(original);
254+
this.combinedAnnotations = original.combinedAnnotations;
255+
}
256+
257+
@Override
258+
@NonNull
259+
public Method getMethod() {
260+
return AnnotatedMethod.this.getBridgedMethod();
261+
}
262+
263+
@Override
264+
public Class<?> getContainingClass() {
265+
return AnnotatedMethod.this.getContainingClass();
266+
}
267+
268+
@Override
269+
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
270+
return AnnotatedMethod.this.getMethodAnnotation(annotationType);
271+
}
272+
273+
@Override
274+
public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) {
275+
return AnnotatedMethod.this.hasMethodAnnotation(annotationType);
276+
}
277+
278+
@Override
279+
public Annotation[] getParameterAnnotations() {
280+
Annotation[] anns = this.combinedAnnotations;
281+
if (anns == null) {
282+
anns = super.getParameterAnnotations();
283+
int index = getParameterIndex();
284+
if (index >= 0) {
285+
for (Annotation[][] ifcAnns : getInterfaceParameterAnnotations()) {
286+
if (index < ifcAnns.length) {
287+
Annotation[] paramAnns = ifcAnns[index];
288+
if (paramAnns.length > 0) {
289+
List<Annotation> merged = new ArrayList<>(anns.length + paramAnns.length);
290+
merged.addAll(Arrays.asList(anns));
291+
for (Annotation paramAnn : paramAnns) {
292+
boolean existingType = false;
293+
for (Annotation ann : anns) {
294+
if (ann.annotationType() == paramAnn.annotationType()) {
295+
existingType = true;
296+
break;
297+
}
298+
}
299+
if (!existingType) {
300+
merged.add(adaptAnnotation(paramAnn));
301+
}
302+
}
303+
anns = merged.toArray(new Annotation[0]);
304+
}
305+
}
306+
}
307+
}
308+
this.combinedAnnotations = anns;
309+
}
310+
return anns;
311+
}
312+
313+
@Override
314+
public AnnotatedMethodParameter clone() {
315+
return new AnnotatedMethodParameter(this);
316+
}
317+
}
318+
319+
320+
/**
321+
* A MethodParameter for an AnnotatedMethod return type based on an actual return value.
322+
*/
323+
private class ReturnValueMethodParameter extends AnnotatedMethodParameter {
324+
325+
@Nullable
326+
private final Class<?> returnValueType;
327+
328+
public ReturnValueMethodParameter(@Nullable Object returnValue) {
329+
super(-1);
330+
this.returnValueType = (returnValue != null ? returnValue.getClass() : null);
331+
}
332+
333+
protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
334+
super(original);
335+
this.returnValueType = original.returnValueType;
336+
}
337+
338+
@Override
339+
public Class<?> getParameterType() {
340+
return (this.returnValueType != null ? this.returnValueType : super.getParameterType());
341+
}
342+
343+
@Override
344+
public ReturnValueMethodParameter clone() {
345+
return new ReturnValueMethodParameter(this);
346+
}
347+
}
348+
349+
}

0 commit comments

Comments
 (0)