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:
committed by
Phillip Webb
parent
4208c93ba7
commit
45f6668d03
@@ -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);
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user