Add @ImportConfigurationPropertiesBean support

Add repeatable `@ImportConfigurationPropertiesBean` annotation that can
be used to import types and treat them as `@ConfigurationProperties`
beans. This annotation is specifically designed to support third-party
classes that can't contain any Spring annotations.

Closes gh-23172
This commit is contained in:
Phillip Webb
2020-09-08 16:16:40 -07:00
parent d2e67ab84d
commit f260c77fe3
35 changed files with 1272 additions and 133 deletions

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2012-2020 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.configurationprocessor;
import org.junit.jupiter.api.Test;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.Metadata;
import org.springframework.boot.configurationsample.ImportConfigurationPropertiesBean;
import org.springframework.boot.configurationsample.ImportConfigurationPropertiesBeans;
import org.springframework.boot.configurationsample.importbean.ImportJavaBeanConfigurationPropertiesBean;
import org.springframework.boot.configurationsample.importbean.ImportMultipleTypeConfigurationPropertiesBean;
import org.springframework.boot.configurationsample.importbean.ImportRepeatedConfigurationPropertiesBean;
import org.springframework.boot.configurationsample.importbean.ImportValueObjectConfigurationPropertiesBean;
import org.springframework.boot.configurationsample.importbean.ImportedJavaBean;
import org.springframework.boot.configurationsample.importbean.ImportedValueObject;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ImportConfigurationPropertiesBean} and
* {@link ImportConfigurationPropertiesBeans}.
*
* @author Phillip Webb
*/
public class ImportBeanTests extends AbstractMetadataGenerationTests {
@Test
void importValueObjectConfigurationPropertiesBean() {
ConfigurationMetadata metadata = compile(ImportValueObjectConfigurationPropertiesBean.class);
assertThat(metadata)
.has(Metadata.withProperty("importbean.value", String.class).fromSource(ImportedValueObject.class));
}
@Test
void importJavaBeanConfigurationPropertiesBean() {
ConfigurationMetadata metadata = compile(ImportJavaBeanConfigurationPropertiesBean.class);
assertThat(metadata)
.has(Metadata.withProperty("importbean.name", String.class).fromSource(ImportedJavaBean.class));
}
@Test
void importMultipleTypeConfigurationPropertiesBean() {
ConfigurationMetadata metadata = compile(ImportMultipleTypeConfigurationPropertiesBean.class);
assertThat(metadata)
.has(Metadata.withProperty("importbean.value", String.class).fromSource(ImportedValueObject.class));
assertThat(metadata)
.has(Metadata.withProperty("importbean.name", String.class).fromSource(ImportedJavaBean.class));
}
@Test
void importRepeatedConfigurationPropertiesBean() {
ConfigurationMetadata metadata = compile(ImportRepeatedConfigurationPropertiesBean.class);
assertThat(metadata).has(Metadata.withProperty("vo.value", String.class).fromSource(ImportedValueObject.class));
assertThat(metadata).has(Metadata.withProperty("jb.name", String.class).fromSource(ImportedJavaBean.class));
}
}

View File

@@ -39,7 +39,9 @@ class MetadataGenerationEnvironmentFactory implements Function<ProcessingEnviron
TestConfigurationMetadataAnnotationProcessor.DEFAULT_VALUE_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.ENDPOINT_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.READ_OPERATION_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.NAME_ANNOTATION);
TestConfigurationMetadataAnnotationProcessor.NAME_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.IMPORT_CONFIGURATION_PROPERTIES_BEAN_ANNOATION,
TestConfigurationMetadataAnnotationProcessor.IMPORT_CONFIGURATION_PROPERTIES_BEANS_ANNOATION);
}
}

View File

