Introduce RegisterReflection
This commit introduces a declarative way of registering reflection information for arbitrary types. Types can be specified as a class, a class name, or by annotating the type itself. This existing RegisterReflectionForBinding becomes a specialized version of the new annotation, registering the necessary hints for data binding. Closes gh-29194
This commit is contained in:
@@ -25,6 +25,7 @@ import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.FieldHint;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
@@ -118,6 +119,18 @@ class ReflectiveRuntimeHintsRegistrarTests {
|
||||
.satisfies(methodHint -> assertThat(methodHint.getName()).isEqualTo("managed")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessDifferentAnnotationsOnTypeAndField() {
|
||||
process(SampleTypeAndFieldAnnotatedBean.class);
|
||||
assertThat(this.runtimeHints.reflection().getTypeHint(SampleTypeAndFieldAnnotatedBean.class))
|
||||
.satisfies(typeHint -> {
|
||||
assertThat(typeHint.fields().map(FieldHint::getName)).containsOnly("MESSAGE");
|
||||
assertThat(typeHint.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
|
||||
assertThat(typeHint.methods()).isEmpty();
|
||||
assertThat(typeHint.constructors()).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInvokeCustomProcessor() {
|
||||
process(SampleCustomProcessor.class);
|
||||
@@ -175,6 +188,14 @@ class ReflectiveRuntimeHintsRegistrarTests {
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)
|
||||
static class SampleTypeAndFieldAnnotatedBean {
|
||||
|
||||
@Reflective
|
||||
private static final String MESSAGE = "Hello";
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class SampleMethodMetaAnnotatedBean {
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* Copyright 2002-2024 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.
|
||||
@@ -28,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
* Tests for {@link RegisterReflectionForBindingProcessor}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class RegisterReflectionForBindingProcessorTests {
|
||||
|
||||
@@ -52,10 +53,11 @@ class RegisterReflectionForBindingProcessorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwExceptionWithoutAnnotationAttributeOnClass() {
|
||||
assertThatThrownBy(() -> processor.registerReflectionHints(hints.reflection(),
|
||||
SampleClassWithoutAnnotationAttribute.class))
|
||||
.isInstanceOf(IllegalStateException.class);
|
||||
void registerReflectionForBindingOnClassItself() {
|
||||
processor.registerReflectionHints(hints.reflection(), SampleClassWithoutAnnotationAttribute.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(SampleClassWithoutAnnotationAttribute.class)).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(String.class)).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onMethod(SampleClassWithoutAnnotationAttribute.class, "getName")).accepts(hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,12 +82,16 @@ class RegisterReflectionForBindingProcessorTests {
|
||||
static class SampleClassWithGetter {
|
||||
|
||||
public String getName() {
|
||||
return null;
|
||||
return "test";
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterReflectionForBinding
|
||||
static class SampleClassWithoutAnnotationAttribute {
|
||||
|
||||
public String getName() {
|
||||
return "test";
|
||||
}
|
||||
}
|
||||
|
||||
static class SampleClassWithoutMethodLevelAnnotationAttribute {
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2002-2024 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.annotation;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.ExecutableHint;
|
||||
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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link RegisterReflectionReflectiveProcessor}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class RegisterReflectionReflectiveProcessorTests {
|
||||
|
||||
private static final List<String> NO_METHODS = Collections.emptyList();
|
||||
|
||||
private final RegisterReflectionReflectiveProcessor processor = new RegisterReflectionReflectiveProcessor();
|
||||
|
||||
private final RuntimeHints hints = new RuntimeHints();
|
||||
|
||||
@Nested
|
||||
class AnnotatedTypeTests {
|
||||
|
||||
@Test
|
||||
void registerReflectionWithMemberCategory() {
|
||||
registerReflectionHints(RegistrationSimple.class);
|
||||
assertBasicTypeHint(SimplePojo.class, NO_METHODS, List.of(MemberCategory.INVOKE_PUBLIC_METHODS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerReflectionForMultipleTargets() {
|
||||
registerReflectionHints(RegistrationMultipleTargets.class);
|
||||
assertThat(hints.reflection().typeHints()).allSatisfy(
|
||||
hasOnlyMemberCategories(MemberCategory.INVOKE_PUBLIC_METHODS));
|
||||
assertThat(hints.reflection().typeHints().map(TypeHint::getType))
|
||||
.hasSameElementsAs(TypeReference.listOf(Number.class, Double.class, Integer.class, Float.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerReflectionOnTargetClass() {
|
||||
registerReflectionHints(AnnotatedSimplePojo.class);
|
||||
assertBasicTypeHint(AnnotatedSimplePojo.class, NO_METHODS,
|
||||
List.of(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AnnotatedMethodTests {
|
||||
|
||||
@Test
|
||||
void registerReflectionForStaticField() throws NoSuchMethodException {
|
||||
Method method = RegistrationMethod.class.getDeclaredMethod("doReflection");
|
||||
registerReflectionHints(method);
|
||||
assertBasicTypeHint(SimplePojo.class, NO_METHODS, List.of(MemberCategory.INVOKE_DECLARED_METHODS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void registerReflectionWithoutTarget() throws NoSuchMethodException {
|
||||
Method method = RegistrationMethodWithoutTarget.class.getDeclaredMethod("doReflection");
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> registerReflectionHints(method))
|
||||
.withMessageContaining("At least one class must be specified, could not detect target from '")
|
||||
.withMessageContaining(method.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void assertBasicTypeHint(Class<?> type, List<String> methodNames, List<MemberCategory> memberCategories) {
|
||||
TypeHint typeHint = getTypeHint(type);
|
||||
assertThat(typeHint.methods()).map(ExecutableHint::getName).hasSameElementsAs(methodNames);
|
||||
assertThat(typeHint.getMemberCategories()).hasSameElementsAs(memberCategories);
|
||||
assertThat(typeHint.fields()).isEmpty();
|
||||
assertThat(typeHint.constructors()).isEmpty();
|
||||
}
|
||||
|
||||
private Consumer<TypeHint> hasOnlyMemberCategories(MemberCategory... categories) {
|
||||
return typeHint -> {
|
||||
assertThat(typeHint.fields()).isEmpty();
|
||||
assertThat(typeHint.methods()).isEmpty();
|
||||
assertThat(typeHint.constructors()).isEmpty();
|
||||
assertThat(typeHint.getMemberCategories()).containsOnly(categories);
|
||||
};
|
||||
}
|
||||
|
||||
private TypeHint getTypeHint(Class<?> target) {
|
||||
TypeHint typeHint = hints.reflection().getTypeHint(target);
|
||||
assertThat(typeHint).isNotNull();
|
||||
return typeHint;
|
||||
}
|
||||
|
||||
private void registerReflectionHints(AnnotatedElement annotatedElement) {
|
||||
this.processor.registerReflectionHints(this.hints.reflection(), annotatedElement);
|
||||
}
|
||||
|
||||
@RegisterReflection(classes = SimplePojo.class, memberCategories = MemberCategory.INVOKE_PUBLIC_METHODS)
|
||||
static class RegistrationSimple {}
|
||||
|
||||
@RegisterReflection(classes = { Number.class, Double.class },
|
||||
classNames = { "java.lang.Integer", "java.lang.Float" }, memberCategories = MemberCategory.INVOKE_PUBLIC_METHODS)
|
||||
static class RegistrationMultipleTargets {
|
||||
|
||||
}
|
||||
|
||||
static class RegistrationMethod {
|
||||
|
||||
@RegisterReflection(classes = SimplePojo.class, memberCategories = MemberCategory.INVOKE_DECLARED_METHODS)
|
||||
private void doReflection() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class RegistrationMethodWithoutTarget {
|
||||
|
||||
@RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)
|
||||
private void doReflection() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
static class SimplePojo {
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
|
||||
@RegisterReflection(memberCategories = MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)
|
||||
static class AnnotatedSimplePojo {
|
||||
|
||||
private String test;
|
||||
|
||||
AnnotatedSimplePojo(String test) {
|
||||
this.test = test;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user