Browse Source
Spring Framework 6.2 introduced support for an escape character for
property placeholders (by default '\'). However, as of Spring Framework
6.2.6, there was no way to either escape the escape character or disable
escape character support.
For example, given a `username` property configured with the value of
`Jane.Smith` and a `DOMAIN\${username}` configuration string, property
placeholder replacement used to result in `DOMAIN\Jane.Smith` prior to
6.2 but now results in `DOMAIN${username}`. Similarly, an attempt to
escape the escape character via `DOMAIN\\${username}` results in
`DOMAIN\${username}`.
In theory, one should be able to disable use of an escape character
altogether, and that is currently possible by invoking
setEscapeCharacter(null) on AbstractPropertyResolver and
PlaceholderConfigurerSupport (the superclass of
PropertySourcesPlaceholderConfigurer).
However, in reality, there are two hurdles.
- As of 6.2.6, an invocation of setEscapeCharacter(null) on a
PropertySourcesPlaceholderConfigurer applied to its internal
top-level PropertySourcesPropertyResolver but not to any nested
PropertySourcesPropertyResolver, which means that the `null` escape
character could not be effectively applied.
- Users may not have an easy way to explicitly set the escape character
to `null` for a PropertyResolver or
PropertySourcesPlaceholderConfigurer. For example, Spring Boot
auto-configures a PropertySourcesPlaceholderConfigurer with the
default escape character enabled.
This first issue above has recently been addressed by gh-34861.
This commit therefore addresses the second issue as follows.
- To allow developers to easily revert to the pre-6.2 behavior without
changes to code or configuration strings, this commit introduces a
`spring.placeholder.escapeCharacter.default` property for use with
SpringProperties which globally sets the default escape character that
is automatically configured in AbstractPropertyResolver and
PlaceholderConfigurerSupport.
- Setting the property to an empty string sets the default escape
character to `null`, effectively disabling the default support for
escape characters.
spring.placeholder.escapeCharacter.default =
- Setting the property to any other character sets the default escape
character to that specific character.
spring.placeholder.escapeCharacter.default = ~
- Setting the property to a string containing more than one character
results in an exception.
- Developers are still able to configure an explicit escape character
in AbstractPropertyResolver and PlaceholderConfigurerSupport if they
choose to do so.
- Third-party components that wish to rely on the same feature can
invoke AbstractPropertyResolver.getDefaultEscapeCharacter() to obtain
the globally configured default escape character.
See gh-9628
See gh-34315
See gh-34861
Closes gh-34865
pull/35405/head
8 changed files with 373 additions and 17 deletions
@ -0,0 +1,120 @@
@@ -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); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue