Skip to content

Commit edf4c83

Browse files
Stefan Zwanenburgphilwebb
authored andcommitted
Support nested @PropertyMapping annotations
Update `AnnotationsPropertySource` so that nested annotations are supported. Prior to this commit, annotations annotated with `@PropertyMapping` that contained nested annotation attributes would result in instances of `TypeMappedAnnotation` being used as properties. This usually led to errors due to not being able to convert those to Strings. This commit makes it so that nested annotations are recursively mapped to properties. This should allow for more complex configuration to be mapped from annotations. See gh-23146
1 parent f60f3cb commit edf4c83

File tree

2 files changed

+79
-3
lines changed

2 files changed

+79
-3
lines changed

spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/properties/AnnotationsPropertySource.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ private void collectProperties(String prefix, SkipPropertyMapping defaultSkip, M
9090
}
9191
}
9292
String name = getName(prefix, attributeMapping, attribute);
93-
putProperties(name, value.get(), properties);
93+
putProperties(name, skip, value.get(), properties);
9494
}
9595

9696
private String getName(String prefix, MergedAnnotation<?> attributeMapping, Method attribute) {
@@ -118,11 +118,17 @@ private String dotAppend(String prefix, String postfix) {
118118
return postfix;
119119
}
120120

121-
private void putProperties(String name, Object value, Map<String, Object> properties) {
121+
private void putProperties(String name, SkipPropertyMapping defaultSkip, Object value,
122+
Map<String, Object> properties) {
122123
if (ObjectUtils.isArray(value)) {
123124
Object[] array = ObjectUtils.toObjectArray(value);
124125
for (int i = 0; i < array.length; i++) {
125-
properties.put(name + "[" + i + "]", array[i]);
126+
putProperties(name + "[" + i + "]", defaultSkip, array[i], properties);
127+
}
128+
}
129+
else if (value instanceof MergedAnnotation<?>) {
130+
for (Method attribute : ((MergedAnnotation<?>) value).getType().getDeclaredMethods()) {
131+
collectProperties(name, defaultSkip, (MergedAnnotation<?>) value, attribute, properties);
126132
}
127133
}
128134
else {

spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/properties/AnnotationsPropertySourceTests.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121

2222
import org.junit.jupiter.api.Test;
2323

24+
import org.springframework.boot.test.autoconfigure.properties.AnnotationsPropertySourceTests.DeeplyNestedAnnotations.Level1;
25+
import org.springframework.boot.test.autoconfigure.properties.AnnotationsPropertySourceTests.DeeplyNestedAnnotations.Level2;
26+
import org.springframework.boot.test.autoconfigure.properties.AnnotationsPropertySourceTests.NestedAnnotations.Entry;
2427
import org.springframework.core.annotation.AliasFor;
2528

2629
import static org.assertj.core.api.Assertions.assertThat;
@@ -172,6 +175,26 @@ void enumValueNotMapped() {
172175
assertThat(source.containsProperty("testenum.value")).isFalse();
173176
}
174177

178+
@Test
179+
void nestedAnnotationsMapped() {
180+
AnnotationsPropertySource source = new AnnotationsPropertySource(PropertyMappedWithNestedAnnotations.class);
181+
assertThat(source.getProperty("testnested")).isNull();
182+
assertThat(source.getProperty("testnested.entries[0]")).isNull();
183+
assertThat(source.getProperty("testnested.entries[0].value")).isEqualTo("one");
184+
assertThat(source.getProperty("testnested.entries[1]")).isNull();
185+
assertThat(source.getProperty("testnested.entries[1].value")).isEqualTo("two");
186+
}
187+
188+
@Test
189+
void deeplyNestedAnnotationsMapped() {
190+
AnnotationsPropertySource source = new AnnotationsPropertySource(
191+
PropertyMappedWithDeeplyNestedAnnotations.class);
192+
assertThat(source.getProperty("testdeeplynested")).isNull();
193+
assertThat(source.getProperty("testdeeplynested.level1")).isNull();
194+
assertThat(source.getProperty("testdeeplynested.level1.level2")).isNull();
195+
assertThat(source.getProperty("testdeeplynested.level1.level2.value")).isEqualTo("level2");
196+
}
197+
175198
static class NoAnnotation {
176199

177200
}
@@ -396,4 +419,51 @@ enum EnumItem {
396419

397420
}
398421

422+
@Retention(RetentionPolicy.RUNTIME)
423+
@PropertyMapping("testnested")
424+
@interface NestedAnnotations {
425+
426+
Entry[] entries();
427+
428+
@Retention(RetentionPolicy.RUNTIME)
429+
@interface Entry {
430+
431+
String value();
432+
433+
}
434+
435+
}
436+
437+
@NestedAnnotations(entries = { @Entry("one"), @Entry("two") })
438+
static class PropertyMappedWithNestedAnnotations {
439+
440+
}
441+
442+
@Retention(RetentionPolicy.RUNTIME)
443+
@PropertyMapping("testdeeplynested")
444+
@interface DeeplyNestedAnnotations {
445+
446+
Level1 level1();
447+
448+
@Retention(RetentionPolicy.RUNTIME)
449+
@interface Level1 {
450+
451+
Level2 level2();
452+
453+
}
454+
455+
@Retention(RetentionPolicy.RUNTIME)
456+
@interface Level2 {
457+
458+
String value();
459+
460+
}
461+
462+
}
463+
464+
@DeeplyNestedAnnotations(level1 = @Level1(level2 = @Level2("level2")))
465+
static class PropertyMappedWithDeeplyNestedAnnotations {
466+
467+
}
468+
399469
}

0 commit comments

Comments
 (0)