mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
Fix nested type discovery in ConfigurationPropertiesReflectionHintsProcessor
The old implementation picked up nested types only if they have been annotated with NestedConfigurationProperty, which not all nested properties are. Now the processor takes into account all referenced types. It ignores only types in the java package and interfaces. This commit also moves some of the tests from ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests to ConfigurationPropertiesReflectionHintsProcessorTests for easier discoverability Closes gh-31708
This commit is contained in:
parent
81fdb76620
commit
785588826e
@ -21,7 +21,6 @@ import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@ -35,12 +34,10 @@ import org.springframework.beans.BeanInfoFactory;
|
||||
import org.springframework.beans.ExtendedBeanInfoFactory;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Registers a given type on {@link ReflectionHints} for binding purposes, discovering any
|
||||
* nested type it may expose via a property.
|
||||
* referenced types it may expose via a property.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Moritz Halbritter
|
||||
@ -77,12 +74,22 @@ public final class ConfigurationPropertiesReflectionHintsProcessor {
|
||||
.process(reflectionHints);
|
||||
}
|
||||
|
||||
private void processNestedType(Class<?> type, ReflectionHints reflectionHints) {
|
||||
processNestedType(type, getBindConstructor(type, true), reflectionHints);
|
||||
private void processType(Class<?> type, ReflectionHints reflectionHints) {
|
||||
if (isTypeIgnored(type)) {
|
||||
return;
|
||||
}
|
||||
new ConfigurationPropertiesReflectionHintsProcessor(type, getBindConstructor(type, true), this.seen)
|
||||
.process(reflectionHints);
|
||||
}
|
||||
|
||||
private void processNestedType(Class<?> type, Constructor<?> bindConstructor, ReflectionHints reflectionHints) {
|
||||
new ConfigurationPropertiesReflectionHintsProcessor(type, bindConstructor, this.seen).process(reflectionHints);
|
||||
private boolean isTypeIgnored(Class<?> type) {
|
||||
if (type.getPackageName().startsWith("java.")) {
|
||||
return true;
|
||||
}
|
||||
if (type.isInterface()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Constructor<?> getBindConstructor(Class<?> type, boolean nestedType) {
|
||||
@ -118,9 +125,8 @@ public final class ConfigurationPropertiesReflectionHintsProcessor {
|
||||
|
||||
private void handleValueObjectProperties(ReflectionHints reflectionHints) {
|
||||
for (int i = 0; i < this.bindConstructor.getParameterCount(); i++) {
|
||||
String propertyName = this.bindConstructor.getParameters()[i].getName();
|
||||
ResolvableType propertyType = ResolvableType.forConstructorParameter(this.bindConstructor, i);
|
||||
handleProperty(reflectionHints, propertyName, propertyType);
|
||||
registerType(reflectionHints, propertyType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,16 +135,12 @@ public final class ConfigurationPropertiesReflectionHintsProcessor {
|
||||
Method readMethod = propertyDescriptor.getReadMethod();
|
||||
if (readMethod != null) {
|
||||
ResolvableType propertyType = ResolvableType.forMethodReturnType(readMethod, this.type);
|
||||
String propertyName = propertyDescriptor.getName();
|
||||
if (isSetterMandatory(propertyName, propertyType) && propertyDescriptor.getWriteMethod() == null) {
|
||||
continue;
|
||||
}
|
||||
handleProperty(reflectionHints, propertyName, propertyType);
|
||||
registerType(reflectionHints, propertyType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleProperty(ReflectionHints reflectionHints, String propertyName, ResolvableType propertyType) {
|
||||
private void registerType(ReflectionHints reflectionHints, ResolvableType propertyType) {
|
||||
Class<?> propertyClass = propertyType.resolve();
|
||||
if (propertyClass == null) {
|
||||
return;
|
||||
@ -148,27 +150,13 @@ public final class ConfigurationPropertiesReflectionHintsProcessor {
|
||||
}
|
||||
Class<?> componentType = getComponentType(propertyType);
|
||||
if (componentType != null) {
|
||||
// Can be a list of simple types
|
||||
if (!isJavaType(componentType)) {
|
||||
processNestedType(componentType, reflectionHints);
|
||||
}
|
||||
processType(componentType, reflectionHints);
|
||||
}
|
||||
else if (isNestedType(propertyName, propertyClass)) {
|
||||
processNestedType(propertyClass, reflectionHints);
|
||||
else {
|
||||
processType(propertyClass, reflectionHints);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSetterMandatory(String propertyName, ResolvableType propertyType) {
|
||||
Class<?> propertyClass = propertyType.resolve();
|
||||
if (propertyClass == null) {
|
||||
return true;
|
||||
}
|
||||
if (getComponentType(propertyType) != null) {
|
||||
return false;
|
||||
}
|
||||
return !isNestedType(propertyName, propertyClass);
|
||||
}
|
||||
|
||||
private Class<?> getComponentType(ResolvableType propertyType) {
|
||||
Class<?> propertyClass = propertyType.toClass();
|
||||
if (propertyType.isArray()) {
|
||||
@ -183,27 +171,6 @@ public final class ConfigurationPropertiesReflectionHintsProcessor {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether the specified property refer to a nested type. A nested type
|
||||
* represents a sub-namespace that need to be fully resolved.
|
||||
* @param propertyName the name of the property
|
||||
* @param propertyType the type of the property
|
||||
* @return whether the specified {@code propertyType} is a nested type
|
||||
*/
|
||||
private boolean isNestedType(String propertyName, Class<?> propertyType) {
|
||||
if (this.type.equals(propertyType.getDeclaringClass())) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
Field field = ReflectionUtils.findField(this.type, propertyName);
|
||||
return field != null && MergedAnnotations.from(field).isPresent(NestedConfigurationProperty.class);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isJavaType(Class<?> candidate) {
|
||||
return candidate.getPackageName().startsWith("java.");
|
||||
}
|
||||
|
||||
private static BeanInfo getBeanInfo(Class<?> beanType) {
|
||||
try {
|
||||
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanType);
|
||||
|
@ -18,9 +18,7 @@ package org.springframework.boot.context.properties;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -41,10 +39,6 @@ import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@ -88,65 +82,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
|
||||
(hint) -> assertThat(hint.getMemberCategories()).contains(MemberCategory.INVOKE_DECLARED_METHODS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void processJavaBeanConfigurationProperties() {
|
||||
RuntimeHints runtimeHints = process(SampleProperties.class);
|
||||
assertThat(runtimeHints.reflection().getTypeHint(SampleProperties.class))
|
||||
.satisfies(javaBeanBinding(SampleProperties.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void processJavaBeanConfigurationPropertiesWithSeveralConstructors() throws NoSuchMethodException {
|
||||
RuntimeHints runtimeHints = process(SamplePropertiesWithSeveralConstructors.class);
|
||||
assertThat(runtimeHints.reflection().getTypeHint(SamplePropertiesWithSeveralConstructors.class))
|
||||
.satisfies(javaBeanBinding(SamplePropertiesWithSeveralConstructors.class,
|
||||
SamplePropertiesWithSeveralConstructors.class.getDeclaredConstructor()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void processJavaBeanConfigurationPropertiesWithMapOfPojo() {
|
||||
RuntimeHints runtimeHints = process(SamplePropertiesWithMap.class);
|
||||
List<TypeHint> typeHints = runtimeHints.reflection().typeHints().toList();
|
||||
assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithMap.class));
|
||||
assertThat(typeHints).anySatisfy(javaBeanBinding(Address.class));
|
||||
assertThat(typeHints).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processJavaBeanConfigurationPropertiesWithListOfPojo() {
|
||||
RuntimeHints runtimeHints = process(SamplePropertiesWithList.class);
|
||||
List<TypeHint> typeHints = runtimeHints.reflection().typeHints().toList();
|
||||
assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithList.class));
|
||||
assertThat(typeHints).anySatisfy(javaBeanBinding(Address.class));
|
||||
assertThat(typeHints).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processJavaBeanConfigurationPropertiesWitArrayOfPojo() {
|
||||
RuntimeHints runtimeHints = process(SamplePropertiesWithArray.class);
|
||||
List<TypeHint> typeHints = runtimeHints.reflection().typeHints().toList();
|
||||
assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithArray.class));
|
||||
assertThat(typeHints).anySatisfy(javaBeanBinding(Address.class));
|
||||
assertThat(typeHints).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processJavaBeanConfigurationPropertiesWithListOfJavaType() {
|
||||
RuntimeHints runtimeHints = process(SamplePropertiesWithSimpleList.class);
|
||||
List<TypeHint> typeHints = runtimeHints.reflection().typeHints().toList();
|
||||
assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithSimpleList.class));
|
||||
assertThat(typeHints).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processValueObjectConfigurationProperties() {
|
||||
RuntimeHints runtimeHints = process(SampleImmutableProperties.class);
|
||||
List<TypeHint> typeHints = runtimeHints.reflection().typeHints().toList();
|
||||
assertThat(typeHints).anySatisfy(valueObjectBinding(SampleImmutableProperties.class,
|
||||
SampleImmutableProperties.class.getDeclaredConstructors()[0]));
|
||||
assertThat(typeHints).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processValueObjectConfigurationPropertiesWithSpecificConstructor() throws NoSuchMethodException {
|
||||
RuntimeHints runtimeHints = process(SampleImmutablePropertiesWithSeveralConstructors.class);
|
||||
@ -156,17 +91,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
|
||||
assertThat(typeHints).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processValueObjectConfigurationPropertiesWithSeveralLayersOfPojo() {
|
||||
RuntimeHints runtimeHints = process(SampleImmutablePropertiesWithList.class);
|
||||
List<TypeHint> typeHints = runtimeHints.reflection().typeHints().toList();
|
||||
assertThat(typeHints).anySatisfy(valueObjectBinding(SampleImmutablePropertiesWithList.class,
|
||||
SampleImmutablePropertiesWithList.class.getDeclaredConstructors()[0]));
|
||||
assertThat(typeHints).anySatisfy(valueObjectBinding(Person.class, Person.class.getDeclaredConstructors()[0]));
|
||||
assertThat(typeHints).anySatisfy(valueObjectBinding(Address.class, Address.class.getDeclaredConstructors()[0]));
|
||||
assertThat(typeHints).hasSize(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processConfigurationPropertiesWithNestedTypeNotUsedIsIgnored() {
|
||||
RuntimeHints runtimeHints = process(SamplePropertiesWithNested.class);
|
||||
@ -174,23 +98,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
|
||||
.satisfies(javaBeanBinding(SamplePropertiesWithNested.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void processConfigurationPropertiesWithNestedExternalType() {
|
||||
RuntimeHints runtimeHints = process(SamplePropertiesWithExternalNested.class);
|
||||
assertThat(runtimeHints.reflection().typeHints())
|
||||
.anySatisfy(javaBeanBinding(SamplePropertiesWithExternalNested.class))
|
||||
.anySatisfy(javaBeanBinding(SampleType.class)).anySatisfy(javaBeanBinding(SampleType.Nested.class))
|
||||
.hasSize(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processConfigurationPropertiesWithRecursiveType() {
|
||||
RuntimeHints runtimeHints = process(SamplePropertiesWithRecursive.class);
|
||||
assertThat(runtimeHints.reflection().typeHints())
|
||||
.anySatisfy(javaBeanBinding(SamplePropertiesWithRecursive.class))
|
||||
.anySatisfy(javaBeanBinding(Recursive.class)).hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processValueObjectConfigurationPropertiesWithRecursiveType() {
|
||||
RuntimeHints runtimeHints = process(SampleImmutablePropertiesWithRecursive.class);
|
||||
@ -202,15 +109,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
|
||||
.hasSize(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processConfigurationPropertiesWithWellKnownTypes() {
|
||||
RuntimeHints runtimeHints = process(SamplePropertiesWithWellKnownTypes.class);
|
||||
assertThat(runtimeHints.reflection().typeHints())
|
||||
.anySatisfy(javaBeanBinding(SamplePropertiesWithWellKnownTypes.class))
|
||||
// TODO
|
||||
.hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void processConfigurationPropertiesWithCrossReference() {
|
||||
RuntimeHints runtimeHints = process(SamplePropertiesWithCrossReference.class);
|
||||
@ -279,65 +177,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
public static class SamplePropertiesWithSeveralConstructors {
|
||||
|
||||
SamplePropertiesWithSeveralConstructors() {
|
||||
}
|
||||
|
||||
SamplePropertiesWithSeveralConstructors(String ignored) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
public static class SamplePropertiesWithMap {
|
||||
|
||||
public Map<String, Address> getAddresses() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
public static class SamplePropertiesWithList {
|
||||
|
||||
public List<Address> getAllAddresses() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
public static class SamplePropertiesWithSimpleList {
|
||||
|
||||
public List<String> getNames() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("test")
|
||||
public static class SamplePropertiesWithArray {
|
||||
|
||||
public Address[] getAllAddresses() {
|
||||
return new Address[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class SampleImmutableProperties {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final String name;
|
||||
|
||||
SampleImmutableProperties(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class SampleImmutablePropertiesWithSeveralConstructors {
|
||||
|
||||
@ -355,18 +194,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class SampleImmutablePropertiesWithList {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final List<Person> family;
|
||||
|
||||
SampleImmutablePropertiesWithList(List<Person> family) {
|
||||
this.family = family;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("nested")
|
||||
public static class SamplePropertiesWithNested {
|
||||
|
||||
@ -376,48 +203,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("nested")
|
||||
public static class SamplePropertiesWithExternalNested {
|
||||
|
||||
private String name;
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private SampleType sampleType;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public SampleType getSampleType() {
|
||||
return this.sampleType;
|
||||
}
|
||||
|
||||
public void setSampleType(SampleType sampleType) {
|
||||
this.sampleType = sampleType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("recursive")
|
||||
public static class SamplePropertiesWithRecursive {
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private Recursive recursive;
|
||||
|
||||
public Recursive getRecursive() {
|
||||
return this.recursive;
|
||||
}
|
||||
|
||||
public void setRecursive(Recursive recursive) {
|
||||
this.recursive = recursive;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class SampleImmutablePropertiesWithRecursive {
|
||||
|
||||
@ -430,84 +215,6 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties("wellKnownTypes")
|
||||
public static class SamplePropertiesWithWellKnownTypes implements ApplicationContextAware, EnvironmentAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return this.applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public Environment getEnvironment() {
|
||||
return this.environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class SampleType {
|
||||
|
||||
private final Nested nested = new Nested();
|
||||
|
||||
public Nested getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
static class Nested {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Address {
|
||||
|
||||
}
|
||||
|
||||
public static class Person {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final String firstName;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final String lastName;
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private final Address address;
|
||||
|
||||
Person(String firstName, String lastName, Address address) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Recursive {
|
||||
|
||||
private Recursive recursive;
|
||||
|
||||
public Recursive getRecursive() {
|
||||
return this.recursive;
|
||||
}
|
||||
|
||||
public void setRecursive(Recursive recursive) {
|
||||
this.recursive = recursive;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ImmutableRecursive {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -0,0 +1,437 @@
|
||||
/*
|
||||
* Copyright 2012-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.boot.context.properties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConfigurationPropertiesReflectionHintsProcessor}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class ConfigurationPropertiesReflectionHintsProcessorTests {
|
||||
|
||||
@Test
|
||||
void shouldRegisterNoArgConstructor() throws NoSuchMethodException {
|
||||
RuntimeHints hints = runProcessor(NoArgsCtorProperties.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onConstructor(ReflectionUtils.accessibleConstructor(NoArgsCtorProperties.class))).accepts(hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterMemberCategories() {
|
||||
RuntimeHints hints = runProcessor(NoArgsCtorProperties.class);
|
||||
hasReflectionHintsForType(hints, NoArgsCtorProperties.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRegisterJavaTypes() {
|
||||
RuntimeHints hints = runProcessor(NoArgsCtorProperties.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(String.class)).rejects(hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRegisterJavaTypesInLists() {
|
||||
RuntimeHints hints = runProcessor(StringListProperties.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(String.class)).rejects(hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterConstructorBinding() throws NoSuchMethodException {
|
||||
RuntimeHints hints = runProcessor(ConstructorBindingProperties.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onConstructor(ReflectionUtils.accessibleConstructor(ConstructorBindingProperties.class, String.class)))
|
||||
.accepts(hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterConstructorBindingWithRecords() throws NoSuchMethodException {
|
||||
RuntimeHints hints = runProcessor(RecordProperties.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection()
|
||||
.onConstructor(ReflectionUtils.accessibleConstructor(RecordProperties.class, String.class)))
|
||||
.accepts(hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterNestedTypes() {
|
||||
RuntimeHints hints = runProcessor(NestedProperties.class);
|
||||
hasReflectionHintsForType(hints, Nested.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterNestedTypesWhenNoSetterIsPresent() {
|
||||
RuntimeHints hints = runProcessor(NestedNoSetterProperties.class);
|
||||
hasReflectionHintsForType(hints, Nested.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterNestedTypesInLists() {
|
||||
RuntimeHints hints = runProcessor(NestedListProperties.class);
|
||||
hasReflectionHintsForType(hints, Nested.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterNestedTypesInMaps() {
|
||||
RuntimeHints hints = runProcessor(NestedMapProperties.class);
|
||||
hasReflectionHintsForType(hints, Nested.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterNestedTypesInArrays() {
|
||||
RuntimeHints hints = runProcessor(NestedArrayProperties.class);
|
||||
hasReflectionHintsForType(hints, Nested.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterNestedWithRecords() {
|
||||
RuntimeHints hints = runProcessor(NestedRecordProperties.class);
|
||||
hasReflectionHintsForType(hints, Nested.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterMultipleLevelsOfNested() {
|
||||
RuntimeHints hints = runProcessor(NestedMultipleLevelsProperties.class);
|
||||
hasReflectionHintsForType(hints, NestedLevel1.class);
|
||||
hasReflectionHintsForType(hints, NestedLevel2.class);
|
||||
hasReflectionHintsForType(hints, NestedLevel3.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotCrashOnCycles() {
|
||||
assertThatCode(() -> runProcessor(CycleProperties.class)).doesNotThrowAnyException();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRegisterInterfaces() {
|
||||
RuntimeHints hints = runProcessor(InterfaceProperties.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(SomeInterface.class)).rejects(hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRegisterKnownTypes() {
|
||||
RuntimeHints hints = runProcessor(AwareProperties.class);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(Environment.class)).rejects(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(ApplicationContext.class)).rejects(hints);
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(BeanFactory.class)).rejects(hints);
|
||||
}
|
||||
|
||||
private RuntimeHints runProcessor(Class<?> type) {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
ConfigurationPropertiesReflectionHintsProcessor.processConfigurationProperties(type, hints.reflection());
|
||||
return hints;
|
||||
}
|
||||
|
||||
private void hasReflectionHintsForType(RuntimeHints hints, Class<?> type) {
|
||||
assertThat(RuntimeHintsPredicates.reflection().onType(type)
|
||||
.withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS))
|
||||
.accepts(hints);
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class NoArgsCtorProperties {
|
||||
|
||||
private String field;
|
||||
|
||||
NoArgsCtorProperties() {
|
||||
}
|
||||
|
||||
NoArgsCtorProperties(String field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
public void setField(String field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class StringListProperties {
|
||||
|
||||
private final List<String> stringList = new ArrayList<>();
|
||||
|
||||
public List<String> getStringList() {
|
||||
return this.stringList;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class ConstructorBindingProperties {
|
||||
|
||||
private final String field;
|
||||
|
||||
ConstructorBindingProperties(String field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public record RecordProperties(String field) {
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class NestedProperties {
|
||||
|
||||
private Nested nested;
|
||||
|
||||
public Nested getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
public void setNested(Nested nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class NestedNoSetterProperties {
|
||||
|
||||
private final Nested nested = new Nested();
|
||||
|
||||
public Nested getNested() {
|
||||
return this.nested;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class NestedListProperties {
|
||||
|
||||
private List<Nested> nestedList;
|
||||
|
||||
public List<Nested> getNestedList() {
|
||||
return this.nestedList;
|
||||
}
|
||||
|
||||
public void setNestedList(List<Nested> nestedList) {
|
||||
this.nestedList = nestedList;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class NestedMapProperties {
|
||||
|
||||
private Map<String, Nested> nestedMap = new HashMap<>();
|
||||
|
||||
public Map<String, Nested> getNestedMap() {
|
||||
return this.nestedMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class NestedArrayProperties {
|
||||
|
||||
private Nested[] nestedArray;
|
||||
|
||||
public Nested[] getNestedArray() {
|
||||
return this.nestedArray;
|
||||
}
|
||||
|
||||
public void setNestedArray(Nested[] nestedArray) {
|
||||
this.nestedArray = nestedArray;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public record NestedRecordProperties(Nested nested) {
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class CycleProperties {
|
||||
|
||||
private CycleProperties cycleProperties;
|
||||
|
||||
public CycleProperties getCycleProperties() {
|
||||
return this.cycleProperties;
|
||||
}
|
||||
|
||||
public void setCycleProperties(CycleProperties cycleProperties) {
|
||||
this.cycleProperties = cycleProperties;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class NestedMultipleLevelsProperties {
|
||||
|
||||
private NestedLevel1 nestedLevel1;
|
||||
|
||||
public NestedLevel1 getNestedLevel1() {
|
||||
return this.nestedLevel1;
|
||||
}
|
||||
|
||||
public void setNestedLevel1(NestedLevel1 nestedLevel1) {
|
||||
this.nestedLevel1 = nestedLevel1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class InterfaceProperties {
|
||||
|
||||
private SomeInterface someInterface;
|
||||
|
||||
public SomeInterface getSomeInterface() {
|
||||
return this.someInterface;
|
||||
}
|
||||
|
||||
public void setSomeInterface(SomeInterface someInterface) {
|
||||
this.someInterface = someInterface;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ConfigurationProperties
|
||||
public static class AwareProperties implements ApplicationContextAware, BeanFactoryAware, EnvironmentAware {
|
||||
|
||||
private String field;
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private Environment environment;
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
public BeanFactory getBeanFactory() {
|
||||
return this.beanFactory;
|
||||
}
|
||||
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return this.applicationContext;
|
||||
}
|
||||
|
||||
public Environment getEnvironment() {
|
||||
return this.environment;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface SomeInterface {
|
||||
|
||||
}
|
||||
|
||||
public static class Nested {
|
||||
|
||||
private String field;
|
||||
|
||||
public String getField() {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
public void setField(String field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class NestedLevel1 {
|
||||
|
||||
private NestedLevel2 nestedLevel2;
|
||||
|
||||
public NestedLevel2 getNestedLevel2() {
|
||||
return this.nestedLevel2;
|
||||
}
|
||||
|
||||
public void setNestedLevel2(NestedLevel2 nestedLevel2) {
|
||||
this.nestedLevel2 = nestedLevel2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class NestedLevel2 {
|
||||
|
||||
private NestedLevel3 nestedLevel3;
|
||||
|
||||
public NestedLevel3 getNestedLevel3() {
|
||||
return this.nestedLevel3;
|
||||
}
|
||||
|
||||
public void setNestedLevel3(NestedLevel3 nestedLevel3) {
|
||||
this.nestedLevel3 = nestedLevel3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class NestedLevel3 {
|
||||
|
||||
private String field;
|
||||
|
||||
public String getField() {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
public void setField(String field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user