From cd403e8dff54a9aca11b7cdb82694df70b5cea27 Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Fri, 10 Dec 2021 19:48:05 +0300 Subject: [PATCH 1/8] Add RECORD_COMPONENT and FIELD targets to DefaultValue --- .../boot/context/properties/bind/DefaultValue.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java index 565a8cc7fab0..f78af52804da 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java @@ -31,13 +31,16 @@ * {@link org.springframework.core.env.Environment} when binding to * {@link org.springframework.boot.context.properties.ConfigurationProperties @ConfigurationProperties}, * the default value for the property will not be used even if the property value is - * empty. + * empty. You can apply this annotation to both parameters and record components, but this annotation + * applied to fields is ignored unless you use some source code generating engine like Project Lombok + * to generate constructors or methods and pass to them fields with applied annotations as parameters. * * @author Madhura Bhave + * @author Pavel Anisimov * @since 2.2.0 */ @Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.PARAMETER }) +@Target({ ElementType.PARAMETER, ElementType.RECORD_COMPONENT, ElementType.FIELD }) @Documented public @interface DefaultValue { From 043329b5e54f86410b0734f6ec53468a27f9d5b9 Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Tue, 14 Dec 2021 11:25:33 +0300 Subject: [PATCH 2/8] Add METHOD target to DefaultValue --- .../boot/context/properties/bind/DefaultValue.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java index f78af52804da..33f9ad64a001 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java @@ -32,15 +32,16 @@ * {@link org.springframework.boot.context.properties.ConfigurationProperties @ConfigurationProperties}, * the default value for the property will not be used even if the property value is * empty. You can apply this annotation to both parameters and record components, but this annotation - * applied to fields is ignored unless you use some source code generating engine like Project Lombok - * to generate constructors or methods and pass to them fields with applied annotations as parameters. + * applied to fields or methods is ignored. Fields and methods support is added for usage with source + * code generating engines like Project Lombok with help of which you can pass annotation of a field + * to a constructor parameter. * * @author Madhura Bhave * @author Pavel Anisimov * @since 2.2.0 */ @Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.PARAMETER, ElementType.RECORD_COMPONENT, ElementType.FIELD }) +@Target({ ElementType.PARAMETER, ElementType.RECORD_COMPONENT, ElementType.FIELD, ElementType.METHOD }) @Documented public @interface DefaultValue { From 22d4b736dded2148f739ef094b5bf64be3161f9b Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Tue, 14 Dec 2021 11:28:13 +0300 Subject: [PATCH 3/8] Add RECORD_COMPONENT, FIELD and METHOD targets to DefaultValue in test source --- .../springframework/boot/configurationsample/DefaultValue.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DefaultValue.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DefaultValue.java index 359f8d4c07b4..c2694ed62d1a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DefaultValue.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DefaultValue.java @@ -27,8 +27,9 @@ * dependency on the real annotation). * * @author Stephane Nicoll + * @author Pavel Anisimov */ -@Target({ ElementType.PARAMETER }) +@Target({ ElementType.PARAMETER, ElementType.RECORD_COMPONENT, ElementType.FIELD, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DefaultValue { From 17f86dc269fc3ba9154f921ae90587dfbe4583bb Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Tue, 14 Dec 2021 17:30:44 +0300 Subject: [PATCH 4/8] Polish --- .../boot/context/properties/bind/DefaultValue.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java index 33f9ad64a001..9cfaca1a6601 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java @@ -31,10 +31,10 @@ * {@link org.springframework.core.env.Environment} when binding to * {@link org.springframework.boot.context.properties.ConfigurationProperties @ConfigurationProperties}, * the default value for the property will not be used even if the property value is - * empty. You can apply this annotation to both parameters and record components, but this annotation - * applied to fields or methods is ignored. Fields and methods support is added for usage with source - * code generating engines like Project Lombok with help of which you can pass annotation of a field - * to a constructor parameter. + * empty. You can apply this annotation to both parameters and record components, but this + * annotation applied to fields or methods is ignored. Fields and methods support is added + * for usage with source code generating engines like Project Lombok with help of which + * you can pass annotation of a field to a constructor parameter. * * @author Madhura Bhave * @author Pavel Anisimov From 7d6c977e6fa5adcce66c046dbb01099034cac652 Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Fri, 7 Jan 2022 10:23:12 +0300 Subject: [PATCH 5/8] Remove FIELD and METHOD targets from DefaultValue annotation --- .../boot/configurationsample/DefaultValue.java | 2 +- .../boot/context/properties/bind/DefaultValue.java | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DefaultValue.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DefaultValue.java index c2694ed62d1a..5bdd5c831304 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DefaultValue.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DefaultValue.java @@ -29,7 +29,7 @@ * @author Stephane Nicoll * @author Pavel Anisimov */ -@Target({ ElementType.PARAMETER, ElementType.RECORD_COMPONENT, ElementType.FIELD, ElementType.METHOD }) +@Target({ ElementType.PARAMETER, ElementType.RECORD_COMPONENT }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DefaultValue { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java index 9cfaca1a6601..6a5451a8f169 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java @@ -31,17 +31,14 @@ * {@link org.springframework.core.env.Environment} when binding to * {@link org.springframework.boot.context.properties.ConfigurationProperties @ConfigurationProperties}, * the default value for the property will not be used even if the property value is - * empty. You can apply this annotation to both parameters and record components, but this - * annotation applied to fields or methods is ignored. Fields and methods support is added - * for usage with source code generating engines like Project Lombok with help of which - * you can pass annotation of a field to a constructor parameter. + * empty. * * @author Madhura Bhave * @author Pavel Anisimov * @since 2.2.0 */ @Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.PARAMETER, ElementType.RECORD_COMPONENT, ElementType.FIELD, ElementType.METHOD }) +@Target({ ElementType.PARAMETER, ElementType.RECORD_COMPONENT }) @Documented public @interface DefaultValue { From 6cd8dada56f6183f1c43593b81445893f251f962 Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Fri, 7 Jan 2022 23:12:39 +0300 Subject: [PATCH 6/8] Add ValueObjectBinderTests#bindToRecordWithDefaultValue --- .../bind/ValueObjectBinderTests.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java index 7286c7c348ec..60702395fd24 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java @@ -16,20 +16,31 @@ package org.springframework.boot.context.properties.bind; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; import java.lang.reflect.Constructor; +import java.net.URL; +import java.net.URLClassLoader; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDate; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.MockConfigurationPropertySource; +import org.springframework.boot.testsupport.compiler.TestCompiler; import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; import org.springframework.format.annotation.DateTimeFormat; @@ -37,12 +48,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.test.util.ReflectionTestUtils.getField; /** * Tests for {@link ValueObjectBinder}. * * @author Madhura Bhave * @author Phillip Webb + * @author Pavel Anisimov */ class ValueObjectBinderTests { @@ -357,6 +370,30 @@ void bindToAnnotationNamedParameter() { assertThat(bound.getImportName()).isEqualTo("test"); } + @Test + @EnabledForJreRange(min = JRE.JAVA_16) + void bindToRecordWithDefaultValue(@TempDir File tempDir) throws IOException, ClassNotFoundException { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("test.record.property1", "value-from-config-1"); + this.sources.add(source); + File recordProperties = new File(tempDir, "RecordProperties.java"); + try (PrintWriter writer = new PrintWriter(new FileWriter(recordProperties))) { + writer.println("public record RecordProperties("); + writer.println( + "@org.springframework.boot.context.properties.bind.DefaultValue(\"default-value-1\") String property1,"); + writer.println( + "@org.springframework.boot.context.properties.bind.DefaultValue(\"default-value-2\") String property2"); + writer.println(") {"); + writer.println("}"); + } + TestCompiler compiler = new TestCompiler(tempDir); + compiler.getTask(Arrays.asList(recordProperties)).call(); + ClassLoader ucl = new URLClassLoader(new URL[] { tempDir.toURI().toURL() }); + Object bean = this.binder.bind("test.record", Class.forName("RecordProperties", true, ucl)).get(); + assertThat(getField(bean, "property1")).isEqualTo("value-from-config-1"); + assertThat(getField(bean, "property2")).isEqualTo("default-value-2"); + } + private void noConfigurationProperty(BindException ex) { assertThat(ex.getProperty()).isNull(); } From e0542edfc8e7fafa3135bbb19819f4e8d67e6f2f Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Sat, 8 Jan 2022 09:22:39 +0300 Subject: [PATCH 7/8] Fixes due to checkstyleTest failure in ValueObjectBinderTests --- .../context/properties/bind/ValueObjectBinderTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java index 60702395fd24..b0628e247278 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java @@ -33,10 +33,10 @@ import java.util.Objects; import org.junit.jupiter.api.Test; - import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; import org.junit.jupiter.api.io.TempDir; + import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.MockConfigurationPropertySource; @@ -44,11 +44,11 @@ import org.springframework.core.ResolvableType; import org.springframework.core.convert.ConversionService; import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.Assert; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.test.util.ReflectionTestUtils.getField; /** * Tests for {@link ValueObjectBinder}. @@ -390,8 +390,8 @@ void bindToRecordWithDefaultValue(@TempDir File tempDir) throws IOException, Cla compiler.getTask(Arrays.asList(recordProperties)).call(); ClassLoader ucl = new URLClassLoader(new URL[] { tempDir.toURI().toURL() }); Object bean = this.binder.bind("test.record", Class.forName("RecordProperties", true, ucl)).get(); - assertThat(getField(bean, "property1")).isEqualTo("value-from-config-1"); - assertThat(getField(bean, "property2")).isEqualTo("default-value-2"); + assertThat(ReflectionTestUtils.getField(bean, "property1")).isEqualTo("value-from-config-1"); + assertThat(ReflectionTestUtils.getField(bean, "property2")).isEqualTo("default-value-2"); } private void noConfigurationProperty(BindException ex) { From fdb7f85b393f7bf696327c95cf41f233b5cc79e3 Mon Sep 17 00:00:00 2001 From: Pavel Anisimov Date: Wed, 12 Jan 2022 17:26:53 +0300 Subject: [PATCH 8/8] Add test ConfigurationMetadataAnnotationProcessorTests#recordPropertiesWithDefaultValues --- ...ationMetadataAnnotationProcessorTests.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index 756d4b9a4ca5..52d034bc5a5b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -74,6 +74,7 @@ * @author Andy Wilkinson * @author Kris De Volder * @author Jonas Keßler + * @author Pavel Anisimov */ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGenerationTests { @@ -460,4 +461,24 @@ void multiConstructorRecordProperties(@TempDir File temp) throws IOException { assertThat(metadata).doesNotHave(Metadata.withProperty("multi.some-integer")); } + @Test + @EnabledForJreRange(min = JRE.JAVA_16) + void recordPropertiesWithDefaultValues(@TempDir File temp) throws IOException { + File exampleRecord = new File(temp, "ExampleRecord.java"); + try (PrintWriter writer = new PrintWriter(new FileWriter(exampleRecord))) { + writer.println( + "@org.springframework.boot.configurationsample.ConfigurationProperties(\"record.defaults\")"); + writer.println("public record ExampleRecord("); + writer.println("@org.springframework.boot.configurationsample.DefaultValue(\"An1s9n\") String someString,"); + writer.println("@org.springframework.boot.configurationsample.DefaultValue(\"594\") Integer someInteger"); + writer.println(") {"); + writer.println("}"); + } + ConfigurationMetadata metadata = compile(exampleRecord); + assertThat(metadata) + .has(Metadata.withProperty("record.defaults.some-string", String.class).withDefaultValue("An1s9n")); + assertThat(metadata) + .has(Metadata.withProperty("record.defaults.some-integer", Integer.class).withDefaultValue(594)); + } + }