From 4f0c8797781a1c74ce2885e17e7d7a07bdcab619 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 16 Aug 2022 14:15:02 +0200 Subject: [PATCH] Rationalize hints required for annotations This commit updates RuntimeHintsUtils to focus on registering a JDK proxy only as annotations of annotated elements that have at least an introspection hints are visible out-of-the-box. This commit also removes unnecessary hints and adapt `@Reflective` to detect if a hint is required using the introduced MergedAnnotation#isSynthesizable. See gh-28967 --- .../JakartaAnnotationsRuntimeHints.java | 42 ------- .../resources/META-INF/spring/aot.factories | 3 - .../JakartaAnnotationsRuntimeHintsTests.java | 62 ---------- ...ProcessorBeanRegistrationAotProcessor.java | 8 +- .../AsyncAnnotationBeanPostProcessor.java | 15 --- .../ScheduledAnnotationBeanPostProcessor.java | 16 --- ...ssorBeanRegistrationAotProcessorTests.java | 14 +-- .../aot/hint/annotation/Reflective.java | 3 + .../support/CoreAnnotationsRuntimeHints.java | 41 ------- .../aot/hint/support/RuntimeHintsUtils.java | 96 +++++---------- .../resources/META-INF/spring/aot.factories | 1 - .../CoreAnnotationsRuntimeHintsTests.java | 61 ---------- .../hint/support/RuntimeHintsUtilsTests.java | 110 ++++++++---------- .../MessagingAnnotationsRuntimeHints.java | 5 +- .../SimpAnnotationsRuntimeHints.java | 5 +- .../annotation/TransactionRuntimeHints.java | 2 +- .../WebAnnotationsRuntimeHintsRegistrar.java | 15 +-- 17 files changed, 101 insertions(+), 398 deletions(-) delete mode 100644 spring-beans/src/main/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHints.java delete mode 100644 spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java delete mode 100644 spring-core/src/main/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHints.java delete mode 100644 spring-core/src/test/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHintsTests.java diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHints.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHints.java deleted file mode 100644 index 7fffb381819..00000000000 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHints.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.beans.factory.annotation; - -import java.util.stream.Stream; - -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.aot.hint.support.RuntimeHintsUtils; -import org.springframework.util.ClassUtils; - -/** - * {@link RuntimeHintsRegistrar} for Jakarta annotations. - *

Hints are only registered if Jakarta inject is on the classpath. - * - * @author Brian Clozel - */ -class JakartaAnnotationsRuntimeHints implements RuntimeHintsRegistrar { - - @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - if (ClassUtils.isPresent("jakarta.inject.Inject", classLoader)) { - Stream.of("jakarta.inject.Inject", "jakarta.inject.Qualifier").forEach(annotationType -> - RuntimeHintsUtils.registerAnnotation(hints, ClassUtils.resolveClassName(annotationType, classLoader))); - } - } - -} diff --git a/spring-beans/src/main/resources/META-INF/spring/aot.factories b/spring-beans/src/main/resources/META-INF/spring/aot.factories index 11530ced5cd..b95137025d8 100644 --- a/spring-beans/src/main/resources/META-INF/spring/aot.factories +++ b/spring-beans/src/main/resources/META-INF/spring/aot.factories @@ -1,5 +1,2 @@ org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\ org.springframework.beans.factory.aot.BeanRegistrationsAotProcessor - -org.springframework.aot.hint.RuntimeHintsRegistrar=\ -org.springframework.beans.factory.annotation.JakartaAnnotationsRuntimeHints \ No newline at end of file diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java deleted file mode 100644 index 9e06e5a1176..00000000000 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/JakartaAnnotationsRuntimeHintsTests.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.beans.factory.annotation; - - -import jakarta.inject.Inject; -import jakarta.inject.Qualifier; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; -import org.springframework.beans.factory.aot.AotServices; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link JakartaAnnotationsRuntimeHints}. - * - * @author Brian Clozel - */ -class JakartaAnnotationsRuntimeHintsTests { - - private final RuntimeHints hints = new RuntimeHints(); - - @BeforeEach - void setup() { - AotServices.factories().load(RuntimeHintsRegistrar.class) - .forEach(registrar -> registrar.registerHints(this.hints, - ClassUtils.getDefaultClassLoader())); - } - - @Test - void jakartaInjectAnnotationHasHints() { - assertThat(RuntimeHintsPredicates.reflection().onType(Inject.class) - .withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints); - } - - @Test - void jakartaQualifierAnnotationHasHints() { - assertThat(RuntimeHintsPredicates.reflection().onType(Qualifier.class) - .withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints); - } - -} diff --git a/spring-context/src/main/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessor.java b/spring-context/src/main/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessor.java index 02a824ae887..7080bcfb0cb 100644 --- a/spring-context/src/main/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessor.java @@ -142,9 +142,11 @@ class ReflectiveProcessorBeanRegistrationAotProcessor implements BeanRegistratio } private void registerAnnotationIfNecessary(RuntimeHints hints, AnnotatedElement element) { - MergedAnnotation reflectiveAnnotation = MergedAnnotations.from(element).get(Reflective.class); - if (reflectiveAnnotation.getDistance() > 0) { - RuntimeHintsUtils.registerAnnotation(hints, reflectiveAnnotation.getRoot().getType()); + MergedAnnotation reflectiveAnnotation = MergedAnnotations.from(element) + .get(Reflective.class); + MergedAnnotation metaSource = reflectiveAnnotation.getMetaSource(); + if (metaSource != null) { + RuntimeHintsUtils.registerAnnotationIfNecessary(hints, metaSource); } } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java index 25e4e9d3f32..4b3cd9415c6 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/AsyncAnnotationBeanPostProcessor.java @@ -25,14 +25,9 @@ import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.autoproxy.AbstractBeanFactoryAwareAdvisingPostProcessor; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.aot.hint.support.RuntimeHintsUtils; import org.springframework.beans.factory.BeanFactory; -import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.core.task.TaskExecutor; import org.springframework.lang.Nullable; -import org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor.AsyncAnnotationRuntimeHints; import org.springframework.util.Assert; import org.springframework.util.function.SingletonSupplier; @@ -67,7 +62,6 @@ import org.springframework.util.function.SingletonSupplier; * @see ScheduledAnnotationBeanPostProcessor */ @SuppressWarnings("serial") -@ImportRuntimeHints(AsyncAnnotationRuntimeHints.class) public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor { /** @@ -160,13 +154,4 @@ public class AsyncAnnotationBeanPostProcessor extends AbstractBeanFactoryAwareAd this.advisor = advisor; } - static class AsyncAnnotationRuntimeHints implements RuntimeHintsRegistrar { - - @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - RuntimeHintsUtils.registerAnnotation(hints, Async.class); - } - - } - } diff --git a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java index cb842bbf7f8..facf5323d03 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/scheduling/annotation/ScheduledAnnotationBeanPostProcessor.java @@ -37,9 +37,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.aop.support.AopUtils; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.aot.hint.support.RuntimeHintsUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; @@ -58,7 +55,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; import org.springframework.context.EmbeddedValueResolverAware; -import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.MethodIntrospector; import org.springframework.core.Ordered; @@ -68,7 +64,6 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.lang.Nullable; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.Trigger; -import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.ScheduledAnnotationsRuntimeHints; import org.springframework.scheduling.config.CronTask; import org.springframework.scheduling.config.FixedDelayTask; import org.springframework.scheduling.config.FixedRateTask; @@ -111,7 +106,6 @@ import org.springframework.util.StringValueResolver; * @see org.springframework.scheduling.config.ScheduledTaskRegistrar * @see AsyncAnnotationBeanPostProcessor */ -@ImportRuntimeHints(ScheduledAnnotationsRuntimeHints.class) public class ScheduledAnnotationBeanPostProcessor implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor, Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware, @@ -611,14 +605,4 @@ public class ScheduledAnnotationBeanPostProcessor this.registrar.destroy(); } - static class ScheduledAnnotationsRuntimeHints implements RuntimeHintsRegistrar { - - @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - RuntimeHintsUtils.registerAnnotation(hints, Scheduled.class); - RuntimeHintsUtils.registerAnnotation(hints, Schedules.class); - } - - } - } diff --git a/spring-context/src/test/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessorTests.java b/spring-context/src/test/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessorTests.java index 5f2dd666c91..cf502e01175 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessorTests.java @@ -25,7 +25,6 @@ import java.lang.annotation.Target; import org.junit.jupiter.api.Test; import org.springframework.aot.generate.GenerationContext; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeReference; import org.springframework.aot.hint.annotation.Reflective; @@ -93,19 +92,18 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests { } @Test - void shouldRegisterAnnotation() { + void shouldNotRegisterAnnotationProxyIfNotNeeded() { process(SampleMethodMetaAnnotatedBean.class); RuntimeHints runtimeHints = this.generationContext.getRuntimeHints(); - assertThat(RuntimeHintsPredicates.reflection().onType(SampleInvoker.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(runtimeHints); assertThat(runtimeHints.proxies().jdkProxies()).isEmpty(); } @Test - void shouldRegisterAnnotationAndProxyWithAliasFor() { + void shouldRegisterAnnotationProxy() { process(SampleMethodMetaAnnotatedBeanWithAlias.class); RuntimeHints runtimeHints = this.generationContext.getRuntimeHints(); - assertThat(RuntimeHintsPredicates.reflection().onType(RetryInvoker.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(runtimeHints); - assertThat(RuntimeHintsPredicates.proxies().forInterfaces(RetryInvoker.class, SynthesizedAnnotation.class)).accepts(runtimeHints); + assertThat(RuntimeHintsPredicates.proxies().forInterfaces( + SampleInvoker.class, SynthesizedAnnotation.class)).accepts(runtimeHints); } @Test @@ -236,7 +234,7 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests { } - @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Reflective @@ -246,7 +244,7 @@ class ReflectiveProcessorBeanRegistrationAotProcessorTests { } - @Target({ElementType.METHOD}) + @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @SampleInvoker diff --git a/spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java b/spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java index 184536a8845..0dba252455e 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/annotation/Reflective.java @@ -32,6 +32,9 @@ import org.springframework.core.annotation.AliasFor; * the annotated element. By default, a reflection hint is added on the * annotated element so that it can be discovered and invoked if necessary. * + *

A reflection hint is also added if necessary on the annotation that + * directly uses this annotation. + * * @author Stephane Nicoll * @author Sam Brannen * @since 6.0 diff --git a/spring-core/src/main/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHints.java b/spring-core/src/main/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHints.java deleted file mode 100644 index 6b452a6706e..00000000000 --- a/spring-core/src/main/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHints.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.aot.hint.support; - -import java.util.stream.Stream; - -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.core.annotation.AliasFor; -import org.springframework.core.annotation.Order; -import org.springframework.lang.Nullable; - -/** - * {@link RuntimeHintsRegistrar} for core annotations. - * - * @author Phillip Webb - * @since 6.0 - */ -class CoreAnnotationsRuntimeHints implements RuntimeHintsRegistrar { - - @Override - public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { - Stream.of(AliasFor.class, Order.class).forEach(annotationType -> - RuntimeHintsUtils.registerAnnotation(hints, annotationType)); - } - -} diff --git a/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java b/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java index 0679bca86e2..916c9a49206 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java @@ -16,19 +16,9 @@ package org.springframework.aot.hint.support; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.function.Consumer; - -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.TypeHint; -import org.springframework.aot.hint.TypeHint.Builder; -import org.springframework.aot.hint.annotation.Reflective; import org.springframework.core.annotation.AliasFor; +import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.SynthesizedAnnotation; /** @@ -40,72 +30,48 @@ import org.springframework.core.annotation.SynthesizedAnnotation; */ public abstract class RuntimeHintsUtils { - /** - * A {@link TypeHint} customizer suitable for an annotation. Make sure - * that its attributes are visible. - */ - public static final Consumer ANNOTATION_HINT = hint -> - hint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS); - /** * Register the necessary hints so that the specified annotation is visible * at runtime. - *

If an annotation attribute aliases an attribute of another annotation, - * the other annotation is registered as well and a JDK proxy hint is defined - * so that the synthesized annotation can be resolved. + * @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 + public static void registerAnnotation(RuntimeHints hints, Class annotationType) { + registerSynthesizedAnnotation(hints, annotationType); + } + + /** + * Register the necessary hints so that the specified annotation can be + * synthesized at runtime if necessary. Such hints are usually required + * if any of the following apply: + *

+ * Consider using {@link #registerAnnotationIfNecessary(RuntimeHints, MergedAnnotation)} + * 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 registerAnnotation(RuntimeHints hints, Class annotationType) { - registerAnnotation(hints, annotationType, false); + public static void registerSynthesizedAnnotation(RuntimeHints hints, Class annotationType) { + hints.proxies().registerJdkProxy(annotationType, SynthesizedAnnotation.class); } /** - * Register the necessary hints so that the specified composable - * annotation is visible at runtime. Use this method rather than the regular - * {@link #registerAnnotation(RuntimeHints, Class)} when the specified - * annotation is meta-annotated, but the meta-annotated annotations do not - * need to be visible. + * Determine if the specified annotation can be synthesized at runtime, and + * register the necessary hints accordingly. * @param hints the {@link RuntimeHints} instance to use - * @param annotationType the composable annotation type - * @see #registerAnnotation(RuntimeHints, Class) + * @param annotation the annotation + * @see #registerSynthesizedAnnotation(RuntimeHints, Class) */ - public static void registerComposableAnnotation(RuntimeHints hints, Class annotationType) { - registerAnnotation(hints, annotationType, true); - } - - private static void registerAnnotation(RuntimeHints hints, Class annotationType, boolean withProxy) { - hints.reflection().registerType(annotationType, ANNOTATION_HINT); - Set> allAnnotations = new LinkedHashSet<>(); - collectAliasedAnnotations(new HashSet<>(), allAnnotations, annotationType); - allAnnotations.forEach(annotation -> { - hints.reflection().registerType(annotation, ANNOTATION_HINT); - hints.proxies().registerJdkProxy(annotation, SynthesizedAnnotation.class); - }); - if (!allAnnotations.isEmpty() || withProxy) { - hints.proxies().registerJdkProxy(annotationType, SynthesizedAnnotation.class); - } - } - - private static void collectAliasedAnnotations(Set> seen, Set> types, Class annotationType) { - if (seen.contains(annotationType) || AliasFor.class.equals(annotationType) || - Reflective.class.equals(annotationType)) { - return; - } - seen.add(annotationType); - for (Method method : annotationType.getDeclaredMethods()) { - AliasFor aliasFor = method.getAnnotation(AliasFor.class); - if (aliasFor != null) { - Class annotationAttribute = aliasFor.annotation(); - Class targetAnnotation = (annotationAttribute != Annotation.class - ? annotationAttribute : annotationType); - if (types.add(targetAnnotation)) { - if (!targetAnnotation.equals(annotationType)) { - collectAliasedAnnotations(seen, types, targetAnnotation); - } - } - } + public static void registerAnnotationIfNecessary(RuntimeHints hints, MergedAnnotation annotation) { + if (annotation.isSynthesizable()) { + registerSynthesizedAnnotation(hints, annotation.getType()); } } diff --git a/spring-core/src/main/resources/META-INF/spring/aot.factories b/spring-core/src/main/resources/META-INF/spring/aot.factories index 5a4fe9170cc..e1e6672e561 100644 --- a/spring-core/src/main/resources/META-INF/spring/aot.factories +++ b/spring-core/src/main/resources/META-INF/spring/aot.factories @@ -1,3 +1,2 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\ -org.springframework.aot.hint.support.CoreAnnotationsRuntimeHints,\ org.springframework.aot.hint.support.SpringFactoriesLoaderRuntimeHints diff --git a/spring-core/src/test/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHintsTests.java deleted file mode 100644 index 704465d1c74..00000000000 --- a/spring-core/src/test/java/org/springframework/aot/hint/support/CoreAnnotationsRuntimeHintsTests.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.aot.hint.support; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; -import org.springframework.core.annotation.AliasFor; -import org.springframework.core.annotation.Order; -import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link CoreAnnotationsRuntimeHints}. - * - * @author Phillip Webb - */ -class CoreAnnotationsRuntimeHintsTests { - - private final RuntimeHints hints = new RuntimeHints(); - - @BeforeEach - void setup() { - SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories") - .load(RuntimeHintsRegistrar.class).forEach(registrar -> registrar - .registerHints(this.hints, ClassUtils.getDefaultClassLoader())); - } - - @Test - void aliasForHasHints() { - assertThat(RuntimeHintsPredicates.reflection().onType(AliasFor.class) - .withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints); - } - - @Test - void orderAnnotationHasHints() { - assertThat(RuntimeHintsPredicates.reflection().onType(Order.class) - .withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(this.hints); - } - -} diff --git a/spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java index b4345a91eaf..919ac575309 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java @@ -23,11 +23,11 @@ import java.util.function.Consumer; import org.junit.jupiter.api.Test; import org.springframework.aot.hint.JdkProxyHint; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.TypeHint; import org.springframework.aot.hint.TypeReference; import org.springframework.core.annotation.AliasFor; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.SynthesizedAnnotation; import static org.assertj.core.api.Assertions.assertThat; @@ -43,75 +43,45 @@ class RuntimeHintsUtilsTests { private final RuntimeHints hints = new RuntimeHints(); @Test - void registerAnnotationType() { - RuntimeHintsUtils.registerAnnotation(this.hints, SampleInvoker.class); - assertThat(this.hints.reflection().typeHints()).singleElement() - .satisfies(annotationHint(SampleInvoker.class)); - assertThat(this.hints.proxies().jdkProxies()).isEmpty(); - } - - @Test - void registerComposableAnnotationType() { - RuntimeHintsUtils.registerComposableAnnotation(this.hints, SampleInvoker.class); - assertThat(this.hints.reflection().typeHints()).singleElement() - .satisfies(annotationHint(SampleInvoker.class)); + void registerSynthesizedAnnotation() { + RuntimeHintsUtils.registerSynthesizedAnnotation(this.hints, SampleInvoker.class); assertThat(this.hints.proxies().jdkProxies()).singleElement() .satisfies(annotationProxy(SampleInvoker.class)); } @Test - void registerAnnotationTypeWithLocalUseOfAliasForRegistersProxy() { - RuntimeHintsUtils.registerAnnotation(this.hints, LocalMapping.class); - assertThat(this.hints.reflection().typeHints()).singleElement() - .satisfies(annotationHint(LocalMapping.class)); + void registerAnnotationIfNecessaryWithNonSynthesizedAnnotation() throws NoSuchFieldException { + MergedAnnotation annotation = MergedAnnotations + .from(TestBean.class.getField("sampleInvoker")).get(SampleInvoker.class); + RuntimeHintsUtils.registerAnnotationIfNecessary(this.hints, annotation); + assertThat(this.hints.proxies().jdkProxies()).isEmpty(); + } + + @Test + void registerAnnotationIfNecessaryWithLocalAliases() throws NoSuchFieldException { + MergedAnnotation annotation = MergedAnnotations + .from(TestBean.class.getField("localMapping")).get(LocalMapping.class); + RuntimeHintsUtils.registerAnnotationIfNecessary(this.hints, annotation); assertThat(this.hints.proxies().jdkProxies()).singleElement() .satisfies(annotationProxy(LocalMapping.class)); } @Test - void registerAnnotationTypeProxyRegistersJdkProxies() { - RuntimeHintsUtils.registerAnnotation(this.hints, RetryInvoker.class); - assertThat(this.hints.reflection().typeHints()) - .anySatisfy(annotationHint(RetryInvoker.class)) - .anySatisfy(annotationHint(SampleInvoker.class)) - .hasSize(2); - assertThat(this.hints.proxies().jdkProxies()) - .anySatisfy(annotationProxy(RetryInvoker.class)) - .anySatisfy(annotationProxy(SampleInvoker.class)) - .hasSize(2); - } - - @Test // gh-28953 - void registerAnnotationForAliasForShouldNotRegisterSynthesizedAnnotationProxy() { - RuntimeHintsUtils.registerAnnotation(this.hints, AliasFor.class); - assertThat(this.hints.reflection().typeHints()).singleElement() - .satisfies(annotationHint(AliasFor.class)); - assertThat(this.hints.proxies().jdkProxies()).isEmpty(); + void registerAnnotationIfNecessaryWithMetaAttributeOverride() throws NoSuchFieldException { + MergedAnnotation annotation = MergedAnnotations + .from(TestBean.class.getField("retryInvoker")).get(SampleInvoker.class); + RuntimeHintsUtils.registerAnnotationIfNecessary(this.hints, annotation); + assertThat(this.hints.proxies().jdkProxies()).singleElement() + .satisfies(annotationProxy(SampleInvoker.class)); } @Test - void registerAnnotationTypeWhereUsedAsAMetaAnnotationRegistersHierarchy() { - RuntimeHintsUtils.registerAnnotation(this.hints, RetryWithEnabledFlagInvoker.class); - assertThat(this.hints.reflection().typeHints()) - .anySatisfy(annotationHint(RetryWithEnabledFlagInvoker.class)) - .anySatisfy(annotationHint(RetryInvoker.class)) - .anySatisfy(annotationHint(SampleInvoker.class)) - .hasSize(3); - assertThat(this.hints.proxies().jdkProxies()) - .anySatisfy(annotationProxy(RetryWithEnabledFlagInvoker.class)) - .anySatisfy(annotationProxy(RetryInvoker.class)) - .anySatisfy(annotationProxy(SampleInvoker.class)) - .hasSize(3); - } - - private Consumer annotationHint(Class type) { - return typeHint -> { - assertThat(typeHint.getType()).isEqualTo(TypeReference.of(type)); - assertThat(typeHint.constructors()).isEmpty(); - assertThat(typeHint.fields()).isEmpty(); - assertThat(typeHint.methods()).isEmpty(); - assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS); - }; + void registerAnnotationIfNecessaryWithSynthesizedAttribute() throws NoSuchFieldException { + MergedAnnotation annotation = MergedAnnotations + .from(TestBean.class.getField("retryContainer")).get(RetryContainer.class); + RuntimeHintsUtils.registerAnnotationIfNecessary(this.hints, annotation); + assertThat(this.hints.proxies().jdkProxies()).singleElement() + .satisfies(annotationProxy(RetryContainer.class)); } private Consumer annotationProxy(Class type) { @@ -120,6 +90,22 @@ class RuntimeHintsUtilsTests { } + static class TestBean { + + @SampleInvoker + public String sampleInvoker; + + @LocalMapping + public String localMapping; + + @RetryInvoker + public String retryInvoker; + + @RetryContainer(retry = @RetryInvoker(3)) + public String retryContainer; + + } + @Retention(RetentionPolicy.RUNTIME) @interface LocalMapping { @@ -149,13 +135,9 @@ class RuntimeHintsUtilsTests { } @Retention(RetentionPolicy.RUNTIME) - @RetryInvoker - @interface RetryWithEnabledFlagInvoker { + @interface RetryContainer { - @AliasFor(annotation = RetryInvoker.class) - int value() default 5; - - boolean enabled() default true; + RetryInvoker retry(); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessagingAnnotationsRuntimeHints.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessagingAnnotationsRuntimeHints.java index 28ba86cfa0a..5d0dc97978f 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessagingAnnotationsRuntimeHints.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessagingAnnotationsRuntimeHints.java @@ -35,8 +35,7 @@ public class MessagingAnnotationsRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { - Stream.of(Controller.class, DestinationVariable.class, Header.class, Headers.class, - MessageExceptionHandler.class, MessageMapping.class, Payload.class, SendTo.class).forEach( - annotationType -> RuntimeHintsUtils.registerAnnotation(hints, annotationType)); + Stream.of(Controller.class, Header.class, Headers.class, Payload.class).forEach(annotationType -> + RuntimeHintsUtils.registerSynthesizedAnnotation(hints, annotationType)); } } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SimpAnnotationsRuntimeHints.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SimpAnnotationsRuntimeHints.java index f5d262cf6c2..1ea02f7b0c3 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SimpAnnotationsRuntimeHints.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/SimpAnnotationsRuntimeHints.java @@ -16,8 +16,6 @@ package org.springframework.messaging.simp.annotation; -import java.util.stream.Stream; - import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.aot.hint.support.RuntimeHintsUtils; @@ -33,7 +31,6 @@ public class SimpAnnotationsRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - Stream.of(SendToUser.class, SubscribeMapping.class).forEach( - annotationType -> RuntimeHintsUtils.registerAnnotation(hints, annotationType)); + RuntimeHintsUtils.registerSynthesizedAnnotation(hints, SendToUser.class); } } diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionRuntimeHints.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionRuntimeHints.java index 2bad3f7585e..8f6ad94bd9d 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionRuntimeHints.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionRuntimeHints.java @@ -37,7 +37,7 @@ class TransactionRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - RuntimeHintsUtils.registerAnnotation(hints, Transactional.class); + RuntimeHintsUtils.registerSynthesizedAnnotation(hints, Transactional.class); hints.reflection() .registerTypes(List.of( TypeReference.of(Isolation.class), diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/WebAnnotationsRuntimeHintsRegistrar.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/WebAnnotationsRuntimeHintsRegistrar.java index 3e6e3e2b5f4..df8886f40df 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/WebAnnotationsRuntimeHintsRegistrar.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/WebAnnotationsRuntimeHintsRegistrar.java @@ -36,15 +36,12 @@ public final class WebAnnotationsRuntimeHintsRegistrar implements RuntimeHintsRe @Override public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { Stream.of(Controller.class, ControllerAdvice.class, CookieValue.class, - CrossOrigin.class, DeleteMapping.class, ExceptionHandler.class, - GetMapping.class, InitBinder.class, Mapping.class, MatrixVariable.class, - ModelAttribute.class, PatchMapping.class, PathVariable.class, - PostMapping.class, PutMapping.class, RequestAttribute.class, - RequestBody.class, RequestHeader.class, RequestMapping.class, - RequestParam.class, RequestPart.class, ResponseBody.class, - ResponseStatus.class, RestController.class, RestControllerAdvice.class, - SessionAttribute.class, SessionAttributes.class).forEach( - annotationType -> RuntimeHintsUtils.registerAnnotation(hints, annotationType)); + CrossOrigin.class, MatrixVariable.class, ModelAttribute.class, + PathVariable.class, RequestAttribute.class, RequestHeader.class, + RequestMapping.class, RequestParam.class, RequestPart.class, + ResponseStatus.class, SessionAttribute.class, SessionAttributes.class) + .forEach(annotationType -> + RuntimeHintsUtils.registerSynthesizedAnnotation(hints, annotationType)); } }