Skip to content

Commit 23b3446

Browse files
committed
Polish "Support @Name with JavaBean-based configuration properties"
See gh-39452
1 parent a305e2d commit 23b3446

File tree

11 files changed

+181
-71
lines changed

11 files changed

+181
-71
lines changed

spring-boot-project/spring-boot-docs/src/docs/antora/modules/reference/pages/features/external-config.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,8 @@ The preceding POJO defines the following properties:
713713
* `my.service.security.password`.
714714
* `my.service.security.roles`, with a collection of `String` that defaults to `USER`.
715715

716+
TIP: To use a reserved keyword in the name of a property, such as `my.service.import`, use the `@Name` annotation on the property's field.
717+
716718
NOTE: The properties that map to `@ConfigurationProperties` classes available in Spring Boot, which are configured through properties files, YAML files, environment variables, and other mechanisms, are public API but the accessors (getters/setters) of the class itself are not meant to be used directly.
717719

718720
[NOTE]

spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ private Stream<PropertyDescriptor> resolveConstructorBoundProperties(TypeElement
8585

8686
private PropertyDescriptor extracted(TypeElement declaringElement, TypeElementMembers members,
8787
VariableElement parameter) {
88-
String name = getParameterName(parameter);
88+
String name = getPropertyName(parameter);
8989
TypeMirror type = parameter.asType();
9090
ExecutableElement getter = members.getPublicGetter(name, type);
9191
ExecutableElement setter = members.getPublicSetter(name, type);
@@ -98,12 +98,16 @@ private PropertyDescriptor extracted(TypeElement declaringElement, TypeElementMe
9898
field);
9999
}
100100

101-
private String getParameterName(VariableElement parameter) {
101+
private String getPropertyName(VariableElement parameter) {
102+
return getPropertyName(parameter, parameter.getSimpleName().toString());
103+
}
104+
105+
private String getPropertyName(VariableElement parameter, String fallback) {
102106
AnnotationMirror nameAnnotation = this.environment.getNameAnnotation(parameter);
103107
if (nameAnnotation != null) {
104108
return this.environment.getAnnotationElementStringValue(nameAnnotation, "value");
105109
}
106-
return parameter.getSimpleName().toString();
110+
return fallback;
107111
}
108112

109113
private Stream<PropertyDescriptor> resolveJavaBeanProperties(TypeElement declaringElement,
@@ -114,16 +118,16 @@ private Stream<PropertyDescriptor> resolveJavaBeanProperties(TypeElement declari
114118
VariableElement field = members.getFields().get(name);
115119
ExecutableElement getter = findMatchingGetter(members, getters, field);
116120
TypeMirror propertyType = getter.getReturnType();
117-
register(candidates, new JavaBeanPropertyDescriptor(name, propertyType, declaringElement, getter,
118-
members.getPublicSetter(name, propertyType), field, factoryMethod));
121+
register(candidates, new JavaBeanPropertyDescriptor(getPropertyName(field, name), propertyType,
122+
declaringElement, getter, members.getPublicSetter(name, propertyType), field, factoryMethod));
119123
});
120124
// Then check for Lombok ones
121125
members.getFields().forEach((name, field) -> {
122126
TypeMirror propertyType = field.asType();
123127
ExecutableElement getter = members.getPublicGetter(name, propertyType);
124128
ExecutableElement setter = members.getPublicSetter(name, propertyType);
125-
register(candidates, new LombokPropertyDescriptor(name, propertyType, declaringElement, getter, setter,
126-
field, factoryMethod));
129+
register(candidates, new LombokPropertyDescriptor(getPropertyName(field, name), propertyType,
130+
declaringElement, getter, setter, field, factoryMethod));
127131
});
128132
return candidates.values().stream();
129133
}

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

Lines changed: 0 additions & 41 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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.configurationprocessor;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
22+
import org.springframework.boot.configurationprocessor.metadata.Metadata;
23+
import org.springframework.boot.configurationsample.immutable.ConstructorParameterNameAnnotationProperties;
24+
import org.springframework.boot.configurationsample.immutable.JavaBeanNameAnnotationProperties;
25+
import org.springframework.boot.configurationsample.immutable.RecordComponentNameAnnotationProperties;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
29+
/**
30+
* Metadata generation tests for using {@code @Name}.
31+
*
32+
* @author Phillip Webb
33+
*/
34+
class NameAnnotationPropertiesTests extends AbstractMetadataGenerationTests {
35+
36+
@Test
37+
void constructorParameterNameAnnotationProperties() {
38+
ConfigurationMetadata metadata = compile(ConstructorParameterNameAnnotationProperties.class);
39+
assertThat(metadata).has(Metadata.withProperty("named.import", String.class)
40+
.fromSource(ConstructorParameterNameAnnotationProperties.class));
41+
}
42+
43+
@Test
44+
void recordComponentNameAnnotationProperties() {
45+
ConfigurationMetadata metadata = compile(RecordComponentNameAnnotationProperties.class);
46+
assertThat(metadata).has(Metadata.withProperty("named.import", String.class)
47+
.fromSource(RecordComponentNameAnnotationProperties.class));
48+
}
49+
50+
@Test
51+
void javaBeanNameAnnotationProperties() {
52+
ConfigurationMetadata metadata = compile(JavaBeanNameAnnotationProperties.class);
53+
assertThat(metadata).has(
54+
Metadata.withProperty("named.import", String.class).fromSource(JavaBeanNameAnnotationProperties.class));
55+
}
56+
57+
}

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@
3131
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
3232
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester;
3333
import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor;
34+
import org.springframework.boot.configurationsample.immutable.ConstructorParameterNameAnnotationProperties;
3435
import org.springframework.boot.configurationsample.immutable.ImmutableClassConstructorBindingProperties;
3536
import org.springframework.boot.configurationsample.immutable.ImmutableDeducedConstructorBindingProperties;
3637
import org.springframework.boot.configurationsample.immutable.ImmutableMultiConstructorProperties;
37-
import org.springframework.boot.configurationsample.immutable.ImmutableNameAnnotationProperties;
3838
import org.springframework.boot.configurationsample.immutable.ImmutableSimpleProperties;
39+
import org.springframework.boot.configurationsample.immutable.JavaBeanNameAnnotationProperties;
40+
import org.springframework.boot.configurationsample.immutable.RecordComponentNameAnnotationProperties;
3941
import org.springframework.boot.configurationsample.lombok.LombokExplicitProperties;
4042
import org.springframework.boot.configurationsample.lombok.LombokSimpleDataProperties;
4143
import org.springframework.boot.configurationsample.lombok.LombokSimpleProperties;
@@ -155,8 +157,20 @@ void propertiesWithMultiConstructorNoDirective() {
155157
}
156158

157159
@Test
158-
void propertiesWithNameAnnotationParameter() {
159-
process(ImmutableNameAnnotationProperties.class,
160+
void contructorParameterPropertyWithNameAnnotationParameter() {
161+
process(ConstructorParameterNameAnnotationProperties.class,
162+
propertyNames((stream) -> assertThat(stream).containsExactly("import")));
163+
}
164+
165+
@Test
166+
void recordComponentPropertyWithNameAnnotationParameter() {
167+
process(RecordComponentNameAnnotationProperties.class,
168+
propertyNames((stream) -> assertThat(stream).containsExactly("import")));
169+
}
170+
171+
@Test
172+
void javaBeanPropertyWithNameAnnotationParameter() {
173+
process(JavaBeanNameAnnotationProperties.class,
160174
propertyNames((stream) -> assertThat(stream).containsExactly("import")));
161175
}
162176

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

Lines changed: 2 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-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.
@@ -28,7 +28,7 @@
2828
*
2929
* @author Phillip Webb
3030
*/
31-
@Target(ElementType.PARAMETER)
31+
@Target({ ElementType.PARAMETER, ElementType.FIELD })
3232
@Retention(RetentionPolicy.RUNTIME)
3333
@Documented
3434
public @interface Name {
Lines changed: 4 additions & 4 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.
@@ -20,16 +20,16 @@
2020
import org.springframework.boot.configurationsample.Name;
2121

2222
/**
23-
* Immutable properties making use of {@code @Name}.
23+
* Immutable class properties making use of {@code @Name}.
2424
*
2525
* @author Phillip Webb
2626
*/
2727
@ConfigurationProperties("named")
28-
public class ImmutableNameAnnotationProperties {
28+
public class ConstructorParameterNameAnnotationProperties {
2929

3030
private final String imports;
3131

32-
public ImmutableNameAnnotationProperties(@Name("import") String imports) {
32+
public ConstructorParameterNameAnnotationProperties(@Name("import") String imports) {
3333
this.imports = imports;
3434
}
3535

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.immutable;
18+
19+
import org.springframework.boot.configurationsample.ConfigurationProperties;
20+
import org.springframework.boot.configurationsample.Name;
21+
22+
/**
23+
* Java bean properties making use of {@code @Name}.
24+
*
25+
* @author Andy Wilkinson
26+
*/
27+
@ConfigurationProperties("named")
28+
public class JavaBeanNameAnnotationProperties {
29+
30+
@Name("import")
31+
private String imports;
32+
33+
public String getImports() {
34+
return this.imports;
35+
}
36+
37+
public void setImports(String imports) {
38+
this.imports = imports;
39+
}
40+
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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.immutable;
18+
19+
import org.springframework.boot.configurationsample.ConfigurationProperties;
20+
import org.springframework.boot.configurationsample.Name;
21+
22+
/**
23+
* Immutable record properties making use of {@code @Name}.
24+
*
25+
* @author Andy Wilkinson
26+
*/
27+
@ConfigurationProperties("named")
28+
public record RecordComponentNameAnnotationProperties(@Name("import") String imports) {
29+
30+
}

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

Lines changed: 4 additions & 1 deletion
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.
@@ -26,6 +26,9 @@
2626
* Annotation that can be used to specify the name when binding to a property. This
2727
* annotation may be required when binding to names that clash with reserved language
2828
* keywords.
29+
* <p>
30+
* When naming a JavaBean-based property, annotate the field. When naming a
31+
* constructor-bound property, annotate the constructor parameter or record component.
2932
*
3033
* @author Phillip Webb
3134
* @author Lasse Wulff

spring-boot-project/spring-boot/src/test/kotlin/org/springframework/boot/context/properties/KotlinConfigurationPropertiesTests.kt

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 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.
@@ -62,11 +62,11 @@ class KotlinConfigurationPropertiesTests {
6262
}
6363

6464
@Test
65-
fun `renamed property can be bound to late init attribute`() {
66-
this.context.register(EnableRenamedLateInitProperties::class.java)
65+
fun `renamed property can be bound`() {
66+
this.context.register(EnableRenamedProperties::class.java)
6767
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.context, "renamed.var=beta")
6868
this.context.refresh()
69-
assertThat(this.context.getBean(RenamedLateInitProperties::class.java).bar).isEqualTo("beta")
69+
assertThat(this.context.getBean(RenamedProperties::class.java).bar).isEqualTo("beta")
7070
}
7171

7272
@Test
@@ -90,15 +90,6 @@ class KotlinConfigurationPropertiesTests {
9090
@ConfigurationProperties(prefix = "foo")
9191
class BingProperties(@Suppress("UNUSED_PARAMETER") bar: String)
9292

93-
@ConfigurationProperties(prefix = "renamed")
94-
class RenamedLateInitProperties{
95-
@Name("var")
96-
lateinit var bar: String
97-
}
98-
99-
@EnableConfigurationProperties(RenamedLateInitProperties::class)
100-
class EnableRenamedLateInitProperties
101-
10293
@EnableConfigurationProperties
10394
class EnableConfigProperties
10495

@@ -136,4 +127,13 @@ class KotlinConfigurationPropertiesTests {
136127
var prop: String = ""
137128
)
138129

130+
@EnableConfigurationProperties(RenamedProperties::class)
131+
class EnableRenamedProperties
132+
133+
@ConfigurationProperties(prefix = "renamed")
134+
class RenamedProperties{
135+
@Name("var")
136+
var bar: String = ""
137+
}
138+
139139
}

0 commit comments

Comments
 (0)