Skip to content

Commit 2517c72

Browse files
committed
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
1 parent 9181ac7 commit 2517c72

File tree

5 files changed

+181
-11
lines changed

5 files changed

+181
-11
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aot.hint.support;
18+
19+
import java.util.function.Consumer;
20+
21+
import org.springframework.aot.hint.MemberCategory;
22+
import org.springframework.aot.hint.RuntimeHints;
23+
import org.springframework.aot.hint.TypeHint;
24+
import org.springframework.aot.hint.TypeHint.Builder;
25+
import org.springframework.core.annotation.MergedAnnotation;
26+
import org.springframework.core.annotation.SynthesizedAnnotation;
27+
28+
/**
29+
* Utility methods for runtime hints support code.
30+
*
31+
* @author Stephane Nicoll
32+
* @since 6.0
33+
*/
34+
public abstract class RuntimeHintsUtils {
35+
36+
/**
37+
* A {@link TypeHint} customizer suitable for an annotation. Make sure
38+
* that its attributes are visible.
39+
*/
40+
public static final Consumer<Builder> ANNOTATION_HINT = hint ->
41+
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
42+
43+
/**
44+
* Register the necessary hints so that the specified annotation is visible
45+
* at runtime.
46+
* @param hints the {@link RuntimeHints} instance ot use
47+
* @param annotation the annotation
48+
* @see SynthesizedAnnotation
49+
*/
50+
public static void registerAnnotation(RuntimeHints hints, MergedAnnotation<?> annotation) {
51+
registerAnnotation(hints, annotation.getType(),
52+
annotation.synthesize() instanceof SynthesizedAnnotation);
53+
}
54+
55+
private static void registerAnnotation(RuntimeHints hints, Class<?> annotationType, boolean withProxy) {
56+
hints.reflection().registerType(annotationType, ANNOTATION_HINT);
57+
if (withProxy) {
58+
hints.proxies().registerJdkProxy(annotationType, SynthesizedAnnotation.class);
59+
}
60+
}
61+
62+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Convenience classes for using runtime hints.
3+
*/
4+
@NonNullApi
5+
@NonNullFields
6+
package org.springframework.aot.hint.support;
7+
8+
import org.springframework.lang.NonNullApi;
9+
import org.springframework.lang.NonNullFields;

spring-core/src/main/java/org/springframework/core/annotation/CoreAnnotationsRuntimeHintsRegistrar.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,9 @@
1616

1717
package org.springframework.core.annotation;
1818

19-
import java.util.function.Consumer;
20-
21-
import org.springframework.aot.hint.MemberCategory;
2219
import org.springframework.aot.hint.RuntimeHints;
2320
import org.springframework.aot.hint.RuntimeHintsRegistrar;
24-
import org.springframework.aot.hint.TypeHint;
21+
import org.springframework.aot.hint.support.RuntimeHintsUtils;
2522

2623
/**
2724
* {@link RuntimeHintsRegistrar} for core annotations.
@@ -31,13 +28,10 @@
3128
*/
3229
class CoreAnnotationsRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
3330

34-
private static final Consumer<TypeHint.Builder> HINT = builder -> builder.withMembers(
35-
MemberCategory.INVOKE_DECLARED_METHODS);
36-
3731
@Override
3832
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
39-
hints.reflection().registerType(AliasFor.class, HINT);
40-
hints.reflection().registerType(Order.class, HINT);
33+
hints.reflection().registerType(AliasFor.class, RuntimeHintsUtils.ANNOTATION_HINT)
34+
.registerType(Order.class, RuntimeHintsUtils.ANNOTATION_HINT);
4135
}
4236

