Skip to content

Commit 4415b4a

Browse files
committed
Polish "Allow NestedConfigurationProperty on getters"
See gh-38844
1 parent 6c8987e commit 4415b4a

File tree

9 files changed

+161
-11
lines changed

9 files changed

+161
-11
lines changed

spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/packaging/native-image/advanced-topics.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ include-code::Nested[]
1717

1818
The example above produces configuration properties for `my.properties.name` and `my.properties.nested.number`.
1919
Without the `@NestedConfigurationProperty` annotation on the `nested` field, the `my.properties.nested.number` property would not be bindable in a native image.
20+
You can also annotate the getter method.
2021

2122
When using constructor binding, you have to annotate the field with `@NestedConfigurationProperty`:
2223

spring-boot-project/spring-boot-docs/src/docs/antora/modules/specification/pages/configuration-metadata/annotation-processor.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ Consider the updated example:
130130
include-code::MyServerProperties[]
131131

132132
The preceding example produces metadata information for `my.server.name`, `my.server.host.ip`, and `my.server.host.port` properties.
133-
You can use the `@NestedConfigurationProperty` annotation on a field to indicate that a regular (non-inner) class should be treated as if it were nested.
133+
You can use the `@NestedConfigurationProperty` annotation on a field or a getter method to indicate that a regular (non-inner) class should be treated as if it were nested.
134134

135135
TIP: This has no effect on collections and maps, as those types are automatically identified, and a single metadata property is generated for each of them.
136136

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
2323
import org.springframework.boot.configurationprocessor.metadata.Metadata;
2424
import org.springframework.boot.configurationsample.deprecation.Dbcp2Configuration;
25+
import org.springframework.boot.configurationsample.method.NestedPropertiesMethod;
2526
import org.springframework.boot.configurationsample.record.ExampleRecord;
2627
import org.springframework.boot.configurationsample.record.NestedPropertiesRecord;
2728
import org.springframework.boot.configurationsample.record.RecordWithGetter;
@@ -337,8 +338,9 @@ void innerClassProperties() {
337338
assertThat(metadata).has(Metadata.withProperty("config.third.value"));
338339
assertThat(metadata).has(Metadata.withProperty("config.fourth"));
339340
assertThat(metadata).isNotEqualTo(Metadata.withGroup("config.fourth"));
340-
assertThat(metadata)
341-
.has(Metadata.withGroup("config.fifth").ofType(DeprecatedSimplePojo.class).fromSource(InnerClassProperties.class));
341+
assertThat(metadata).has(Metadata.withGroup("config.fifth")
342+
.ofType(DeprecatedSimplePojo.class)
343+
.fromSource(InnerClassProperties.class));
342344
assertThat(metadata).has(Metadata.withProperty("config.fifth.value").withDeprecation());
343345
}
344346

@@ -362,6 +364,15 @@ void innerClassAnnotatedGetterConfig() {
362364
assertThat(metadata).isNotEqualTo(Metadata.withProperty("specific.foo"));
363365
}
364366

