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
This commit is contained in:
Sam Brannen 2022-10-11 19:27:36 +02:00
parent 9876701493
commit 332b25b680
2 changed files with 19 additions and 4 deletions

View File

@ -18,7 +18,9 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import org.springframework.lang.Nullable;
@ -131,6 +133,19 @@ public abstract class RepeatableContainers {
return NoRepeatableContainers.INSTANCE;
}
private static Object invokeAnnotationMethod(Annotation annotation, Method method) {
if (Proxy.isProxyClass(annotation.getClass())) {
try {
InvocationHandler handler = Proxy.getInvocationHandler(annotation);
return handler.invoke(annotation, method, null);
}
catch (Throwable ex) {
// ignore and fall back to reflection below
}
}
return ReflectionUtils.invokeMethod(method, annotation);
}
/**
* Standard {@link RepeatableContainers} implementation that searches using
@ -153,7 +168,7 @@ public abstract class RepeatableContainers {
Annotation[] findRepeatedAnnotations(Annotation annotation) {
Method method = getRepeatedAnnotationsMethod(annotation.annotationType());
if (method != null) {
return (Annotation[]) ReflectionUtils.invokeMethod(method, annotation);
return (Annotation[]) invokeAnnotationMethod(annotation, method);
}
return super.findRepeatedAnnotations(annotation);
}
@ -240,7 +255,7 @@ public abstract class RepeatableContainers {
@Nullable
Annotation[] findRepeatedAnnotations(Annotation annotation) {
if (this.container.isAssignableFrom(annotation.annotationType())) {
return (Annotation[]) ReflectionUtils.invokeMethod(this.valueMethod, annotation);
return (Annotation[]) invokeAnnotationMethod(annotation, this.valueMethod);
}
return super.findRepeatedAnnotations(annotation);
}

View File

@ -181,7 +181,7 @@ class NestedRepeatableAnnotationsTests {
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Repeatable(A.Container.class)
public @interface A {
@interface A {
int value() default 0;
@ -197,7 +197,7 @@ class NestedRepeatableAnnotationsTests {
@Repeatable(B.Container.class)
@A
@A
public @interface B {
@interface B {
@AliasFor(annotation = A.class)
int value();