Stop implementing SynthesizedAnnotation in annotation proxies
SynthesizedAnnotation was originally introduced as a convenience for easily detecting if an annotation had been synthesized by Spring via a simple `if (myAnnotation instanceof SynthesizedAnnotation)` check. However, the introduction of SynthesizedAnnotation in the JDK dynamic proxy for a synthesized annotation results in a separate proxy class for each annotation synthesized by Spring, and this causes issues with GraalVM native images since users and framework developers must always ensure that the additional proxy classes are registered. This commit completely removes the use of SynthesizedAnnotation in synthesized annotation proxies. This change allows the proxy class for an annotation to be reused for a synthesized annotation of the same annotation type. Consequently: - Extra proxy classes are not generated on the JVM or in a native image. - Extra proxy classes are not required to be registered for a native image. Closes gh-29041
This commit is contained in:
@@ -49,7 +49,6 @@ public abstract class RuntimeHintsUtils {
|
||||
* at runtime.
|
||||
* @param hints the {@link RuntimeHints} instance to use
|
||||
* @param annotationType the annotation type
|
||||
* @see SynthesizedAnnotation
|
||||
* @deprecated as annotation attributes are visible without additional hints
|
||||
*/
|
||||
@Deprecated
|
||||
@@ -70,7 +69,6 @@ public abstract class RuntimeHintsUtils {
|
||||
* that determines if the hints are required.
|
||||
* @param hints the {@link RuntimeHints} instance to use
|
||||
* @param annotationType the annotation type
|
||||
* @see SynthesizedAnnotation
|
||||
*/
|
||||
public static void registerSynthesizedAnnotation(RuntimeHints hints, Class<?> annotationType) {
|
||||
hints.proxies().registerJdkProxy(annotationType, SynthesizedAnnotation.class);
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -1288,7 +1289,15 @@ public abstract class AnnotationUtils {
|
||||
* @since 5.3.23
|
||||
*/
|
||||
public static boolean isSynthesizedAnnotation(@Nullable Annotation annotation) {
|
||||
return (annotation instanceof SynthesizedAnnotation);
|
||||
try {
|
||||
return ((annotation != null) && Proxy.isProxyClass(annotation.getClass()) &&
|
||||
(Proxy.getInvocationHandler(annotation) instanceof SynthesizedMergedAnnotationInvocationHandler));
|
||||
}
|
||||
catch (SecurityException ex) {
|
||||
// Security settings disallow reflective access to the InvocationHandler:
|
||||
// assume the annotation has not been synthesized by Spring.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -299,9 +299,8 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
|
||||
@SuppressWarnings("unchecked")
|
||||
static <A extends Annotation> A createProxy(MergedAnnotation<A> annotation, Class<A> type) {
|
||||
ClassLoader classLoader = type.getClassLoader();
|
||||
Class<?>[] interfaces = new Class<?>[] {type};
|
||||
InvocationHandler handler = new SynthesizedMergedAnnotationInvocationHandler<>(annotation, type);
|
||||
Class<?>[] interfaces = isVisible(classLoader, SynthesizedAnnotation.class) ?
|
||||
new Class<?>[] {type, SynthesizedAnnotation.class} : new Class<?>[] {type};
|
||||
return (A) Proxy.newProxyInstance(classLoader, interfaces, handler);
|
||||
}
|
||||
|
||||
@@ -310,17 +309,4 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
|
||||
return (canonicalName != null ? canonicalName : clazz.getName());
|
||||
}
|
||||
|
||||
|
||||
private static boolean isVisible(ClassLoader classLoader, Class<?> interfaceClass) {
|
||||
if (classLoader == interfaceClass.getClassLoader()) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
return Class.forName(interfaceClass.getName(), false, classLoader) == interfaceClass;
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
|
||||
* @since 5.3.22
|
||||
*/
|
||||
private boolean isSynthesizable(Annotation annotation) {
|
||||
if (annotation instanceof SynthesizedAnnotation) {
|
||||
if (AnnotationUtils.isSynthesizedAnnotation(annotation)) {
|
||||
return false;
|
||||
}
|
||||
return isSynthesizable();
|
||||
|
||||
@@ -1511,6 +1511,26 @@ class MergedAnnotationsTests {
|
||||
assertThat(MergedAnnotation.from(component).isSynthesizable()).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 6.0
|
||||
*/
|
||||
@Test
|
||||
void synthesizedAnnotationShouldReuseJdkProxyClass() throws Exception {
|
||||
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
|
||||
|
||||
RequestMapping jdkRequestMapping = method.getAnnotation(RequestMapping.class);
|
||||
assertThat(jdkRequestMapping).isNotNull();
|
||||
assertThat(jdkRequestMapping.value()).containsExactly("/test");
|
||||
assertThat(jdkRequestMapping.path()).containsExactly("");
|
||||
|
||||
RequestMapping synthesizedRequestMapping = MergedAnnotation.from(jdkRequestMapping).synthesize();
|
||||
assertSynthesized(synthesizedRequestMapping);
|
||||
assertThat(synthesizedRequestMapping.value()).containsExactly("/test");
|
||||
assertThat(synthesizedRequestMapping.path()).containsExactly("/test");
|
||||
|
||||
assertThat(jdkRequestMapping.getClass()).isSameAs(synthesizedRequestMapping.getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
void synthesizeAlreadySynthesized() throws Exception {
|
||||
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
|
||||
@@ -1585,7 +1605,6 @@ class MergedAnnotationsTests {
|
||||
mergedAnnotations.get(EnableGlobalAuthentication.class);
|
||||
assertThat(enableGlobalAuthenticationMergedAnnotation.isSynthesizable()).isFalse();
|
||||
EnableGlobalAuthentication enableGlobalAuthentication = enableGlobalAuthenticationMergedAnnotation.synthesize();
|
||||
assertThat(enableGlobalAuthentication).isNotInstanceOf(SynthesizedAnnotation.class);
|
||||
assertNotSynthesized(enableGlobalAuthentication);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user