Skip to content

Commit 680094d

Browse files
committed
Merge branch '6.2.x'
2 parents 233eb7f + 03ae97b commit 680094d

File tree

8 files changed

+371
-17
lines changed

8 files changed

+371
-17
lines changed

framework-docs/modules/ROOT/pages/appendix.adoc

+8
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ for details.
103103
{spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
104104
for details.
105105

106+
| `spring.placeholder.escapeCharacter.default`
107+
| The default escape character for property placeholder support. If not set, `'\'` will
108+
be used. Can be set to a custom escape character or an empty string to disable support
109+
for an escape character. The default escape character be explicitly overridden in
110+
`PropertySourcesPlaceholderConfigurer` and subclasses of `AbstractPropertyResolver`. See
111+
{spring-framework-api}++/core/env/AbstractPropertyResolver.html#DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME++[`AbstractPropertyResolver`]
112+
for details.
113+
106114
| `spring.test.aot.processing.failOnError`
107115
| A boolean flag that controls whether errors encountered during AOT processing in the
108116
_Spring TestContext Framework_ should result in an exception that fails the overall process.

framework-docs/modules/ROOT/pages/core/beans/annotation-config/value-annotations.adoc

+5-2
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,11 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig
101101

102102
Using the above configuration ensures Spring initialization failure if any `${}`
103103
placeholder could not be resolved. It is also possible to use methods like
104-
`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or
105-
`setEscapeCharacter` to customize placeholders.
104+
`setPlaceholderPrefix()`, `setPlaceholderSuffix()`, `setValueSeparator()`, or
105+
`setEscapeCharacter()` to customize the placeholder syntax. In addition, the default
106+
escape character can be changed or disabled globally by setting the
107+
`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via
108+
the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
106109

107110
NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that
108111
will get properties from `application.properties` and `application.yml` files.

framework-docs/modules/ROOT/pages/core/beans/factory-extension.adoc

+9-6
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ Thus, marking it for lazy initialization will be ignored, and the
314314

315315

316316
[[beans-factory-placeholderconfigurer]]
317-
=== Example: The Class Name Substitution `PropertySourcesPlaceholderConfigurer`
317+
=== Example: Property Placeholder Substitution with `PropertySourcesPlaceholderConfigurer`
318318

319319
You can use the `PropertySourcesPlaceholderConfigurer` to externalize property values
320320
from a bean definition in a separate file by using the standard Java `Properties` format.
@@ -341,7 +341,7 @@ with placeholder values is defined:
341341

342342
The example shows properties configured from an external `Properties` file. At runtime,
343343
a `PropertySourcesPlaceholderConfigurer` is applied to the metadata that replaces some
344-
properties of the DataSource. The values to replace are specified as placeholders of the
344+
properties of the `DataSource`. The values to replace are specified as placeholders of the
345345
form pass:q[`${property-name}`], which follows the Ant, log4j, and JSP EL style.
346346

347347
The actual values come from another file in the standard Java `Properties` format:
@@ -355,10 +355,13 @@ jdbc.password=root
355355
----
356356

357357
Therefore, the `${jdbc.username}` string is replaced at runtime with the value, 'sa', and
358-
the same applies for other placeholder values that match keys in the properties file.
359-
The `PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
360-
attributes of a bean definition. Furthermore, you can customize the placeholder prefix, suffix,
361-
default value separator, and escape character.
358+
the same applies for other placeholder values that match keys in the properties file. The
359+
`PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
360+
attributes of a bean definition. Furthermore, you can customize the placeholder prefix,
361+
suffix, default value separator, and escape character. In addition, the default escape
362+
character can be changed or disabled globally by setting the
363+
`spring.placeholder.escapeCharacter.default` property via a JVM system property (or via
364+
the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
362365

363366
With the `context` namespace, you can configure property placeholders
364367
with a dedicated configuration element. You can provide one or more locations as a

framework-docs/modules/ROOT/pages/languages/kotlin/spring-projects-in.adoc

+8-3
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ NOTE: If you use Spring Boot, you should probably use
190190
instead of `@Value` annotations.
191191

192192
As an alternative, you can customize the property placeholder prefix by declaring the
193-
following configuration bean:
193+
following `PropertySourcesPlaceholderConfigurer` bean:
194194

195195
[source,kotlin,indent=0]
196196
----
@@ -200,8 +200,10 @@ following configuration bean:
200200
}
201201
----
202202

203-
You can customize existing code (such as Spring Boot actuators or `@LocalServerPort`)
204-
that uses the `${...}` syntax, with configuration beans, as the following example shows:
203+
You can support components (such as Spring Boot actuators or `@LocalServerPort`) that use
204+
the standard `${...}` syntax alongside components that use the custom `%{...}` syntax by
205+
declaring multiple `PropertySourcesPlaceholderConfigurer` beans, as the following example
206+
shows:
205207

206208
[source,kotlin,indent=0]
207209
----
@@ -215,6 +217,9 @@ that uses the `${...}` syntax, with configuration beans, as the following exampl
215217
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
216218
----
217219

220+
In addition, the default escape character can be changed or disabled globally by setting
221+
the `spring.placeholder.escapeCharacter.default` property via a JVM system property (or
222+
via the xref:appendix.adoc#appendix-spring-properties[`SpringProperties`] mechanism).
218223

219224

220225
[[checked-exceptions]]

spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java

+12-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.beans.factory.BeanFactory;
2323
import org.springframework.beans.factory.BeanFactoryAware;
2424
import org.springframework.beans.factory.BeanNameAware;
25+
import org.springframework.core.env.AbstractPropertyResolver;
2526
import org.springframework.util.StringValueResolver;
2627
import org.springframework.util.SystemPropertyUtils;
2728

@@ -86,6 +87,7 @@
8687
*
8788
* @author Chris Beams
8889
* @author Juergen Hoeller
90+
* @author Sam Brannen
8991
* @since 3.1
9092
* @see PropertyPlaceholderConfigurer
9193
* @see org.springframework.context.support.PropertySourcesPlaceholderConfigurer
@@ -102,7 +104,11 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
102104
/** Default value separator: {@value}. */
103105
public static final String DEFAULT_VALUE_SEPARATOR = SystemPropertyUtils.VALUE_SEPARATOR;
104106

105-
/** Default escape character: {@code '\'}. */
107+
/**
108+
* Default escape character: {@code '\'}.
109+
* @since 6.2
110+
* @see AbstractPropertyResolver#getDefaultEscapeCharacter()
111+
*/
106112
public static final Character DEFAULT_ESCAPE_CHARACTER = SystemPropertyUtils.ESCAPE_CHARACTER;
107113

108114

@@ -115,8 +121,10 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
115121
/** Defaults to {@value #DEFAULT_VALUE_SEPARATOR}. */
116122
protected @Nullable String valueSeparator = DEFAULT_VALUE_SEPARATOR;
117123

118-
/** Defaults to {@link #DEFAULT_ESCAPE_CHARACTER}. */
119-
protected @Nullable Character escapeCharacter = DEFAULT_ESCAPE_CHARACTER;
124+
/**
125+
* The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}.
126+
*/
127+
protected @Nullable Character escapeCharacter = AbstractPropertyResolver.getDefaultEscapeCharacter();
120128

121129
protected boolean trimValues = false;
122130

@@ -160,6 +168,7 @@ public void setValueSeparator(@Nullable String valueSeparator) {
160168
* {@linkplain #setPlaceholderPrefix(String) placeholder prefix} and the
161169
* {@linkplain #setValueSeparator(String) value separator}, or {@code null}
162170
* if no escaping should take place.
171+
* <p>The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}.
163172
* @since 6.2
164173
*/
165174
public void setEscapeCharacter(@Nullable Character escapeCharacter) {

spring-context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java

+110
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@
1616

1717
package org.springframework.context.support;
1818

19+
import java.lang.reflect.Field;
1920
import java.util.ArrayList;
2021
import java.util.Collections;
2122
import java.util.List;
2223
import java.util.Optional;
2324
import java.util.Properties;
2425

26+
import org.junit.jupiter.api.AfterAll;
27+
import org.junit.jupiter.api.BeforeEach;
2528
import org.junit.jupiter.api.Nested;
2629
import org.junit.jupiter.api.Test;
2730
import org.junit.jupiter.params.ParameterizedTest;
@@ -37,7 +40,9 @@
3740
import org.springframework.context.annotation.Bean;
3841
import org.springframework.context.annotation.Configuration;
3942
import org.springframework.context.annotation.Scope;
43+
import org.springframework.core.SpringProperties;
4044
import org.springframework.core.convert.support.DefaultConversionService;
45+
import org.springframework.core.env.AbstractPropertyResolver;
4146
import org.springframework.core.env.EnumerablePropertySource;
4247
import org.springframework.core.env.MutablePropertySources;
4348
import org.springframework.core.env.PropertySource;
@@ -47,12 +52,15 @@
4752
import org.springframework.core.testfixture.env.MockPropertySource;
4853
import org.springframework.mock.env.MockEnvironment;
4954
import org.springframework.util.PlaceholderResolutionException;
55+
import org.springframework.util.ReflectionUtils;
5056

5157
import static org.assertj.core.api.Assertions.assertThat;
5258
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
59+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
5360
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
5461
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
5562
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
63+
import static org.springframework.core.env.AbstractPropertyResolver.DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME;
5664

5765
/**
5866
* Tests for {@link PropertySourcesPlaceholderConfigurer}.
@@ -667,6 +675,108 @@ private static DefaultListableBeanFactory createBeanFactory() {
667675
}
668676

669677

678+
/**
679+
* Tests that globally set the default escape character (or disable it) and
680+
* rely on nested placeholder resolution.
681+
*/
682+
@Nested
683+
class GlobalDefaultEscapeCharacterTests {
684+
685+
private static final Field defaultEscapeCharacterField =
686+
ReflectionUtils.findField(AbstractPropertyResolver.class, "defaultEscapeCharacter");
687+
688+
static {
689+
ReflectionUtils.makeAccessible(defaultEscapeCharacterField);
690+
}
691+
692+
693+
@BeforeEach
694+
void resetStateBeforeEachTest() {
695+
resetState();
696+
}
697+
698+
@AfterAll
699+
static void resetState() {
700+
ReflectionUtils.setField(defaultEscapeCharacterField, null, Character.MIN_VALUE);
701+
setSpringProperty(null);
702+
}
703+
704+
705+
@Test // gh-34865
706+
void defaultEscapeCharacterSetToXyz() {
707+
setSpringProperty("XYZ");
708+
709+
assertThatIllegalArgumentException()
710+
.isThrownBy(PropertySourcesPlaceholderConfigurer::new)
711+
.withMessage("Value [XYZ] for property [%s] must be a single character or an empty string",
712+
DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME);
713+
}
714+
715+
@Test // gh-34865
716+
void defaultEscapeCharacterDisabled() {
717+
setSpringProperty("");
718+
719+
MockEnvironment env = new MockEnvironment()
720+
.withProperty("user.home", "admin")
721+
.withProperty("my.property", "\\DOMAIN\\${user.home}");
722+
723+
DefaultListableBeanFactory bf = createBeanFactory();
724+
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
725+
ppc.setEnvironment(env);
726+
ppc.postProcessBeanFactory(bf);
727+
728+
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("\\DOMAIN\\admin");
729+
}
730+
731+
@Test // gh-34865
732+
void defaultEscapeCharacterSetToBackslash() {
733+
setSpringProperty("\\");
734+
735+
MockEnvironment env = new MockEnvironment()
736+
.withProperty("user.home", "admin")
737+
.withProperty("my.property", "\\DOMAIN\\${user.home}");
738+
739+
DefaultListableBeanFactory bf = createBeanFactory();
740+
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
741+
ppc.setEnvironment(env);
742+
ppc.postProcessBeanFactory(bf);
743+
744+
// \DOMAIN\${user.home} resolves to \DOMAIN${user.home} instead of \DOMAIN\admin
745+
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("\\DOMAIN${user.home}");
746+
}
747+
748+
@Test // gh-34865
749+
void defaultEscapeCharacterSetToTilde() {
750+
setSpringProperty("~");
751+
752+
MockEnvironment env = new MockEnvironment()
753+
.withProperty("user.home", "admin\\~${nested}")
754+
.withProperty("my.property", "DOMAIN\\${user.home}\\~${enigma}");
755+
756+
DefaultListableBeanFactory bf = createBeanFactory();
757+
PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
758+
ppc.setEnvironment(env);
759+
ppc.postProcessBeanFactory(bf);
760+
761+
assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("DOMAIN\\admin\\${nested}\\${enigma}");
762+
}
763+
764+
private static void setSpringProperty(String value) {
765+
SpringProperties.setProperty(DEFAULT_PLACEHOLDER_ESCAPE_CHARACTER_PROPERTY_NAME, value);
766+
}
767+
768+
private static DefaultListableBeanFactory createBeanFactory() {
769+
BeanDefinition beanDefinition = genericBeanDefinition(TestBean.class)
770+
.addPropertyValue("name", "${my.property}")
771+
.getBeanDefinition();
772+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
773+
bf.registerBeanDefinition("testBean",beanDefinition);
774+
return bf;
775+
}
776+
777+
}
778+
779+
670780
private static class OptionalTestBean {
671781

672782
private Optional<String> name;

0 commit comments

Comments
 (0)