Skip to content

Commit 2da3875

Browse files
committed
Merge pull request #38844 from jaredtbates
* pr/38844: Polish "Allow NestedConfigurationProperty on getters" Allow NestedConfigurationProperty on getters Closes gh-38844
2 parents 5d4a777 + 4415b4a commit 2da3875

File tree

10 files changed

+210
-11
lines changed

10 files changed

+210
-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/main/java/org/springframework/boot/configurationprocessor/JavaBeanPropertyDescriptor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ ExecutableElement getSetter() {
5353

5454
@Override
5555
protected boolean isMarkedAsNested(MetadataGenerationEnvironment environment) {
56-
return environment.getNestedConfigurationPropertyAnnotation(this.field) != null;
56+
return environment.getNestedConfigurationPropertyAnnotation(this.field) != null
57+
|| environment.getNestedConfigurationPropertyAnnotation(getGetter()) != null;
5758
}
5859

5960
@Override

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

Lines changed: 15 additions & 0 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;
@@ -45,6 +46,7 @@
4546
import org.springframework.boot.configurationsample.specific.BoxingPojo;
4647
import org.springframework.boot.configurationsample.specific.BuilderPojo;
4748
import org.springframework.boot.configurationsample.specific.DeprecatedLessPreciseTypePojo;
49+
import org.springframework.boot.configurationsample.specific.DeprecatedSimplePojo;
4850
import org.springframework.boot.configurationsample.specific.DeprecatedUnrelatedMethodPojo;
4951
import org.springframework.boot.configurationsample.specific.DoubleRegistrationProperties;
5052
import org.springframework.boot.configurationsample.specific.EmptyDefaultValueProperties;
@@ -336,6 +338,10 @@ void innerClassProperties() {
336338
assertThat(metadata).has(Metadata.withProperty("config.third.value"));
337339
assertThat(metadata).has(Metadata.withProperty("config.fourth"));
338340
assertThat(metadata).isNotEqualTo(Metadata.withGroup("config.fourth"));
341+
assertThat(metadata).has(Metadata.withGroup("config.fifth")
342+
.ofType(DeprecatedSimplePojo.class)
343+
.fromSource(InnerClassProperties.class));
344+
assertThat(metadata).has(Metadata.withProperty("config.fifth.value").withDeprecation());
339345
}
340346

341347
@Test
@@ -358,6 +364,15 @@ void innerClassAnnotatedGetterConfig() {
358364
assertThat(metadata).isNotEqualTo(Metadata.withProperty("specific.foo"));
359365
}
360366

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+
361376
@Test
362377
void nestedClassChildProperties() {
363378
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: 2 additions & 2 deletions
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.
@@ -30,7 +30,7 @@
3030
* @author Phillip Webb
3131
* @since 1.2.0
3232
*/
33-
@Target({ ElementType.FIELD, ElementType.RECORD_COMPONENT })
33+
@Target({ ElementType.FIELD, ElementType.RECORD_COMPONENT, ElementType.METHOD })
3434
@Retention(RetentionPolicy.RUNTIME)
3535
@Documented
3636
public @interface NestedConfigurationProperty {
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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.specific;
18+
19+
/**
20+
* POJO for use with samples needing a deprecated value.
21+
*
22+
* @author Jared Bates
23+
*/
24+
public class DeprecatedSimplePojo {
25+
26+
private int value;
27+
28+
@Deprecated
29+
public int getValue() {
30+
return this.value;
31+
}
32+
33+
public void setValue(int value) {
34+
this.value = value;
35+
}
36+
37+
}

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

Lines changed: 8 additions & 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.
@@ -36,6 +36,8 @@ public class InnerClassProperties {
3636

3737
private Fourth fourth;
3838

39+
private final DeprecatedSimplePojo fifth = new DeprecatedSimplePojo();
40+
3941
public Foo getFirst() {
4042
return this.first;
4143
}
@@ -60,6 +62,11 @@ public void setFourth(Fourth fourth) {
6062
this.fourth = fourth;
6163
}
6264

65+
@NestedConfigurationProperty
66+
public DeprecatedSimplePojo getFifth() {
67+
return this.fifth;
68+
}
69+
6370
public static class Foo {
6471

6572
private String name;

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

Lines changed: 44 additions & 6 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,20 +25,58 @@
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
3977
* @since 1.2.0
4078
*/
41-
@Target({ ElementType.FIELD, ElementType.RECORD_COMPONENT })
79+
@Target({ ElementType.FIELD, ElementType.RECORD_COMPONENT, ElementType.METHOD })
4280
@Retention(RetentionPolicy.RUNTIME)
4381
@Documented
4482
@Nested

0 commit comments

Comments
 (0)