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:
parent
9876701493
commit
332b25b680
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue