From 2517c72f7d2a7406137c25c9f63ccdd35c082979 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 20 May 2022 14:48:06 +0200 Subject: [PATCH] Add a common utility to register hints for an annotation This commit adds a utility that takes care of registering the necessary hints to make an annotation visible at runtime. The core framework may create a JDK proxy if necessary, which requires specific handling. Closes gh-28497 --- .../aot/hint/support/RuntimeHintsUtils.java | 62 +++++++++++ .../aot/hint/support/package-info.java | 9 ++ .../CoreAnnotationsRuntimeHintsRegistrar.java | 12 +- .../hint/support/RuntimeHintsUtilsTests.java | 105 ++++++++++++++++++ ...AnnotationsRuntimeHintsRegistrarTests.java | 4 +- 5 files changed, 181 insertions(+), 11 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java create mode 100644 spring-core/src/main/java/org/springframework/aot/hint/support/package-info.java create mode 100644 spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java 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 new file mode 100644 index 0000000000..f28c976e30 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java @@ -0,0 +1,62 @@ +/* + * 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.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.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.SynthesizedAnnotation; + +/** + * Utility methods for runtime hints support code. + * + * @author Stephane Nicoll + * @since 6.0 + */ +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_PUBLIC_METHODS); + + /** + * Register the necessary hints so that the specified annotation is visible + * at runtime. + * @param hints the {@link RuntimeHints} instance ot use + * @param annotation the annotation + * @see SynthesizedAnnotation + */ + public static void registerAnnotation(RuntimeHints hints, MergedAnnotation annotation) { + registerAnnotation(hints, annotation.getType(), + annotation.synthesize() instanceof SynthesizedAnnotation); + } + + private static void registerAnnotation(RuntimeHints hints, Class annotationType, boolean withProxy) { + hints.reflection().registerType(annotationType, ANNOTATION_HINT); + if (withProxy) { + hints.proxies().registerJdkProxy(annotationType, SynthesizedAnnotation.class); + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/aot/hint/support/package-info.java b/spring-core/src/main/java/org/springframework/aot/hint/support/package-info.java new file mode 100644 index 0000000000..87ce610cfe --- /dev/null +++ b/spring-core/src/main/java/org/springframework/aot/hint/support/package-info.java @@ -0,0 +1,9 @@ +/** + * Convenience classes for using runtime hints. + */ +@NonNullApi +@NonNullFields +package org.springframework.aot.hint.support; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-core/src/main/java/org/springframework/core/annotation/CoreAnnotationsRuntimeHintsRegistrar.java b/spring-core/src/main/java/org/springframework/core/annotation/CoreAnnotationsRuntimeHintsRegistrar.java index 53fc59a692..4e15a6ae92 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/CoreAnnotationsRuntimeHintsRegistrar.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/CoreAnnotationsRuntimeHintsRegistrar.java @@ -16,12 +16,9 @@ package org.springframework.core.annotation; -import java.util.function.Consumer; - -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.aot.hint.TypeHint; +import org.springframework.aot.hint.support.RuntimeHintsUtils; /** * {@link RuntimeHintsRegistrar} for core annotations. @@ -31,13 +28,10 @@ import org.springframework.aot.hint.TypeHint; */ class CoreAnnotationsRuntimeHintsRegistrar implements RuntimeHintsRegistrar { - private static final Consumer HINT = builder -> builder.withMembers( - MemberCategory.INVOKE_DECLARED_METHODS); - @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - hints.reflection().registerType(AliasFor.class, HINT); - hints.reflection().registerType(Order.class, HINT); + hints.reflection().registerType(AliasFor.class, RuntimeHintsUtils.ANNOTATION_HINT) + .registerType(Order.class, RuntimeHintsUtils.ANNOTATION_HINT); } } 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 new file mode 100644 index 0000000000..a2547635fe --- /dev/null +++ b/spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java @@ -0,0 +1,105 @@ +/* + * 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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.core.annotation.AliasFor; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.SynthesizedAnnotation; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link RuntimeHintsUtils}. + * + * @author Stephane Nicoll + */ +class RuntimeHintsUtilsTests { + + private final RuntimeHints hints = new RuntimeHints(); + + @Test + void registerAnnotation() { + RuntimeHintsUtils.registerAnnotation(this.hints, MergedAnnotations + .from(SampleInvokerClass.class).get(SampleInvoker.class)); + assertThat(this.hints.reflection().getTypeHint(SampleInvoker.class)).satisfies(typeHint -> { + assertThat(typeHint.constructors()).isEmpty(); + assertThat(typeHint.fields()).isEmpty(); + assertThat(typeHint.methods()).isEmpty(); + assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS); + }); + assertThat(this.hints.proxies().jdkProxies()).isEmpty(); + } + + @Test + void registerAnnotationProxyRegistersJdkProxy() { + RuntimeHintsUtils.registerAnnotation(this.hints, MergedAnnotations + .from(RetryInvokerClass.class).get(RetryInvoker.class)); + assertThat(this.hints.reflection().getTypeHint(RetryInvoker.class)).satisfies(typeHint -> { + assertThat(typeHint.constructors()).isEmpty(); + assertThat(typeHint.fields()).isEmpty(); + assertThat(typeHint.methods()).isEmpty(); + assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS); + }); + assertThat(this.hints.proxies().jdkProxies()).anySatisfy(jdkProxyHint -> + assertThat(jdkProxyHint.getProxiedInterfaces()).containsExactly( + TypeReference.of(RetryInvoker.class), TypeReference.of(SynthesizedAnnotation.class))); + } + + + @SampleInvoker + static class SampleInvokerClass { + + } + + @RetryInvoker + static class RetryInvokerClass { + + } + + + @Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @interface SampleInvoker { + + int retries() default 0; + + } + + @Target({ ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @SampleInvoker + @interface RetryInvoker { + + @AliasFor(attribute = "retries", annotation = SampleInvoker.class) + int value() default 1; + + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/annotation/CoreAnnotationsRuntimeHintsRegistrarTests.java b/spring-core/src/test/java/org/springframework/core/annotation/CoreAnnotationsRuntimeHintsRegistrarTests.java index 683837f6f0..9e87a4ddfe 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/CoreAnnotationsRuntimeHintsRegistrarTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/CoreAnnotationsRuntimeHintsRegistrarTests.java @@ -49,14 +49,14 @@ class CoreAnnotationsRuntimeHintsRegistrarTests { void aliasForHasHints() { assertThat(this.hints.reflection().getTypeHint(TypeReference.of(AliasFor.class))) .satisfies(hint -> assertThat(hint.getMemberCategories()) - .containsExactly(MemberCategory.INVOKE_DECLARED_METHODS)); + .containsExactly(MemberCategory.INVOKE_PUBLIC_METHODS)); } @Test void orderAnnotationHasHints() { assertThat(this.hints.reflection().getTypeHint(TypeReference.of(Order.class))) .satisfies(hint -> assertThat(hint.getMemberCategories()) - .containsExactly(MemberCategory.INVOKE_DECLARED_METHODS)); + .containsExactly(MemberCategory.INVOKE_PUBLIC_METHODS)); } }