diff --git a/spring-core/src/main/java/org/springframework/util/PlaceholderParser.java b/spring-core/src/main/java/org/springframework/util/PlaceholderParser.java index 22aa50e602ef..ac157bb8bd5c 100644 --- a/spring-core/src/main/java/org/springframework/util/PlaceholderParser.java +++ b/spring-core/src/main/java/org/springframework/util/PlaceholderParser.java @@ -519,7 +519,7 @@ public SimplePlaceholderPart(String text,String key, @Nullable String fallback) @Override public String resolve(PartResolutionContext resolutionContext) { - String value = resolveRecursively(resolutionContext, this.key); + String value = resolveRecursively(resolutionContext); if (value != null) { return value; } @@ -528,6 +528,17 @@ else if (this.fallback != null) { } return resolutionContext.handleUnresolvablePlaceholder(this.key, text()); } + + @Nullable + private String resolveRecursively(PartResolutionContext resolutionContext) { + if (!this.text().equals(this.key)) { + String value = resolveRecursively(resolutionContext, this.text()); + if (value != null) { + return value; + } + } + return resolveRecursively(resolutionContext, this.key); + } } diff --git a/spring-core/src/test/java/org/springframework/util/PlaceholderParserTests.java b/spring-core/src/test/java/org/springframework/util/PlaceholderParserTests.java index 65be74d6ff73..3968d49f693e 100644 --- a/spring-core/src/test/java/org/springframework/util/PlaceholderParserTests.java +++ b/spring-core/src/test/java/org/springframework/util/PlaceholderParserTests.java @@ -24,6 +24,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InOrder; import org.springframework.util.PlaceholderParser.ParsedValue; import org.springframework.util.PlaceholderParser.TextPart; @@ -33,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -220,12 +222,37 @@ static Stream nestedPlaceholders() { ); } + @ParameterizedTest(name = "{0} -> {1}") + @MethodSource("exactMatchPlaceholders") + void placeholdersWithExactMatchAreConsidered(String text, String expected) { + Properties properties = new Properties(); + properties.setProperty("prefix://my-service", "example-service"); + properties.setProperty("px", "prefix"); + properties.setProperty("p1", "${prefix://my-service}"); + assertThat(this.parser.replacePlaceholders(text, properties::getProperty)).isEqualTo(expected); + } + + static Stream exactMatchPlaceholders() { + return Stream.of( + Arguments.of("${prefix://my-service}", "example-service"), + Arguments.of("${p1}", "example-service") + ); + } + + @Test + void parseWithKeyEqualsToText() { + PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "Steve"); + assertThat(this.parser.replacePlaceholders("${firstName}", resolver)) + .isEqualTo("Steve"); + verifyPlaceholderResolutions(resolver, "firstName"); + } + @Test void parseWithHardcodedFallback() { PlaceholderResolver resolver = mockPlaceholderResolver(); assertThat(this.parser.replacePlaceholders("${firstName:Steve}", resolver)) .isEqualTo("Steve"); - verifyPlaceholderResolutions(resolver, "firstName"); + verifyPlaceholderResolutions(resolver, "firstName:Steve", "firstName"); } @Test @@ -233,7 +260,7 @@ void parseWithNestedPlaceholderInKeyUsingFallback() { PlaceholderResolver resolver = mockPlaceholderResolver("firstName", "John"); assertThat(this.parser.replacePlaceholders("${first${invalid:Name}}", resolver)) .isEqualTo("John"); - verifyPlaceholderResolutions(resolver, "invalid", "firstName"); + verifyPlaceholderResolutions(resolver, "invalid:Name", "invalid", "firstName"); } @Test @@ -348,8 +375,9 @@ PlaceholderResolver mockPlaceholderResolver(String... pairs) { } void verifyPlaceholderResolutions(PlaceholderResolver mock, String... placeholders) { + InOrder ordered = inOrder(mock); for (String placeholder : placeholders) { - verify(mock).resolvePlaceholder(placeholder); + ordered.verify(mock).resolvePlaceholder(placeholder); } verifyNoMoreInteractions(mock); }