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

@@ -22,6 +22,7 @@ import java.io.StringWriter;
import java.time.Duration;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -40,6 +41,7 @@ import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
@@ -60,17 +62,13 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
@SupportedAnnotationTypes({ "*" })
public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor {
static final String ADDITIONAL_METADATA_LOCATIONS_OPTION = "org.springframework.boot."
+ "configurationprocessor.additionalMetadataLocations";
static final String ADDITIONAL_METADATA_LOCATIONS_OPTION = "org.springframework.boot.configurationprocessor.additionalMetadataLocations";
static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot."
+ "context.properties.ConfigurationProperties";
static final String CONFIGURATION_PROPERTIES_ANNOTATION = "org.springframework.boot.context.properties.ConfigurationProperties";
static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot."
+ "context.properties.NestedConfigurationProperty";
static final String NESTED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.context.properties.NestedConfigurationProperty";
static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot."
+ "context.properties.DeprecatedConfigurationProperty";
static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.context.properties.DeprecatedConfigurationProperty";
static final String CONSTRUCTOR_BINDING_ANNOTATION = "org.springframework.boot.context.properties.ConstructorBinding";
@@ -78,11 +76,14 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
static final String ENDPOINT_ANNOTATION = "org.springframework.boot.actuate.endpoint.annotation.Endpoint";
static final String READ_OPERATION_ANNOTATION = "org.springframework.boot.actuate."
+ "endpoint.annotation.ReadOperation";
static final String READ_OPERATION_ANNOTATION = "org.springframework.boot.actuate.endpoint.annotation.ReadOperation";
static final String NAME_ANNOTATION = "org.springframework.boot.context.properties.bind.Name";
static final String IMPORT_CONFIGURATION_PROPERTIES_BEAN_ANNOATION = "org.springframework.boot.context.properties.ImportConfigurationPropertiesBean";
static final String IMPORT_CONFIGURATION_PROPERTIES_BEANS_ANNOATION = "org.springframework.boot.context.properties.ImportConfigurationPropertiesBeans";
private static final Set<String> SUPPORTED_OPTIONS = Collections
.unmodifiableSet(Collections.singleton(ADDITIONAL_METADATA_LOCATIONS_OPTION));
@@ -124,6 +125,14 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
return NAME_ANNOTATION;
}
protected String importConfigurationPropertiesBeanAnnotation() {
return IMPORT_CONFIGURATION_PROPERTIES_BEAN_ANNOATION;
}
protected String importConfigurationPropertiesBeansAnnotation() {
return IMPORT_CONFIGURATION_PROPERTIES_BEANS_ANNOATION;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
@@ -142,22 +151,16 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
this.metadataEnv = new MetadataGenerationEnvironment(env, configurationPropertiesAnnotation(),
nestedConfigurationPropertyAnnotation(), deprecatedConfigurationPropertyAnnotation(),
constructorBindingAnnotation(), defaultValueAnnotation(), endpointAnnotation(),
readOperationAnnotation(), nameAnnotation());
readOperationAnnotation(), nameAnnotation(), importConfigurationPropertiesBeanAnnotation(),
importConfigurationPropertiesBeansAnnotation());
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
this.metadataCollector.processing(roundEnv);
TypeElement annotationType = this.metadataEnv.getConfigurationPropertiesAnnotationElement();
if (annotationType != null) { // Is @ConfigurationProperties available
for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
processElement(element);
}
}
TypeElement endpointType = this.metadataEnv.getEndpointAnnotationElement();
if (endpointType != null) { // Is @Endpoint available
getElementsAnnotatedOrMetaAnnotatedWith(roundEnv, endpointType).forEach(this::processEndpoint);
}
processConfigurationProperties(roundEnv);
processEndpoint(roundEnv);
processImportConfigurationPropertiesBean(roundEnv);
if (roundEnv.processingOver()) {
try {
writeMetaData();
@@ -169,6 +172,40 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
return false;
}
private void processConfigurationProperties(RoundEnvironment roundEnv) {
TypeElement annotationType = this.metadataEnv.getConfigurationPropertiesAnnotationElement();
if (annotationType != null) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
processElement(element);
}
}
}
private void processEndpoint(RoundEnvironment roundEnv) {
TypeElement endpointType = this.metadataEnv.getEndpointAnnotationElement();
if (endpointType != null) {
getElementsAnnotatedOrMetaAnnotatedWith(roundEnv, endpointType).forEach(this::processEndpoint);
}
}
private void processImportConfigurationPropertiesBean(RoundEnvironment roundEnv) {
TypeElement importConfigurationPropertiesBeanType = this.metadataEnv
.getImportConfigurationPropertiesBeanAnnotationElement();
TypeElement importConfigurationPropertiesBeansType = this.metadataEnv
.getImportConfigurationPropertiesBeansAnnotationElement();
if (importConfigurationPropertiesBeanType == null && importConfigurationPropertiesBeansType == null) {
return;
}
Set<Element> elements = new LinkedHashSet<>();
if (importConfigurationPropertiesBeanType != null) {
elements.addAll(roundEnv.getElementsAnnotatedWith(importConfigurationPropertiesBeanType));
}
if (importConfigurationPropertiesBeansType != null) {
elements.addAll(roundEnv.getElementsAnnotatedWith(importConfigurationPropertiesBeansType));
}
elements.forEach(this::processImportConfigurationPropertiesBean);
}
private Map<Element, List<Element>> getElementsAnnotatedOrMetaAnnotatedWith(RoundEnvironment roundEnv,
TypeElement annotation) {
Map<Element, List<Element>> result = new LinkedHashMap<>();
@@ -187,7 +224,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
if (annotation != null) {
String prefix = getPrefix(annotation);
if (element instanceof TypeElement) {
processAnnotatedTypeElement(prefix, (TypeElement) element, new Stack<>());
processAnnotatedTypeElement(prefix, (TypeElement) element, false, new Stack<>());
}
else if (element instanceof ExecutableElement) {
processExecutableElement(prefix, (ExecutableElement) element, new Stack<>());
@@ -199,10 +236,11 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
}
}
private void processAnnotatedTypeElement(String prefix, TypeElement element, Stack<TypeElement> seen) {
private void processAnnotatedTypeElement(String prefix, TypeElement element, boolean fromImport,
Stack<TypeElement> seen) {
String type = this.metadataEnv.getTypeUtils().getQualifiedName(element);
this.metadataCollector.add(ItemMetadata.newGroup(prefix, type, type, null));
processTypeElement(prefix, element, null, seen);
processTypeElement(prefix, element, fromImport, null, seen);
}
private void processExecutableElement(String prefix, ExecutableElement element, Stack<TypeElement> seen) {
@@ -220,25 +258,26 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
}
else {
this.metadataCollector.add(group);
processTypeElement(prefix, (TypeElement) returns, element, seen);
processTypeElement(prefix, (TypeElement) returns, false, element, seen);
}
}
}
}
private void processTypeElement(String prefix, TypeElement element, ExecutableElement source,
private void processTypeElement(String prefix, TypeElement element, boolean fromImport, ExecutableElement source,
Stack<TypeElement> seen) {
if (!seen.contains(element)) {
seen.push(element);
new PropertyDescriptorResolver(this.metadataEnv).resolve(element, source).forEach((descriptor) -> {
this.metadataCollector.add(descriptor.resolveItemMetadata(prefix, this.metadataEnv));
if (descriptor.isNested(this.metadataEnv)) {
TypeElement nestedTypeElement = (TypeElement) this.metadataEnv.getTypeUtils()
.asElement(descriptor.getType());
String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, descriptor.getName());
processTypeElement(nestedPrefix, nestedTypeElement, source, seen);
}
});
new PropertyDescriptorResolver(this.metadataEnv).resolve(element, fromImport, source)
.forEach((descriptor) -> {
this.metadataCollector.add(descriptor.resolveItemMetadata(prefix, this.metadataEnv));
if (descriptor.isNested(this.metadataEnv)) {
TypeElement nestedTypeElement = (TypeElement) this.metadataEnv.getTypeUtils()
.asElement(descriptor.getType());
String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, descriptor.getName());
processTypeElement(nestedPrefix, nestedTypeElement, false, source, seen);
}
});
seen.pop();
}
}
@@ -275,6 +314,21 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
}
}
private void processImportConfigurationPropertiesBean(Element element) {
this.metadataEnv.getImportConfigurationPropertiesBeanAnnotations(element)
.forEach(this::processImportConfigurationPropertiesBean);
}
@SuppressWarnings("unchecked")
private void processImportConfigurationPropertiesBean(AnnotationMirror annotation) {
String prefix = getPrefix(annotation);
List<TypeMirror> types = (List<TypeMirror>) this.metadataEnv.getAnnotationElementValues(annotation).get("type");
for (TypeMirror type : types) {
Element element = this.metadataEnv.getTypeUtils().asElement(type);
processAnnotatedTypeElement(prefix, (TypeElement) element, true, new Stack<>());
}
}
private boolean hasMainReadOperation(TypeElement element) {
for (ExecutableElement method : ElementFilter.methodsIn(element.getEnclosedElements())) {
if (this.metadataEnv.getReadOperationAnnotation(method) != null

View File

@@ -97,10 +97,15 @@ class MetadataGenerationEnvironment {
private final String nameAnnotation;
private final String importConfigurationPropertiesBeanAnnotation;
private final String importConfigurationPropertiesBeansAnnotation;
MetadataGenerationEnvironment(ProcessingEnvironment environment, String configurationPropertiesAnnotation,
String nestedConfigurationPropertyAnnotation, String deprecatedConfigurationPropertyAnnotation,
String constructorBindingAnnotation, String defaultValueAnnotation, String endpointAnnotation,
String readOperationAnnotation, String nameAnnotation) {
String readOperationAnnotation, String nameAnnotation, String importConfigurationPropertiesBeanAnnotation,
String importConfigurationPropertiesBeansAnnotation) {
this.typeUtils = new TypeUtils(environment);
this.elements = environment.getElementUtils();
this.messager = environment.getMessager();
@@ -113,6 +118,8 @@ class MetadataGenerationEnvironment {
this.endpointAnnotation = endpointAnnotation;
this.readOperationAnnotation = readOperationAnnotation;
this.nameAnnotation = nameAnnotation;
this.importConfigurationPropertiesBeanAnnotation = importConfigurationPropertiesBeanAnnotation;
this.importConfigurationPropertiesBeansAnnotation = importConfigurationPropertiesBeansAnnotation;
}
private static FieldValuesParser resolveFieldValuesParser(ProcessingEnvironment env) {
@@ -258,6 +265,14 @@ class MetadataGenerationEnvironment {
return this.elements.getTypeElement(this.configurationPropertiesAnnotation);
}
TypeElement getImportConfigurationPropertiesBeanAnnotationElement() {
return this.elements.getTypeElement(this.importConfigurationPropertiesBeanAnnotation);
}
TypeElement getImportConfigurationPropertiesBeansAnnotationElement() {
return this.elements.getTypeElement(this.importConfigurationPropertiesBeansAnnotation);
}
AnnotationMirror getConfigurationPropertiesAnnotation(Element element) {
return getAnnotation(element, this.configurationPropertiesAnnotation);
}
@@ -282,6 +297,22 @@ class MetadataGenerationEnvironment {
return getAnnotation(element, this.nameAnnotation);
}
List<AnnotationMirror> getImportConfigurationPropertiesBeanAnnotations(Element element) {
List<AnnotationMirror> annotations = new ArrayList<>();
AnnotationMirror importBean = getAnnotation(element, this.importConfigurationPropertiesBeanAnnotation);
if (importBean != null) {
annotations.add(importBean);
}
AnnotationMirror importBeans = getAnnotation(element, this.importConfigurationPropertiesBeansAnnotation);
if (importBeans != null) {
AnnotationValue value = importBeans.getElementValues().values().iterator().next();
for (Object contained : (List<?>) value.getValue()) {
annotations.add((AnnotationMirror) contained);
}
}
return Collections.unmodifiableList(annotations);
}
boolean hasNullableAnnotation(Element element) {
return getAnnotation(element, NULLABLE_ANNOTATION) != null;
}

View File

@@ -49,16 +49,19 @@ class PropertyDescriptorResolver {
* specified {@link TypeElement type} based on the specified {@link ExecutableElement
* factory method}, if any.
* @param type the target type
* @param fromImport it the type was imported via a
* {@code @ImportConfigurationPropertiesBean}
* @param factoryMethod the method that triggered the metadata for that {@code type}
* or {@code null}
* @return the candidate properties for metadata generation
*/
Stream<PropertyDescriptor<?>> resolve(TypeElement type, ExecutableElement factoryMethod) {
Stream<PropertyDescriptor<?>> resolve(TypeElement type, boolean fromImport, ExecutableElement factoryMethod) {
TypeElementMembers members = new TypeElementMembers(this.environment, type);
if (factoryMethod != null) {
return resolveJavaBeanProperties(type, factoryMethod, members);
}
return resolve(ConfigurationPropertiesTypeElement.of(type, this.environment), factoryMethod, members);
return resolve(ConfigurationPropertiesTypeElement.of(type, fromImport, this.environment), factoryMethod,
members);
}
private Stream<PropertyDescriptor<?>> resolve(ConfigurationPropertiesTypeElement type,
@@ -178,20 +181,29 @@ class PropertyDescriptorResolver {
return boundConstructor;
}
static ConfigurationPropertiesTypeElement of(TypeElement type, MetadataGenerationEnvironment env) {
boolean constructorBoundType = isConstructorBoundType(type, env);
static ConfigurationPropertiesTypeElement of(TypeElement type, boolean fromImport,
MetadataGenerationEnvironment env) {
List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
List<ExecutableElement> boundConstructors = constructors.stream()
.filter(env::hasConstructorBindingAnnotation).collect(Collectors.toList());
boolean constructorBoundType = isConstructorBoundType(type, fromImport, constructors, env);
return new ConfigurationPropertiesTypeElement(type, constructorBoundType, constructors, boundConstructors);
}
private static boolean isConstructorBoundType(TypeElement type, MetadataGenerationEnvironment env) {
private static boolean isConstructorBoundType(TypeElement type, boolean fromImport,
List<ExecutableElement> constructors, MetadataGenerationEnvironment env) {
if (env.hasConstructorBindingAnnotation(type)) {
return true;
}
if (type.getNestingKind() == NestingKind.MEMBER) {
return isConstructorBoundType((TypeElement) type.getEnclosingElement(), env);
return isConstructorBoundType((TypeElement) type.getEnclosingElement(), false, constructors, env);
}
if (fromImport) {
for (ExecutableElement constructor : constructors) {
if (!constructor.getParameters().isEmpty()) {
return true;
}
}
}
return false;
}

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;
}
}