4337
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.aot.hint.support;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.junit.jupiter.api.Test;
26+
27+
import org.springframework.aot.hint.MemberCategory;
28+
import org.springframework.aot.hint.RuntimeHints;
29+
import org.springframework.aot.hint.TypeReference;
30+
import org.springframework.core.annotation.AliasFor;
31+
import org.springframework.core.annotation.MergedAnnotations;
32+
import org.springframework.core.annotation.SynthesizedAnnotation;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
36+
/**
37+
* Tests for {@link RuntimeHintsUtils}.
38+
*
39+
* @author Stephane Nicoll
40+
*/
41+
class RuntimeHintsUtilsTests {
42+
43+
private final RuntimeHints hints = new RuntimeHints();
44+
45+
@Test
46+
void registerAnnotation() {
47+
RuntimeHintsUtils.registerAnnotation(this.hints, MergedAnnotations
48+
.from(SampleInvokerClass.class).get(SampleInvoker.class));
49+
assertThat(this.hints.reflection().getTypeHint(SampleInvoker.class)).satisfies(typeHint -> {
50+
assertThat(typeHint.constructors()).isEmpty();
51+
assertThat(typeHint.fields()).isEmpty();
52+
assertThat(typeHint.methods()).isEmpty();
53+
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS);
54+
});
55+
assertThat(this.hints.proxies().jdkProxies()).isEmpty();
56+
}
57+
58+
@Test
59+
void registerAnnotationProxyRegistersJdkProxy() {
60+
RuntimeHintsUtils.registerAnnotation(this.hints, MergedAnnotations
61+
.from(RetryInvokerClass.class).get(RetryInvoker.class));
62+
assertThat(this.hints.reflection().getTypeHint(RetryInvoker.class)).satisfies(typeHint -> {
63+
assertThat(typeHint.constructors()).isEmpty();
64+
assertThat(typeHint.fields()).isEmpty();
65+
assertThat(typeHint.methods()).isEmpty();
66+
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_PUBLIC_METHODS);
67+
});
68+
assertThat(this.hints.proxies().jdkProxies()).anySatisfy(jdkProxyHint ->
69+
assertThat(jdkProxyHint.getProxiedInterfaces()).containsExactly(
70+
TypeReference.of(RetryInvoker.class), TypeReference.of(SynthesizedAnnotation.class)));
71+
}
72+
73+
74+
@SampleInvoker
75+
static class SampleInvokerClass {
76+
77+
}
78+
79+
@RetryInvoker
80+
static class RetryInvokerClass {
81+
82+
}
83+
84+
85+
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
86+
@Retention(RetentionPolicy.RUNTIME)
87+
@Documented
88+
@interface SampleInvoker {
89+
90+
int retries() default 0;
91+
92+
}
93+
94+
@Target({ ElementType.TYPE })
95+
@Retention(RetentionPolicy.RUNTIME)
96+
@Documented
97+
@SampleInvoker
98+
@interface RetryInvoker {
99+
100+
@AliasFor(attribute = "retries", annotation = SampleInvoker.class)
101+
int value() default 1;
102+
103+
}
104+
105+
}

spring-core/src/test/java/org/springframework/core/annotation/CoreAnnotationsRuntimeHintsRegistrarTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ void setup() {
4949
void aliasForHasHints() {
5050
assertThat(this.hints.reflection().getTypeHint(TypeReference.of(AliasFor.class)))
5151
.satisfies(hint -> assertThat(hint.getMemberCategories())
52-
.containsExactly(MemberCategory.INVOKE_DECLARED_METHODS));
52+
.containsExactly(MemberCategory.INVOKE_PUBLIC_METHODS));
5353
}
5454

5555
@Test
5656
void orderAnnotationHasHints() {
5757
assertThat(this.hints.reflection().getTypeHint(TypeReference.of(Order.class)))
5858
.satisfies(hint -> assertThat(hint.getMemberCategories())
59-
.containsExactly(MemberCategory.INVOKE_DECLARED_METHODS));
59+
.containsExactly(MemberCategory.INVOKE_PUBLIC_METHODS));
6060
}
6161

6262
}

0 commit comments

Comments
 (0)