Use @ConstructorBinding when generating meta-data

Update the configuration processor to use the newly introduced
`@ConstructorBinding` annotation to determine when meta data
should be generated from constructor parameters.

Prior to this commit, the processor had no good way to tell when
constructor parameters should be used instead of getters/setters.

Closes gh-17035
This commit is contained in:
Stephane Nicoll 2019-10-02 12:03:46 +02:00 committed by Phillip Webb
parent 4208c93ba7
commit 45f6668d03
16 changed files with 430 additions and 42 deletions

View File

@ -22,7 +22,6 @@ import java.io.StringWriter;
import java.time.Duration;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -39,7 +38,6 @@ import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
@ -73,6 +71,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
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";
static final String DEFAULT_VALUE_ANNOTATION = "org.springframework.boot.context.properties.bind.DefaultValue";
static final String ENDPOINT_ANNOTATION = "org.springframework.boot.actuate.endpoint.annotation.Endpoint";
@ -101,6 +101,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION;
}
protected String constructorBindingAnnotation() {
return CONSTRUCTOR_BINDING_ANNOTATION;
}
protected String defaultValueAnnotation() {
return DEFAULT_VALUE_ANNOTATION;
}
@ -130,7 +134,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
this.metadataCollector = new MetadataCollector(env, this.metadataStore.readMetadata());
this.metadataEnv = new MetadataGenerationEnvironment(env, configurationPropertiesAnnotation(),
nestedConfigurationPropertyAnnotation(), deprecatedConfigurationPropertyAnnotation(),
defaultValueAnnotation(), endpointAnnotation(), readOperationAnnotation());
constructorBindingAnnotation(), defaultValueAnnotation(), endpointAnnotation(),
readOperationAnnotation());
}
@Override
@ -159,38 +164,16 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
private Map<Element, List<Element>> getElementsAnnotatedOrMetaAnnotatedWith(RoundEnvironment roundEnv,
TypeElement annotation) {
DeclaredType annotationType = (DeclaredType) annotation.asType();
Map<Element, List<Element>> result = new LinkedHashMap<>();
for (Element element : roundEnv.getRootElements()) {
LinkedList<Element> stack = new LinkedList<>();
stack.push(element);
collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack);
stack.removeFirst();
if (!stack.isEmpty()) {
result.put(element, Collections.unmodifiableList(stack));
List<Element> annotations = this.metadataEnv.getElementsAnnotatedOrMetaAnnotatedWith(element, annotation);
if (!annotations.isEmpty()) {
result.put(element, annotations);
}
}
return result;
}
private boolean collectElementsAnnotatedOrMetaAnnotatedWith(DeclaredType annotationType,
LinkedList<Element> stack) {
Element element = stack.peekLast();
for (AnnotationMirror annotation : this.processingEnv.getElementUtils().getAllAnnotationMirrors(element)) {
Element annotationElement = annotation.getAnnotationType().asElement();
if (!stack.contains(annotationElement)) {
stack.addLast(annotationElement);
if (annotationElement.equals(annotationType.asElement())) {
return true;
}
if (!collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack)) {
stack.removeLast();
}
}
}
return false;
}
private void processElement(Element element) {
try {
AnnotationMirror annotation = this.metadataEnv.getConfigurationPropertiesAnnotation(element);

View File

@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -86,6 +87,8 @@ class MetadataGenerationEnvironment {
private final String deprecatedConfigurationPropertyAnnotation;
private final String constructorBindingAnnotation;
private final String defaultValueAnnotation;
private final String endpointAnnotation;
@ -94,7 +97,8 @@ class MetadataGenerationEnvironment {
MetadataGenerationEnvironment(ProcessingEnvironment environment, String configurationPropertiesAnnotation,
String nestedConfigurationPropertyAnnotation, String deprecatedConfigurationPropertyAnnotation,
String defaultValueAnnotation, String endpointAnnotation, String readOperationAnnotation) {
String constructorBindingAnnotation, String defaultValueAnnotation, String endpointAnnotation,
String readOperationAnnotation) {
this.typeUtils = new TypeUtils(environment);
this.elements = environment.getElementUtils();
this.messager = environment.getMessager();
@ -102,6 +106,7 @@ class MetadataGenerationEnvironment {
this.configurationPropertiesAnnotation = configurationPropertiesAnnotation;
this.nestedConfigurationPropertyAnnotation = nestedConfigurationPropertyAnnotation;
this.deprecatedConfigurationPropertyAnnotation = deprecatedConfigurationPropertyAnnotation;
this.constructorBindingAnnotation = constructorBindingAnnotation;
this.defaultValueAnnotation = defaultValueAnnotation;
this.endpointAnnotation = endpointAnnotation;
this.readOperationAnnotation = readOperationAnnotation;
@ -170,6 +175,14 @@ class MetadataGenerationEnvironment {
return new ItemDeprecation(reason, replacement);
}
boolean hasConstructorBindingAnnotation(TypeElement typeElement) {
return hasAnnotationRecursive(typeElement, this.constructorBindingAnnotation);
}
boolean hasConstructorBindingAnnotation(ExecutableElement element) {
return hasAnnotation(element, this.constructorBindingAnnotation);
}
boolean hasAnnotation(Element element, String type) {
return getAnnotation(element, type) != null;
}
@ -185,6 +198,42 @@ class MetadataGenerationEnvironment {
return null;
}
/**
* Collect the annotations that are annotated or meta-annotated with the specified
* {@link TypeElement annotation}.
* @param element the element to inspect
* @param annotationType the annotation to discover
* @return the annotations that are annotated or meta-annotated with this annotation
*/
List<Element> getElementsAnnotatedOrMetaAnnotatedWith(Element element, TypeElement annotationType) {
LinkedList<Element> stack = new LinkedList<>();
stack.push(element);
collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack);
stack.removeFirst();
return Collections.unmodifiableList(stack);
}
private boolean hasAnnotationRecursive(Element element, String type) {
return !getElementsAnnotatedOrMetaAnnotatedWith(element, this.elements.getTypeElement(type)).isEmpty();
}
private boolean collectElementsAnnotatedOrMetaAnnotatedWith(TypeElement annotationType, LinkedList<Element> stack) {
Element element = stack.peekLast();
for (AnnotationMirror annotation : this.elements.getAllAnnotationMirrors(element)) {
Element annotationElement = annotation.getAnnotationType().asElement();
if (!stack.contains(annotationElement)) {
stack.addLast(annotationElement);
if (annotationElement.equals(annotationType)) {
return true;
}
if (!collectElementsAnnotatedOrMetaAnnotatedWith(annotationType, stack)) {
stack.removeLast();
}
}
}
return false;
}
Map<String, Object> getAnnotationElementValues(AnnotationMirror annotation) {
Map<String, Object> values = new LinkedHashMap<>();
annotation.getElementValues()

View File

@ -19,6 +19,7 @@ package org.springframework.boot.configurationprocessor;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.lang.model.element.ExecutableElement;
@ -31,6 +32,7 @@ import javax.lang.model.util.ElementFilter;
* Resolve {@link PropertyDescriptor} instances.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
class PropertyDescriptorResolver {
@ -54,13 +56,19 @@ class PropertyDescriptorResolver {
if (factoryMethod != null) {
return resolveJavaBeanProperties(type, factoryMethod, members);
}
ExecutableElement constructor = resolveConstructor(type);
if (constructor != null) {
return resolveConstructorProperties(type, factoryMethod, members, constructor);
}
else {
return resolveJavaBeanProperties(type, factoryMethod, members);
return resolve(ConfigurationPropertiesTypeElement.of(type, this.environment), factoryMethod, members);
}
private Stream<PropertyDescriptor<?>> resolve(ConfigurationPropertiesTypeElement type,
ExecutableElement factoryMethod, TypeElementMembers members) {
if (type.isConstructorBindingEnabled()) {
ExecutableElement constructor = type.getBindConstructor();
if (constructor != null) {
return resolveConstructorProperties(type.getType(), factoryMethod, members, constructor);
}
return Stream.empty();
}
return resolveJavaBeanProperties(type.getType(), factoryMethod, members);
}
Stream<PropertyDescriptor<?>> resolveConstructorProperties(TypeElement type, ExecutableElement factoryMethod,
@ -108,12 +116,66 @@ class PropertyDescriptorResolver {
return descriptor.isProperty(this.environment) || descriptor.isNested(this.environment);
}
private ExecutableElement resolveConstructor(TypeElement type) {
List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
if (constructors.size() == 1 && constructors.get(0).getParameters().size() > 0) {
return constructors.get(0);
/**
* Wrapper around a {@link TypeElement} that could be bound.
*/
private static class ConfigurationPropertiesTypeElement {
private final TypeElement type;
private final boolean constructorBoundType;
private final List<ExecutableElement> constructors;
private final List<ExecutableElement> boundConstructors;
ConfigurationPropertiesTypeElement(TypeElement type, boolean constructorBoundType,
List<ExecutableElement> constructors, List<ExecutableElement> boundConstructors) {
this.type = type;
this.constructorBoundType = constructorBoundType;
this.constructors = constructors;
this.boundConstructors = boundConstructors;
}
return null;
TypeElement getType() {
return this.type;
}
boolean isConstructorBindingEnabled() {
return this.constructorBoundType || !this.boundConstructors.isEmpty();
}
ExecutableElement getBindConstructor() {
if (this.constructorBoundType && this.boundConstructors.isEmpty()) {
return findBoundConstructor();
}
if (this.boundConstructors.size() == 1) {
return this.boundConstructors.get(0);
}
return null;
}
private ExecutableElement findBoundConstructor() {
ExecutableElement boundConstructor = null;
for (ExecutableElement canidate : this.constructors) {
if (!canidate.getParameters().isEmpty()) {
if (boundConstructor != null) {
return null;
}
boundConstructor = canidate;
}
}
return boundConstructor;
}
static ConfigurationPropertiesTypeElement of(TypeElement type, MetadataGenerationEnvironment env) {
boolean constructorBoundType = env.hasConstructorBindingAnnotation(type);
List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
List<ExecutableElement> boundConstructors = constructors.stream()
.filter(env::hasConstructorBindingAnnotation).collect(Collectors.toList());
return new ConfigurationPropertiesTypeElement(type, constructorBoundType, constructors, boundConstructors);
}
}
}

View File

@ -35,6 +35,7 @@ class MetadataGenerationEnvironmentFactory implements Function<ProcessingEnviron
TestConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.NESTED_CONFIGURATION_PROPERTY_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.CONSTRUCTOR_BINDING_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.DEFAULT_VALUE_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.ENDPOINT_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.READ_OPERATION_ANNOTATION);

View File

@ -34,6 +34,8 @@ import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester;
import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor;
import org.springframework.boot.configurationsample.immutable.ImmutableClassConstructorBindingProperties;
import org.springframework.boot.configurationsample.immutable.ImmutableMultiConstructorProperties;
import org.springframework.boot.configurationsample.immutable.ImmutableSimpleProperties;
import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties;
import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties;
@ -42,6 +44,8 @@ import org.springframework.boot.configurationsample.simple.HierarchicalPropertie
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesGrandparent;
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesParent;
import org.springframework.boot.configurationsample.simple.SimpleProperties;
import org.springframework.boot.configurationsample.specific.MatchingConstructorNoDirectiveProperties;
import org.springframework.boot.configurationsample.specific.TwoConstructorsClassConstructorBindingExample;
import org.springframework.boot.configurationsample.specific.TwoConstructorsExample;
import org.springframework.boot.testsupport.compiler.TestCompiler;
@ -96,13 +100,45 @@ class PropertyDescriptorResolverTests {
}
@Test
void propertiesWithConstructorParameters() throws IOException {
void propertiesWithConstructorWithConstructorBinding() throws IOException {
process(ImmutableSimpleProperties.class, propertyNames(
(stream) -> assertThat(stream).containsExactly("theName", "flag", "comparator", "counter")));
process(ImmutableSimpleProperties.class, properties((stream) -> assertThat(stream)
.allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor)));
}
@Test
void propertiesWithSeveralConstructors() throws IOException {
void propertiesWithConstructorAndClassConstructorBinding() throws IOException {
process(ImmutableClassConstructorBindingProperties.class,
propertyNames((stream) -> assertThat(stream).containsExactly("name", "description")));
process(ImmutableClassConstructorBindingProperties.class, properties((stream) -> assertThat(stream)
.allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor)));
}
@Test
void propertiesWithConstructorAndClassConstructorBindingAndSeveralCandidates() throws IOException {
process(TwoConstructorsClassConstructorBindingExample.class,
propertyNames((stream) -> assertThat(stream).isEmpty()));
}
@Test
void propertiesWithConstructorNoDirective() throws IOException {
process(MatchingConstructorNoDirectiveProperties.class,
propertyNames((stream) -> assertThat(stream).containsExactly("name")));
process(MatchingConstructorNoDirectiveProperties.class, properties((stream) -> assertThat(stream)
.allMatch((predicate) -> predicate instanceof JavaBeanPropertyDescriptor)));
}
@Test
void propertiesWithMultiConstructor() throws IOException {
process(ImmutableMultiConstructorProperties.class,
propertyNames((stream) -> assertThat(stream).containsExactly("name", "description")));
process(ImmutableMultiConstructorProperties.class, properties((stream) -> assertThat(stream)
.allMatch((predicate) -> predicate instanceof ConstructorParameterPropertyDescriptor)));
}
@Test
void propertiesWithMultiConstructorNoDirective() throws IOException {
process(TwoConstructorsExample.class, propertyNames((stream) -> assertThat(stream).containsExactly("name")));
process(TwoConstructorsExample.class,
properties((stream) -> assertThat(stream).element(0).isInstanceOf(JavaBeanPropertyDescriptor.class)));

View File

@ -47,6 +47,8 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
public static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.configurationsample.DeprecatedConfigurationProperty";
public static final String CONSTRUCTOR_BINDING_ANNOTATION = "org.springframework.boot.configurationsample.ConstructorBinding";
public static final String DEFAULT_VALUE_ANNOTATION = "org.springframework.boot.configurationsample.DefaultValue";
public static final String ENDPOINT_ANNOTATION = "org.springframework.boot.configurationsample.Endpoint";
@ -76,6 +78,11 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
return DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION;
}
@Override
protected String constructorBindingAnnotation() {
return CONSTRUCTOR_BINDING_ANNOTATION;
}
@Override
protected String defaultValueAnnotation() {
return DEFAULT_VALUE_ANNOTATION;

View File

@ -0,0 +1,36 @@
/*
* Copyright 2012-2019 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 @ConstructorBinding} for testing (removes the need
* for a dependency on the real annotation).
*
* @author Stephane Nicoll
*/
@Target({ ElementType.TYPE, ElementType.CONSTRUCTOR })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConstructorBinding {
}

View File

@ -0,0 +1,31 @@
/*
* Copyright 2012-2019 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;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ConstructorBinding
public @interface MetaConstructorBinding {
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2012-2019 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.immutable;
import org.springframework.boot.configurationsample.MetaConstructorBinding;
/**
* Simple immutable properties with several constructors.
*
* @author Stephane Nicoll
*/
@SuppressWarnings("unused")
@MetaConstructorBinding
public class ImmutableClassConstructorBindingProperties {
private final String name;
private final String description;
public ImmutableClassConstructorBindingProperties(String name, String description) {
this.name = name;
this.description = description;
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2012-2019 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.immutable;
import org.springframework.boot.configurationsample.ConstructorBinding;
/**
* Simple immutable properties with several constructors.
*
* @author Stephane Nicoll
*/
@SuppressWarnings("unused")
public class ImmutableMultiConstructorProperties {
private final String name;
/**
* Test description.
*/
private final String description;
public ImmutableMultiConstructorProperties(String name) {
this(name, null);
}
@ConstructorBinding
public ImmutableMultiConstructorProperties(String name, String description) {
this.name = name;
this.description = description;
}
}

View File

@ -19,6 +19,7 @@ package org.springframework.boot.configurationsample.immutable;
import java.util.Comparator;
import org.springframework.boot.configurationsample.ConfigurationProperties;
import org.springframework.boot.configurationsample.ConstructorBinding;
import org.springframework.boot.configurationsample.DefaultValue;
/**
@ -47,6 +48,7 @@ public class ImmutableSimpleProperties {
@SuppressWarnings("unused")
private final Long counter;
@ConstructorBinding
public ImmutableSimpleProperties(@DefaultValue("boot") String theName, boolean flag, Comparator<?> comparator,
Long counter) {
this.theName = theName;

View File

@ -17,6 +17,7 @@
package org.springframework.boot.configurationsample.specific;
import org.springframework.boot.configurationsample.ConfigurationProperties;
import org.springframework.boot.configurationsample.ConstructorBinding;
import org.springframework.boot.configurationsample.DefaultValue;
/**
@ -29,6 +30,7 @@ public class InvalidDefaultValueCharacterProperties {
private final char letter;
@ConstructorBinding
public InvalidDefaultValueCharacterProperties(@DefaultValue("bad") char letter) {
this.letter = letter;
}

View File

@ -17,6 +17,7 @@
package org.springframework.boot.configurationsample.specific;
import org.springframework.boot.configurationsample.ConfigurationProperties;
import org.springframework.boot.configurationsample.ConstructorBinding;
import org.springframework.boot.configurationsample.DefaultValue;
/**
@ -26,6 +27,7 @@ import org.springframework.boot.configurationsample.DefaultValue;
* @author Stephane Nicoll
*/
@ConfigurationProperties("test")
@ConstructorBinding
public class InvalidDefaultValueFloatingPointProperties {
private final Double ratio;

View File

@ -18,6 +18,7 @@ package org.springframework.boot.configurationsample.specific;
import org.springframework.boot.configurationsample.ConfigurationProperties;
import org.springframework.boot.configurationsample.DefaultValue;
import org.springframework.boot.configurationsample.MetaConstructorBinding;
/**
* Demonstrates that an invalid default number value leads to a compilation failure.
@ -25,6 +26,7 @@ import org.springframework.boot.configurationsample.DefaultValue;
* @author Stephane Nicoll
*/
@ConfigurationProperties("test")
@MetaConstructorBinding
public class InvalidDefaultValueNumberProperties {
private final int counter;

View File

@ -0,0 +1,40 @@
/*
* Copyright 2012-2019 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.specific;
/**
* Simple properties with a constructor but no binding directive.
*
* @author Stephane Nicoll
*/
public class MatchingConstructorNoDirectiveProperties {
private String name;
public MatchingConstructorNoDirectiveProperties(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2012-2019 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.specific;
import org.springframework.boot.configurationsample.MetaConstructorBinding;
/**
* A type that declares constructor binding but with two available constructors.
*
* @author Stephane Nicoll
*/
@MetaConstructorBinding
public class TwoConstructorsClassConstructorBindingExample {
private String name;
private String description;
public TwoConstructorsClassConstructorBindingExample(String name) {
this(name, null);
}
public TwoConstructorsClassConstructorBindingExample(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}