Skip to content

Commit a1ee9c4

Browse files
committed
Merge pull request #29010 from An1s9n
* gh-29010: Polish "Allow @DefaultValue to be used on record components" Allow @DefaultValue to be used on record components Closes gh-29010
2 parents 1793cee + 5ee3fda commit a1ee9c4

File tree

5 files changed

+62
-5
lines changed

5 files changed

+62
-5
lines changed

spring-boot-project/spring-boot-docs/src/docs/asciidoc/features/external-config.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,8 @@ Unless your record has multiple constructors, there is no need to use `@Construc
719719

720720
Nested members of a constructor bound class (such as `Security` in the example above) will also be bound through their constructor.
721721

722-
Default values can be specified using `@DefaultValue` and the same conversion service will be applied to coerce the `String` value to the target type of a missing property.
722+
Default values can be specified using `@DefaultValue` on constructor parameters and record components.
723+
The conversion service will be applied to coerce the annotation's `String` value to the target type of a missing property.
723724
By default, if no properties are bound to `Security`, the `MyProperties` instance will contain a `null` value for `security`.
724725
If you wish you return a non-null instance of `Security` even when no properties are bound to it, you can use an empty `@DefaultValue` annotation to do so:
725726

spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
* @author Andy Wilkinson
7575
* @author Kris De Volder
7676
* @author Jonas Keßler
77+
* @author Pavel Anisimov
7778
*/
7879
class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGenerationTests {
7980

@@ -445,4 +446,23 @@ void multiConstructorRecordProperties(@TempDir File temp) throws IOException {
445446
assertThat(metadata).doesNotHave(Metadata.withProperty("multi.some-integer"));
446447
}
447448

449+
@Test
450+
void recordPropertiesWithDefaultValues(@TempDir File temp) throws IOException {
451+
File exampleRecord = new File(temp, "ExampleRecord.java");
452+
try (PrintWriter writer = new PrintWriter(new FileWriter(exampleRecord))) {
453+
writer.println(
454+
"@org.springframework.boot.configurationsample.ConfigurationProperties(\"record.defaults\")");
455+
writer.println("public record ExampleRecord(");
456+
writer.println("@org.springframework.boot.configurationsample.DefaultValue(\"An1s9n\") String someString,");
457+
writer.println("@org.springframework.boot.configurationsample.DefaultValue(\"594\") Integer someInteger");
458+
writer.println(") {");
459+
writer.println("}");
460+
}
461+
ConfigurationMetadata metadata = compile(exampleRecord);
462+
assertThat(metadata)
463+
.has(Metadata.withProperty("record.defaults.some-string", String.class).withDefaultValue("An1s9n"));
464+
assertThat(metadata)
465+
.has(Metadata.withProperty("record.defaults.some-integer", Integer.class).withDefaultValue(594));
466+
}
467+
448468
}

spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/DefaultValue.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,8 +27,9 @@
2727
* dependency on the real annotation).
2828
*
2929
* @author Stephane Nicoll
30+
* @author Pavel Anisimov
3031
*/
31-
@Target({ ElementType.PARAMETER })
32+
@Target({ ElementType.PARAMETER, ElementType.RECORD_COMPONENT })
3233
@Retention(RetentionPolicy.RUNTIME)
3334
@Documented
3435
public @interface DefaultValue {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/DefaultValue.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@
3737
* must be constant.
3838
*
3939
* @author Madhura Bhave
40+
* @author Pavel Anisimov
4041
* @since 2.2.0
4142
*/
4243
@Retention(RetentionPolicy.RUNTIME)
43-
@Target({ ElementType.PARAMETER })
44+
@Target({ ElementType.PARAMETER, ElementType.RECORD_COMPONENT })
4445
@Documented
4546
public @interface DefaultValue {
4647

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,23 +16,33 @@
1616

1717
package org.springframework.boot.context.properties.bind;
1818

19+
import java.io.File;
20+
import java.io.FileWriter;
21+
import java.io.IOException;
22+
import java.io.PrintWriter;
1923
import java.lang.reflect.Constructor;
24+
import java.net.URL;
25+
import java.net.URLClassLoader;
2026
import java.nio.file.Path;
2127
import java.nio.file.Paths;
2228
import java.time.LocalDate;
2329
import java.util.ArrayList;
30+
import java.util.Arrays;
2431
import java.util.List;
2532
import java.util.Map;
2633
import java.util.Objects;
2734

2835
import org.junit.jupiter.api.Test;
36+
import org.junit.jupiter.api.io.TempDir;
2937

3038
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
3139
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
3240
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource;
41+
import org.springframework.boot.testsupport.compiler.TestCompiler;
3342
import org.springframework.core.ResolvableType;
3443
import org.springframework.core.convert.ConversionService;
3544
import org.springframework.format.annotation.DateTimeFormat;
45+
import org.springframework.test.util.ReflectionTestUtils;
3646
import org.springframework.util.Assert;
3747

3848
import static org.assertj.core.api.Assertions.assertThat;
@@ -43,6 +53,7 @@
4353
*
4454
* @author Madhura Bhave
4555
* @author Phillip Webb
56+
* @author Pavel Anisimov
4657
*/
4758
class ValueObjectBinderTests {
4859

@@ -357,6 +368,29 @@ void bindToAnnotationNamedParameter() {
357368
assertThat(bound.getImportName()).isEqualTo("test");
358369
}
359370

371+
@Test
372+
void bindToRecordWithDefaultValue(@TempDir File tempDir) throws IOException, ClassNotFoundException {
373+
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
374+
source.put("test.record.property1", "value-from-config-1");
375+
this.sources.add(source);
376+
File recordProperties = new File(tempDir, "RecordProperties.java");
377+
try (PrintWriter writer = new PrintWriter(new FileWriter(recordProperties))) {
378+
writer.println("public record RecordProperties(");
379+
writer.println(
380+
"@org.springframework.boot.context.properties.bind.DefaultValue(\"default-value-1\") String property1,");
381+
writer.println(
382+
"@org.springframework.boot.context.properties.bind.DefaultValue(\"default-value-2\") String property2");
383+
writer.println(") {");
384+
writer.println("}");
385+
}
386+
TestCompiler compiler = new TestCompiler(tempDir);
387+
compiler.getTask(Arrays.asList(recordProperties)).call();
388+
ClassLoader ucl = new URLClassLoader(new URL[] { tempDir.toURI().toURL() });
389+
Object bean = this.binder.bind("test.record", Class.forName("RecordProperties", true, ucl)).get();
390+
assertThat(ReflectionTestUtils.getField(bean, "property1")).isEqualTo("value-from-config-1");
391+
assertThat(ReflectionTestUtils.getField(bean, "property2")).isEqualTo("default-value-2");
392+
}
393+
360394
private void noConfigurationProperty(BindException ex) {
361395
assertThat(ex.getProperty()).isNull();
362396
}

0 commit comments

Comments
 (0)