Skip to content

Commit 9c9b235

Browse files
committed
Add RuntimeHints predicates generator
The `RuntimeHints` API allows to describe hints for the reflection, proxies and resources behavior at runtime. The need for a particular behavior can be covered by several types of hints, at different levels. This knowledge can be important in several cases: * before contributing additional hints, infrastructure can check if an existing hint already covers the behavior * this can be used in test suites and test infrastructure This commit adds a new RuntimeHintsPredicates that generates `Predicate` instances for testing `RuntimeHints` against a desired runtime behavior for reflection, resources or proxies. Closes gh-28555
1 parent 100ce96 commit 9c9b235

File tree

13 files changed

+1318
-55
lines changed

13 files changed

+1318
-55
lines changed

spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import org.junit.jupiter.api.Test;
2929

3030
import org.springframework.aot.generate.GeneratedMethods;
31-
import org.springframework.aot.hint.ExecutableMode;
3231
import org.springframework.aot.hint.RuntimeHints;
32+
import org.springframework.aot.hint.RuntimeHintsPredicates;
3333
import org.springframework.aot.test.generator.compile.Compiled;
3434
import org.springframework.aot.test.generator.compile.TestCompiler;
3535
import org.springframework.beans.factory.config.BeanDefinition;
@@ -277,15 +277,8 @@ void setDestroyMethodWhenMultipleDestroyMethods() {
277277
}
278278

279279
private void assertHasMethodInvokeHints(Class<?> beanType, String... methodNames) {
280-
assertThat(this.hints.reflection().getTypeHint(beanType)).satisfies(typeHint -> {
281-
for (String methodName : methodNames) {
282-
assertThat(typeHint.methods()).anySatisfy(methodHint -> {
283-
assertThat(methodHint.getName()).isEqualTo(methodName);
284-
assertThat(methodHint.getModes())
285-
.containsExactly(ExecutableMode.INVOKE);
286-
});
287-
}
288-
});
280+
assertThat(methodNames).allMatch(methodName ->
281+
RuntimeHintsPredicates.reflection().onMethod(beanType, methodName).invoke().test(this.hints));
289282
}
290283

291284
@Test

spring-context/src/test/java/org/springframework/context/aot/ReflectiveProcessorBeanRegistrationAotProcessorTests.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.aot.generate.InMemoryGeneratedFiles;
3030
import org.springframework.aot.hint.MemberCategory;
3131
import org.springframework.aot.hint.RuntimeHints;
32+
import org.springframework.aot.hint.RuntimeHintsPredicates;
3233
import org.springframework.aot.hint.TypeReference;
3334
import org.springframework.aot.hint.annotation.Reflective;
3435
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
@@ -96,20 +97,16 @@ void shouldProcessAnnotationOnMethod() {
9697
void shouldRegisterAnnotation() {
9798
process(SampleMethodMetaAnnotatedBean.class);
9899
RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
99-
assertThat(runtimeHints.reflection().getTypeHint(SampleInvoker.class)).satisfies(typeHint ->
100-
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS));
100+
assertThat(RuntimeHintsPredicates.reflection().onType(SampleInvoker.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(runtimeHints);
101101
assertThat(runtimeHints.proxies().jdkProxies()).isEmpty();
102102
}
103103

104104
@Test
105105
void shouldRegisterAnnotationAndProxyWithAliasFor() {
106106
process(SampleMethodMetaAnnotatedBeanWithAlias.class);
107107
RuntimeHints runtimeHints = this.generationContext.getRuntimeHints();
108-
assertThat(runtimeHints.reflection().getTypeHint(RetryInvoker.class)).satisfies(typeHint ->
109-
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS));
110-
assertThat(runtimeHints.proxies().jdkProxies()).anySatisfy(jdkProxyHint ->
111-
assertThat(jdkProxyHint.getProxiedInterfaces()).containsExactly(
112-
TypeReference.of(RetryInvoker.class), TypeReference.of(SynthesizedAnnotation.class)));
108+
assertThat(RuntimeHintsPredicates.reflection().onType(RetryInvoker.class).withMemberCategory(MemberCategory.INVOKE_DECLARED_METHODS)).accepts(runtimeHints);
109+
assertThat(RuntimeHintsPredicates.proxies().forInterfaces(RetryInvoker.class, SynthesizedAnnotation.class)).accepts(runtimeHints);
113110
}
114111

115112
@Nullable
@@ -196,7 +193,7 @@ void notManaged() {
196193

197194
}
198195

199-
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
196+
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
200197
@Retention(RetentionPolicy.RUNTIME)
201198
@Documented
202199
@Reflective
@@ -206,7 +203,7 @@ void notManaged() {
206203

207204
}
208205

209-
@Target({ ElementType.METHOD })
206+
@Target({ElementType.METHOD})
210207
@Retention(RetentionPolicy.RUNTIME)
211208
@Documented
212209
@SampleInvoker
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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;
18+
19+
import java.util.Arrays;
20+
import java.util.function.Predicate;
21+
22+
import org.springframework.util.Assert;
23+
24+
/**
25+
* Generator of {@link ProxyHints} predicates, testing whether the given hints
26+
* match the expected behavior for proxies.
27+
* @author Brian Clozel
28+
* @since 6.0
29+
*/
30+
public class ProxyHintsPredicates {
31+
32+
ProxyHintsPredicates() {
33+
}
34+
35+
/**
36+
* Return a predicate that checks whether a {@link org.springframework.aot.hint.JdkProxyHint}
37+
* is registered for the given interfaces.
38+
* <p>Note that the order in which interfaces are given matters.
39+
* @param interfaces the proxied interfaces
40+
* @return the {@link RuntimeHints} predicate
41+
* @see java.lang.reflect.Proxy
42+
*/
43+
public Predicate<RuntimeHints> forInterfaces(Class<?>... interfaces) {
44+
Assert.notEmpty(interfaces, "'interfaces' should not be empty");
45+
return forInterfaces(Arrays.stream(interfaces).map(TypeReference::of).toArray(TypeReference[]::new));
46+
}
47+
48+
/**
49+
* Return a predicate that checks whether a {@link org.springframework.aot.hint.JdkProxyHint}
50+
* is registered for the given interfaces.
51+
* <p>Note that the order in which interfaces are given matters.
52+
* @param interfaces the proxied interfaces as type references
53+
* @return the {@link RuntimeHints} predicate
54+
* @see java.lang.reflect.Proxy
55+
*/
56+
public Predicate<RuntimeHints> forInterfaces(TypeReference... interfaces) {
57+
Assert.notEmpty(interfaces, "'interfaces' should not be empty");
58+
return hints -> hints.proxies().jdkProxies().anyMatch(proxyHint ->
59+
proxyHint.getProxiedInterfaces().equals(Arrays.asList(interfaces)));
60+
}
61+
}

0 commit comments

Comments
 (0)