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

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