@@ -74,9 +74,9 @@ class PropertyDescriptorResolverTests {
Arrays.asList(HierarchicalPropertiesParent.class, HierarchicalPropertiesGrandparent.class),
(type, metadataEnv) -> {
PropertyDescriptorResolver resolver = new PropertyDescriptorResolver(metadataEnv);
assertThat(resolver.resolve(type, null).map(PropertyDescriptor::getName)).containsExactly("third",
"second", "first");
assertThat(resolver.resolve(type, null)
assertThat(resolver.resolve(type, false, null).map(PropertyDescriptor::getName))
.containsExactly("third", "second", "first");
assertThat(resolver.resolve(type, false, null)
.map((descriptor) -> descriptor.resolveItemMetadata("test", metadataEnv))
.map(ItemMetadata::getDefaultValue)).containsExactly("three", "two", "one");
});
@@ -155,7 +155,7 @@ class PropertyDescriptorResolverTests {
Consumer<Stream<PropertyDescriptor<?>>> stream) {
return (element, metadataEnv) -> {
PropertyDescriptorResolver resolver = new PropertyDescriptorResolver(metadataEnv);
stream.accept(resolver.resolve(element, null));
stream.accept(resolver.resolve(element, false, null));
};
}

View File

@@ -57,6 +57,10 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
public static final String NAME_ANNOTATION = "org.springframework.boot.configurationsample.Name";
public static final String IMPORT_CONFIGURATION_PROPERTIES_BEAN_ANNOATION = "org.springframework.boot.configurationsample.ImportConfigurationPropertiesBean";
public static final String IMPORT_CONFIGURATION_PROPERTIES_BEANS_ANNOATION = "org.springframework.boot.configurationsample.ImportConfigurationPropertiesBeans";
private ConfigurationMetadata metadata;
private final File outputLocation;
@@ -105,6 +109,16 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
return NAME_ANNOTATION;
}
@Override
protected String importConfigurationPropertiesBeanAnnotation() {
return IMPORT_CONFIGURATION_PROPERTIES_BEAN_ANNOATION;
}
@Override
protected String importConfigurationPropertiesBeansAnnotation() {
return IMPORT_CONFIGURATION_PROPERTIES_BEANS_ANNOATION;
}
@Override
protected ConfigurationMetadata writeMetaData() throws Exception {
super.writeMetaData();

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012-2020 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.configurationsample;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Alternative to Spring Boot's {@code ImportConfigurationPropertiesBean} for testing
* (removes the need for a dependency on the real annotation).
*
* @author Phillip Webb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ConfigurationProperties
@Repeatable(ImportConfigurationPropertiesBeans.class)
public @interface ImportConfigurationPropertiesBean {
Class<?>[] type();
@AliasFor(annotation = ConfigurationProperties.class)
String prefix() default "";
@AliasFor(annotation = ConfigurationProperties.class)
boolean ignoreInvalidFields() default false;
@AliasFor(annotation = ConfigurationProperties.class)
boolean ignoreUnknownFields() default true;
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2012-2020 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.configurationsample;
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;
/**
* Alternative to Spring Boot's {@code ImportConfigurationPropertiesBeans} for testing
* (removes the need for a dependency on the real annotation).
*
* @author Phillip Webb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ImportConfigurationPropertiesBeans {
ImportConfigurationPropertiesBean[] value();
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2012-2020 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.configurationsample.importbean;
import org.springframework.boot.configurationsample.ImportConfigurationPropertiesBean;
/**
* An import of a java bean.
*
* @author Phillip Webb
*/
@ImportConfigurationPropertiesBean(type = ImportedJavaBean.class, prefix = "importbean")
public class ImportJavaBeanConfigurationPropertiesBean {
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2012-2020 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.configurationsample.importbean;
import org.springframework.boot.configurationsample.ImportConfigurationPropertiesBean;
/**
* An import of a java bean and a value object.
*
* @author Phillip Webb
*/
@ImportConfigurationPropertiesBean(type = { ImportedJavaBean.class, ImportedValueObject.class }, prefix = "importbean")
public class ImportMultipleTypeConfigurationPropertiesBean {
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2012-2020 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.configurationsample.importbean;
import org.springframework.boot.configurationsample.ImportConfigurationPropertiesBean;
/**
* An import of a java bean and a value object.
*
* @author Phillip Webb
*/
@ImportConfigurationPropertiesBean(type = ImportedJavaBean.class, prefix = "jb")
@ImportConfigurationPropertiesBean(type = ImportedValueObject.class, prefix = "vo")
public class ImportRepeatedConfigurationPropertiesBean {
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2012-2020 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.configurationsample.importbean;
import org.springframework.boot.configurationsample.ImportConfigurationPropertiesBean;
/**
* An import of a value object.
*
* @author Phillip Webb
*/
@ImportConfigurationPropertiesBean(type = ImportedValueObject.class, prefix = "importbean")
public class ImportValueObjectConfigurationPropertiesBean {
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2012-2020 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.configurationsample.importbean;
/**
* Java bean that can be imported.
*
* @author Phillip Webb
*/
public class ImportedJavaBean {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2012-2020 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.configurationsample.importbean;
/**
* Value object that can be imported.
*
* @author Phillip Webb
*/
public class ImportedValueObject {
private final String value;
public ImportedValueObject(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
}