Skip to content

Commit 332b25b

Browse files
committed
Support searches for non-public repeatable annotations
Prior to this commit, searches for non-public repeatable annotations failed with error messages similar to the following, since the repeatable annotation's container's `value()` method could not be invoked via reflection. JDK 8: java.lang.IllegalAccessError: tried to access class org.springframework.core.annotation.NestedRepeatableAnnotationsTests$A from class com.sun.proxy.$Proxy12 JDK 17: java.lang.IllegalAccessError: failed to access class org.springframework.core.annotation.NestedRepeatableAnnotationsTests$A from class jdk.proxy2.$Proxy12 (org.springframework.core.annotation.NestedRepeatableAnnotationsTests$A is in unnamed module of loader 'app'; jdk.proxy2.$Proxy12 is in module jdk.proxy2 of loader 'app') This commit makes it possible to search for non-public repeatable annotations by first attempting to invoke the repeatable annotation's container's `value()` method via the container's InvocationHandler (if the container is a JDK dynamic proxy) and then falling back to reflection for the method invocation if an error occurs (such as a SecurityException). Closes gh-29301
1 parent 9876701 commit 332b25b

File tree

2 files changed

+19
-4
lines changed

2 files changed

+19
-4
lines changed

spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import java.lang.annotation.Annotation;
2020
import java.lang.annotation.Repeatable;
21+
import java.lang.reflect.InvocationHandler;
2122
import java.lang.reflect.Method;
23+
import java.lang.reflect.Proxy;
2224
import java.util.Map;
2325

2426
import org.springframework.lang.Nullable;
@@ -131,6 +133,19 @@ public static RepeatableContainers none() {
131133
return NoRepeatableContainers.INSTANCE;
132134
}
133135

136+
private static Object invokeAnnotationMethod(Annotation annotation, Method method) {
137+
if (Proxy.isProxyClass(annotation.getClass())) {
138+
try {
139+
InvocationHandler handler = Proxy.getInvocationHandler(annotation);
140+
return handler.invoke(annotation, method, null);
141+
}
142+
catch (Throwable ex) {
143+
// ignore and fall back to reflection below
144+
}
145+
}
146+
return ReflectionUtils.invokeMethod(method, annotation);
147+
}
148+
134149

135150
/**
136151
* Standard {@link RepeatableContainers} implementation that searches using
@@ -153,7 +168,7 @@ private static class StandardRepeatableContainers extends RepeatableContainers {
153168
Annotation[] findRepeatedAnnotations(Annotation annotation) {
154169
Method method = getRepeatedAnnotationsMethod(annotation.annotationType());
155170
if (method != null) {
156-
return (Annotation[]) ReflectionUtils.invokeMethod(method, annotation);
171+
return (Annotation[]) invokeAnnotationMethod(annotation, method);
157172
}
158173
return super.findRepeatedAnnotations(annotation);
159174
}
@@ -240,7 +255,7 @@ private Class<? extends Annotation> deduceContainer(Class<? extends Annotation>
240255
@Nullable
241256
Annotation[] findRepeatedAnnotations(Annotation annotation) {
242257
if (this.container.isAssignableFrom(annotation.annotationType())) {
243-
return (Annotation[]) ReflectionUtils.invokeMethod(this.valueMethod, annotation);
258+
return (Annotation[]) invokeAnnotationMethod(annotation, this.valueMethod);
244259
}
245260
return super.findRepeatedAnnotations(annotation);
246261
}

spring-core/src/test/java/org/springframework/core/annotation/NestedRepeatableAnnotationsTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ void annotatedMethod() {
181181
@Retention(RetentionPolicy.RUNTIME)
182182
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
183183
@Repeatable(A.Container.class)
184-
public @interface A {
184+
@interface A {
185185

186186
int value() default 0;
187187

@@ -197,7 +197,7 @@ void annotatedMethod() {
197197
@Repeatable(B.Container.class)
198198
@A
199199
@A
200-
public @interface B {
200+
@interface B {
201201

202202
@AliasFor(annotation = A.class)
203203
int value();

0 commit comments

Comments
 (0)