Skip to content

Commit

Permalink
Support searches for non-public repeatable annotations
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sbrannen committed Oct 11, 2022
1 parent 9876701 commit 332b25b
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 4 deletions.
Expand Up @@ -18,7 +18,9 @@

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;
Expand Down Expand Up @@ -131,6 +133,19 @@ public static RepeatableContainers none() {
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
Expand All @@ -153,7 +168,7 @@ private static class StandardRepeatableContainers extends 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);
}
Expand Down Expand Up @@ -240,7 +255,7 @@ private Class<? extends Annotation> deduceContainer(Class<? extends Annotation>
@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);
}
Expand Down
Expand Up @@ -181,7 +181,7 @@ void annotatedMethod() {
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Repeatable(A.Container.class)
public @interface A {
@interface A {

int value() default 0;

Expand All @@ -197,7 +197,7 @@ void annotatedMethod() {
@Repeatable(B.Container.class)
@A
@A
public @interface B {
@interface B {

@AliasFor(annotation = A.class)
int value();
Expand Down

0 comments on commit 332b25b

Please sign in to comment.