diff --git a/framework-docs/modules/ROOT/pages/appendix.adoc b/framework-docs/modules/ROOT/pages/appendix.adoc index 9a8c9048c05..6e7e5cecd0e 100644 --- a/framework-docs/modules/ROOT/pages/appendix.adoc +++ b/framework-docs/modules/ROOT/pages/appendix.adoc @@ -103,6 +103,14 @@ for details. {spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`] for details. +| `spring.placeholder.escapeCharacter.default` +| The default escape character for property placeholder support. If not set, `'\'` will +be used. Can be set to a custom escape character or an empty string to disable support +for an escape character. The default escape character be explicitly overridden in +`PropertySourcesPlaceholderConfigurer` and subclasses of `AbstractPropertyResolver`. See +{spring-framework-api}++/core/env/AbstractPropertyResolver.html#DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME++[`AbstractPropertyResolver`] +for details. + | `spring.test.aot.processing.failOnError` | A boolean flag that controls whether errors encountered during AOT processing in the _Spring TestContext Framework_ should result in an exception that fails the overall process. diff --git a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc index 72e70005d0c..d91aaafb19b 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc @@ -101,8 +101,11 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig Using the above configuration ensures Spring initialization failure if any `${}` placeholder could not be resolved. It is also possible to use methods like -`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or -`setEscapeCharacter` to customize placeholders. +`setPlaceholderPrefix()`, `setPlaceholderSuffix()`, `setValueSeparator()`, or +`setEscapeCharacter()` to customize the placeholder syntax. In addition, the default +escape character can be changed or disabled globally by setting the +`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via +the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism). NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that will get properties from `application.properties` and `application.yml` files. diff --git a/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc b/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc index 571eba4d686..56641fd847e 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc @@ -314,7 +314,7 @@ Thus, marking it for lazy initialization will be ignored, and the [[beans-factory-placeholderconfigurer]] -=== Example: The Class Name Substitution `PropertySourcesPlaceholderConfigurer` +=== Example: Property Placeholder Substitution with `PropertySourcesPlaceholderConfigurer` You can use the `PropertySourcesPlaceholderConfigurer` to externalize property values from a bean definition in a separate file by using the standard Java `Properties` format. @@ -341,7 +341,7 @@ with placeholder values is defined: The example shows properties configured from an external `Properties` file. At runtime, a `PropertySourcesPlaceholderConfigurer` is applied to the metadata that replaces some -properties of the DataSource. The values to replace are specified as placeholders of the +properties of the `DataSource`. The values to replace are specified as placeholders of the form pass:q[`${property-name}`], which follows the Ant, log4j, and JSP EL style. The actual values come from another file in the standard Java `Properties` format: @@ -355,10 +355,13 @@ jdbc.password=root ---- Therefore, the `${jdbc.username}` string is replaced at runtime with the value, 'sa', and -the same applies for other placeholder values that match keys in the properties file. -The `PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and -attributes of a bean definition. Furthermore, you can customize the placeholder prefix, suffix, -default value separator, and escape character. +the same applies for other placeholder values that match keys in the properties file. The +`PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and +attributes of a bean definition. Furthermore, you can customize the placeholder prefix, +suffix, default value separator, and escape character. In addition, the default escape +character can be changed or disabled globally by setting the +`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via +the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism). With the `context` namespace, you can configure property placeholders with a dedicated configuration element. You can provide one or more locations as a diff --git a/framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc b/framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc index a4425c6849f..2a8670de074 100644 --- a/framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc +++ b/framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc @@ -190,7 +190,7 @@ NOTE: If you use Spring Boot, you should probably use instead of `@Value` annotations. As an alternative, you can customize the property placeholder prefix by declaring the -following configuration bean: +following `PropertySourcesPlaceholderConfigurer` bean: [source,kotlin,indent=0] ---- @@ -200,8 +200,10 @@ following configuration bean: } ---- -You can customize existing code (such as Spring Boot actuators or `@LocalServerPort`) -that uses the `${...}` syntax, with configuration beans, as the following example shows: +You can support components (such as Spring Boot actuators or `@LocalServerPort`) that use +the standard `${...}` syntax alongside components that use the custom `%{...}` syntax by +declaring multiple `PropertySourcesPlaceholderConfigurer` beans, as the following example +shows: [source,kotlin,indent=0] ---- @@ -215,6 +217,9 @@ that uses the `${...}` syntax, with configuration beans, as the following exampl fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer() ---- +In addition, the default escape character can be changed or disabled globally by setting +the `spring.placeholder.escapeCharacter.default` property via a JVM system property (or +via the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism). [[checked-exceptions]] diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java index 6d1fd92d97b..49f21f9044b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; +import org.springframework.core.env.AbstractPropertyResolver; import org.springframework.lang.Nullable; import org.springframework.util.StringValueResolver; import org.springframework.util.SystemPropertyUtils; @@ -85,6 +86,7 @@ import org.springframework.util.SystemPropertyUtils; * * @author Chris Beams * @author Juergen Hoeller + * @author Sam Brannen * @since 3.1 * @see PropertyPlaceholderConfigurer * @see org.springframework.context.support.PropertySourcesPlaceholderConfigurer @@ -101,7 +103,11 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi /** Default value separator: {@value}. */ public static final String DEFAULT_VALUE_SEPARATOR = SystemPropertyUtils.VALUE_SEPARATOR; - /** Default escape character: {@code '\'}. */ + /** + * Default escape character: {@code '\'}. + * @since 6.2 + * @see AbstractPropertyResolver#getDefaultEscapeCharacter() + */ public static final Character DEFAULT_ESCAPE_CHARACTER = SystemPropertyUtils.ESCAPE_CHARACTER; @@ -115,9 +121,11 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi @Nullable protected String valueSeparator = DEFAULT_VALUE_SEPARATOR; - /** Defaults to {@link #DEFAULT_ESCAPE_CHARACTER}. */ + /** + * The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}. + */ @Nullable - protected Character escapeCharacter = DEFAULT_ESCAPE_CHARACTER; + protected Character escapeCharacter = AbstractPropertyResolver.getDefaultEscapeCharacter(); protected boolean trimValues = false; @@ -164,6 +172,7 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi * {@linkplain #setPlaceholderPrefix(String) placeholder prefix} and the * {@linkplain #setValueSeparator(String) value separator}, or {@code null} * if no escaping should take place. + *
The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}.
* @since 6.2
*/
public void setEscapeCharacter(@Nullable Character escapeCharacter) {
diff --git a/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java b/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java
index 293ae6925c1..d78bb08406a 100644
--- a/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java
+++ b/spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java
@@ -16,12 +16,15 @@
package org.springframework.context.support;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -37,7 +40,9 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
+import org.springframework.core.SpringProperties;
import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.core.env.AbstractPropertyResolver;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
@@ -47,12 +52,15 @@ import org.springframework.core.io.Resource;
import org.springframework.core.testfixture.env.MockPropertySource;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.util.PlaceholderResolutionException;
+import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
+import static org.springframework.core.env.AbstractPropertyResolver.DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME;
/**
* Tests for {@link PropertySourcesPlaceholderConfigurer}.
@@ -667,6 +675,108 @@ class PropertySourcesPlaceholderConfigurerTests {
}
+ /**
+ * Tests that globally set the default escape character (or disable it) and
+ * rely on nested placeholder resolution.
+ */
+ @Nested
+ class GlobalDefaultEscapeCharacterTests {
+
+ private static final Field defaultEscapeCharacterField =
+ ReflectionUtils.findField(AbstractPropertyResolver.class, "defaultEscapeCharacter");
+
+ static {
+ ReflectionUtils.makeAccessible(defaultEscapeCharacterField);
+ }
+
+
+ @BeforeEach
+ void resetStateBeforeEachTest() {
+ resetState();
+ }
+
+ @AfterAll
+ static void resetState() {
+ ReflectionUtils.setField(defaultEscapeCharacterField, null, Character.MIN_VALUE);
+ setSpringProperty(null);
+ }
+
+
+ @Test // gh-34865
+ void defaultEscapeCharacterSetToXyz() {
+ setSpringProperty("XYZ");
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(PropertySourcesPlaceholderConfigurer::new)
+ .withMessage("Value [XYZ] for property [%s] must be a single character or an empty string",
+ DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME);
+ }
+
+ @Test // gh-34865
+ void defaultEscapeCharacterDisabled() {
+ setSpringProperty("");
+
+ MockEnvironment env = new MockEnvironment()
+ .withProperty("user.home", "admin")
+ .withProperty("my.property", "\\DOMAIN\\${user.home}");
+
+ DefaultListableBeanFactory bf = createBeanFactory();
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ ppc.setEnvironment(env);
+ ppc.postProcessBeanFactory(bf);
+
+ assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("\\DOMAIN\\admin");
+ }
+
+ @Test // gh-34865
+ void defaultEscapeCharacterSetToBackslash() {
+ setSpringProperty("\\");
+
+ MockEnvironment env = new MockEnvironment()
+ .withProperty("user.home", "admin")
+ .withProperty("my.property", "\\DOMAIN\\${user.home}");
+
+ DefaultListableBeanFactory bf = createBeanFactory();
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ ppc.setEnvironment(env);
+ ppc.postProcessBeanFactory(bf);
+
+ // \DOMAIN\${user.home} resolves to \DOMAIN${user.home} instead of \DOMAIN\admin
+ assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("\\DOMAIN${user.home}");
+ }
+
+ @Test // gh-34865
+ void defaultEscapeCharacterSetToTilde() {
+ setSpringProperty("~");
+
+ MockEnvironment env = new MockEnvironment()
+ .withProperty("user.home", "admin\\~${nested}")
+ .withProperty("my.property", "DOMAIN\\${user.home}\\~${enigma}");
+
+ DefaultListableBeanFactory bf = createBeanFactory();
+ PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+ ppc.setEnvironment(env);
+ ppc.postProcessBeanFactory(bf);
+
+ assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("DOMAIN\\admin\\${nested}\\${enigma}");
+ }
+
+ private static void setSpringProperty(String value) {
+ SpringProperties.setProperty(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME, value);
+ }
+
+ private static DefaultListableBeanFactory createBeanFactory() {
+ BeanDefinition beanDefinition = genericBeanDefinition(TestBean.class)
+ .addPropertyValue("name", "${my.property}")
+ .getBeanDefinition();
+ DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
+ bf.registerBeanDefinition("testBean",beanDefinition);
+ return bf;
+ }
+
+ }
+
+
private static class OptionalTestBean {
private Optional To configure a custom escape character, supply a string containing a
+ * single character (other than {@link Character#MIN_VALUE}). For example,
+ * supplying the following JVM system property via the command line sets the
+ * default escape character to {@code '@'}.
+ * To disable escape character support, set the value to an empty string
+ * — for example, by supplying the following JVM system property via
+ * the command line.
+ * If the property is not set, {@code '\'} will be used as the default
+ * escape character.
+ * May alternatively be configured via a
+ * {@link org.springframework.core.SpringProperties spring.properties} file
+ * in the root of the classpath.
+ * @since 6.2.7
+ * @see #getDefaultEscapeCharacter()
+ */
+ public static final String DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME =
+ "spring.placeholder.escapeCharacter.default";
+
+ /**
+ * Since {@code null} is a valid value for {@link #defaultEscapeCharacter},
+ * this constant provides a way to represent an undefined (or not yet set)
+ * value. Consequently, {@link #getDefaultEscapeCharacter()} prevents the use
+ * of {@link Character#MIN_VALUE} as the actual escape character.
+ * @since 6.2.7
+ */
+ static final Character UNDEFINED_ESCAPE_CHARACTER = Character.MIN_VALUE;
+
+
+ /**
+ * Cached value for the default escape character.
+ * @since 6.2.7
+ */
+ @Nullable
+ static volatile Character defaultEscapeCharacter = UNDEFINED_ESCAPE_CHARACTER;
+
+
protected final Log logger = LogFactory.getLog(getClass());
@Nullable
@@ -62,7 +105,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
@Nullable
- private Character escapeCharacter = SystemPropertyUtils.ESCAPE_CHARACTER;
+ private Character escapeCharacter = getDefaultEscapeCharacter();
private final Set The default is {@code '\'}.
+ * The default is determined by {@link #getDefaultEscapeCharacter()}.
* @since 6.2
- * @see SystemPropertyUtils#ESCAPE_CHARACTER
*/
@Override
public void setEscapeCharacter(@Nullable Character escapeCharacter) {
@@ -287,4 +329,60 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
@Nullable
protected abstract String getPropertyAsRawString(String key);
+
+ /**
+ * Get the default {@linkplain #setEscapeCharacter(Character) escape character}
+ * to use when parsing strings for property placeholder resolution.
+ * This method attempts to retrieve the default escape character configured
+ * via the {@value #DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME} JVM system
+ * property or Spring property.
+ * Falls back to {@code '\'} if the property has not been set.
+ * @return the configured default escape character, {@code null} if escape character
+ * support has been disabled, or {@code '\'} if the property has not been set
+ * @throws IllegalArgumentException if the property is configured with an
+ * invalid value, such as {@link Character#MIN_VALUE} or a string containing
+ * more than one character
+ * @since 6.2.7
+ * @see #DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME
+ * @see SystemPropertyUtils#ESCAPE_CHARACTER
+ * @see SpringProperties
+ */
+ @Nullable
+ public static Character getDefaultEscapeCharacter() throws IllegalArgumentException {
+ Character escapeCharacter = defaultEscapeCharacter;
+ if (UNDEFINED_ESCAPE_CHARACTER.equals(escapeCharacter)) {
+ String value = SpringProperties.getProperty(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME);
+ if (value != null) {
+ if (value.isEmpty()) {
+ // Disable escape character support by default.
+ escapeCharacter = null;
+ }
+ else if (value.length() == 1) {
+ try {
+ // Use custom default escape character.
+ escapeCharacter = value.charAt(0);
+ }
+ catch (Exception ex) {
+ throw new IllegalArgumentException("Failed to process value [%s] for property [%s]: %s"
+ .formatted(value, DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME, ex.getMessage()), ex);
+ }
+ Assert.isTrue(!escapeCharacter.equals(Character.MIN_VALUE),
+ () -> "Value for property [%s] must not be Character.MIN_VALUE"
+ .formatted(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME));
+ }
+ else {
+ throw new IllegalArgumentException(
+ "Value [%s] for property [%s] must be a single character or an empty string"
+ .formatted(value, DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME));
+ }
+ }
+ else {
+ // Use standard default value for the escape character.
+ escapeCharacter = SystemPropertyUtils.ESCAPE_CHARACTER;
+ }
+ defaultEscapeCharacter = escapeCharacter;
+ }
+ return escapeCharacter;
+ }
+
}
diff --git a/spring-core/src/test/java/org/springframework/core/env/AbstractPropertyResolverTests.java b/spring-core/src/test/java/org/springframework/core/env/AbstractPropertyResolverTests.java
new file mode 100644
index 00000000000..ca536d83078
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/core/env/AbstractPropertyResolverTests.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2002-2025 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.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core.env;
+
+import java.util.stream.IntStream;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.core.SpringProperties;
+import org.springframework.lang.Nullable;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.springframework.core.env.AbstractPropertyResolver.DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME;
+import static org.springframework.core.env.AbstractPropertyResolver.UNDEFINED_ESCAPE_CHARACTER;
+
+/**
+ * Unit tests for {@link AbstractPropertyResolver}.
+ *
+ * @author Sam Brannen
+ * @since 6.2.7
+ */
+class AbstractPropertyResolverTests {
+
+ @BeforeEach
+ void resetStateBeforeEachTest() {
+ resetState();
+ }
+
+ @AfterAll
+ static void resetState() {
+ AbstractPropertyResolver.defaultEscapeCharacter = UNDEFINED_ESCAPE_CHARACTER;
+ setSpringProperty(null);
+ }
+
+
+ @Test
+ void getDefaultEscapeCharacterWithSpringPropertySetToCharacterMinValue() {
+ setSpringProperty("" + Character.MIN_VALUE);
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(AbstractPropertyResolver::getDefaultEscapeCharacter)
+ .withMessage("Value for property [%s] must not be Character.MIN_VALUE",
+ DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME);
+
+ assertThat(AbstractPropertyResolver.defaultEscapeCharacter).isEqualTo(UNDEFINED_ESCAPE_CHARACTER);
+ }
+
+ @Test
+ void getDefaultEscapeCharacterWithSpringPropertySetToXyz() {
+ setSpringProperty("XYZ");
+
+ assertThatIllegalArgumentException()
+ .isThrownBy(AbstractPropertyResolver::getDefaultEscapeCharacter)
+ .withMessage("Value [XYZ] for property [%s] must be a single character or an empty string",
+ DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME);
+
+ assertThat(AbstractPropertyResolver.defaultEscapeCharacter).isEqualTo(UNDEFINED_ESCAPE_CHARACTER);
+ }
+
+ @Test
+ void getDefaultEscapeCharacterWithSpringPropertySetToEmptyString() {
+ setSpringProperty("");
+ assertEscapeCharacter(null);
+ }
+
+ @Test
+ void getDefaultEscapeCharacterWithoutSpringPropertySet() {
+ assertEscapeCharacter('\\');
+ }
+
+ @Test
+ void getDefaultEscapeCharacterWithSpringPropertySetToBackslash() {
+ setSpringProperty("\\");
+ assertEscapeCharacter('\\');
+ }
+
+ @Test
+ void getDefaultEscapeCharacterWithSpringPropertySetToTilde() {
+ setSpringProperty("~");
+ assertEscapeCharacter('~');
+ }
+
+ @Test
+ void getDefaultEscapeCharacterFromMultipleThreads() {
+ setSpringProperty("~");
+
+ IntStream.range(1, 32).parallel().forEach(__ ->
+ assertThat(AbstractPropertyResolver.getDefaultEscapeCharacter()).isEqualTo('~'));
+
+ assertThat(AbstractPropertyResolver.defaultEscapeCharacter).isEqualTo('~');
+ }
+
+
+ private static void setSpringProperty(String value) {
+ SpringProperties.setProperty(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME, value);
+ }
+
+ private static void assertEscapeCharacter(@Nullable Character expected) {
+ assertThat(AbstractPropertyResolver.getDefaultEscapeCharacter()).isEqualTo(expected);
+ assertThat(AbstractPropertyResolver.defaultEscapeCharacter).isEqualTo(expected);
+ }
+
+}
-Dspring.placeholder.escapeCharacter.default=@
+ * -Dspring.placeholder.escapeCharacter.default=
+ *