367+
@Test
368+
void nestedClassMethod() {
369+
ConfigurationMetadata metadata = compile(NestedPropertiesMethod.class);
370+
assertThat(metadata).has(Metadata.withGroup("method-nested.nested"));
371+
assertThat(metadata).has(Metadata.withProperty("method-nested.nested.my-nested-property"));
372+
assertThat(metadata).has(Metadata.withGroup("method-nested.inner.nested"));
373+
assertThat(metadata).has(Metadata.withProperty("method-nested.inner.nested.my-nested-property"));
374+
}
375+
365376
@Test
366377
void nestedClassChildProperties() {
367378
ConfigurationMetadata metadata = compile(ClassWithNestedProperties.class);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2024 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.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.configurationsample.method;
18+
19+
import org.springframework.boot.configurationsample.ConfigurationProperties;
20+
import org.springframework.boot.configurationsample.NestedConfigurationProperty;
21+
22+
@ConfigurationProperties("method-nested")
23+
public class NestedPropertiesMethod {
24+
25+
private String myProperty;
26+
27+
private final NestedProperty nested = new NestedProperty();
28+
29+
private final Inner inner = new Inner();
30+
31+
public String getMyProperty() {
32+
return this.myProperty;
33+
}
34+
35+
public void setMyProperty(String myProperty) {
36+
this.myProperty = myProperty;
37+
}
38+
39+
@NestedConfigurationProperty
40+
public NestedProperty getNested() {
41+
return this.nested;
42+
}
43+
44+
public Inner getInner() {
45+
return this.inner;
46+
}
47+
48+
public static class Inner {
49+
50+
private String myInnerProperty;
51+
52+
private final NestedProperty nested = new NestedProperty();
53+
54+
public String getMyInnerProperty() {
55+
return this.myInnerProperty;
56+
}
57+
58+
public void setMyInnerProperty(String myInnerProperty) {
59+
this.myInnerProperty = myInnerProperty;
60+
}
61+
62+
@NestedConfigurationProperty
63+
public NestedProperty getNested() {
64+
return this.nested;
65+
}
66+
67+
}
68+
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.configurationsample.method;
18+
19+
public class NestedProperty {
20+
21+
private String myNestedProperty;
22+
23+
public String getMyNestedProperty() {
24+
return this.myNestedProperty;
25+
}
26+
27+
public void setMyNestedProperty(String myNestedProperty) {
28+
this.myNestedProperty = myNestedProperty;
29+
}
30+
31+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2024 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.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2024 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.

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

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2022 the original author or authors.
2+
* Copyright 2012-2024 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.
@@ -25,14 +25,52 @@
2525
import org.springframework.boot.context.properties.bind.Nested;
2626

2727
/**
28-
* Indicates that a field in a {@link ConfigurationProperties @ConfigurationProperties}
28+
* Indicates that a property in a {@link ConfigurationProperties @ConfigurationProperties}
2929
* object should be treated as if it were a nested type. This annotation has no bearing on
3030
* the actual binding processes, but it is used by the
31-
* {@code spring-boot-configuration-processor} as a hint that a field is not bound as a
32-
* single value. When this is specified, a nested group is created for the field and its
33-
* type is harvested.
31+
* {@code spring-boot-configuration-processor} as a hint that a property is not bound as a
32+
* single value. When this is specified, a nested group is created for the property and
33+
* its type is harvested.
34+
* <p>
35+
* In the example below, {@code Host} is flagged as a nested property using its field and
36+
* an {@code example.server.host} nested group is created with any property that
37+
* {@code Host} defines:<pre><code class="java">
38+
* &#064;ConfigurationProperties("example.server")
39+
* class ServerProperties {
40+
*
41+
* &#064;NestedConfigurationProperty
42+
* private final Host host = new Host();
43+
*
44+
* public Host getHost() { ... }
45+
*
46+
* // Other properties, getter, setter.
47+
*
48+
* }</code></pre>
49+
* <p>
50+
* The annotation can also be specified on a getter method. If you use records, you can
51+
* annotate the record component.
3452
* <p>
3553
* This has no effect on collections and maps as these types are automatically identified.
54+
* Also, the annotation is not necessary if the target type is an inner class of the
55+
* {@link ConfigurationProperties @ConfigurationProperties} object. In the example below,
56+
* {@code Host} is detected as a nested type as it is defined as an inner class:
57+
* <pre><code class="java">
58+
* &#064;ConfigurationProperties("example.server")
59+
* class ServerProperties {
60+
*
61+
* private final Host host = new Host();
62+
*
63+
* public Host getHost() { ... }
64+
*
65+
* // Other properties, getter, setter.
66+
*
67+
* public static class Host {
68+
*
69+
* // properties, getter, setter.
70+
*
71+
* }
72+
*
73+
* }</code></pre>
3674
*
3775
* @author Stephane Nicoll
3876
* @author Phillip Webb

0 commit comments

Comments
 (0)