diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolver.java index 9cb2d77b736..7b7d8f8ad1d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolver.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolver.java @@ -27,6 +27,7 @@ import org.springframework.core.env.PropertySourcesPropertyResolver; * underlying sources if the name is a value {@link ConfigurationPropertyName}. * * @author Phillip Webb + * @author Yanming Zhou */ class ConfigurationPropertySourcesPropertyResolver extends AbstractPropertyResolver { @@ -76,8 +77,16 @@ class ConfigurationPropertySourcesPropertyResolver extends AbstractPropertyResol if (value == null) { return null; } - if (resolveNestedPlaceholders && value instanceof String string) { - value = resolveNestedPlaceholders(string); + if (resolveNestedPlaceholders) { + if (value instanceof String string) { + value = resolveNestedPlaceholders(string); + } + else if (value instanceof CharSequence cs && !targetValueType.isInstance(value)) { + // keep value as it is if value is instance of targetValueType + // to avoid potential ConverterNotFoundException while converting String + // back to value's type + value = resolveNestedPlaceholders(cs.toString()); + } } return convertValueIfNecessary(value, targetValueType); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolverTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolverTests.java index 8032febc231..c52935240d6 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolverTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesPropertyResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link ConfigurationPropertySourcesPropertyResolver}. * * @author Phillip Webb + * @author Yanming Zhou */ class ConfigurationPropertySourcesPropertyResolverTests { @@ -113,6 +114,45 @@ class ConfigurationPropertySourcesPropertyResolverTests { assertThat(environment.getProperty("v2", Integer.class)).isOne(); } + @Test // gh-34195 + void resolveNestedPlaceholdersIfValueIsCharSequence() { + CharSequence cs = new CharSequence() { + + static final String underlying = "${v1}"; + + @Override + public int length() { + return underlying.length(); + } + + @Override + public char charAt(int index) { + return underlying.charAt(index); + } + + @Override + public CharSequence subSequence(int start, int end) { + return underlying.subSequence(start, end); + } + + @Override + public String toString() { + return underlying; + } + }; + ResolverEnvironment environment = new ResolverEnvironment(); + MockPropertySource propertySource = new MockPropertySource(); + propertySource.withProperty("v1", "1"); + propertySource.withProperty("v2", cs); + environment.getPropertySources().addFirst(propertySource); + assertThat(environment.getProperty("v2")).isEqualTo("1"); + assertThat(environment.getProperty("v2", Integer.class)).isOne(); + + // do not resolve to avoid ConverterNotFoundException + assertThat(environment.getProperty("v2", CharSequence.class)).isSameAs(cs); + assertThat(environment.getProperty("v2", cs.getClass())).isSameAs(cs); + } + private CountingMockPropertySource createMockPropertySource(StandardEnvironment environment, boolean attach) { CountingMockPropertySource propertySource = new CountingMockPropertySource(); propertySource.withProperty("spring", "boot");