Merge branch '6.2.x'
This commit is contained in:
@@ -103,6 +103,14 @@ for details.
|
|||||||
{spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
|
{spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`]
|
||||||
for details.
|
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`
|
| `spring.test.aot.processing.failOnError`
|
||||||
| A boolean flag that controls whether errors encountered during AOT processing in the
|
| 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.
|
_Spring TestContext Framework_ should result in an exception that fails the overall process.
|
||||||
|
|||||||
@@ -101,8 +101,11 @@ NOTE: When configuring a `PropertySourcesPlaceholderConfigurer` using JavaConfig
|
|||||||
|
|
||||||
Using the above configuration ensures Spring initialization failure if any `${}`
|
Using the above configuration ensures Spring initialization failure if any `${}`
|
||||||
placeholder could not be resolved. It is also possible to use methods like
|
placeholder could not be resolved. It is also possible to use methods like
|
||||||
`setPlaceholderPrefix`, `setPlaceholderSuffix`, `setValueSeparator`, or
|
`setPlaceholderPrefix()`, `setPlaceholderSuffix()`, `setValueSeparator()`, or
|
||||||
`setEscapeCharacter` to customize placeholders.
|
`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
|
NOTE: Spring Boot configures by default a `PropertySourcesPlaceholderConfigurer` bean that
|
||||||
will get properties from `application.properties` and `application.yml` files.
|
will get properties from `application.properties` and `application.yml` files.
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ Thus, marking it for lazy initialization will be ignored, and the
|
|||||||
|
|
||||||
|
|
||||||
[[beans-factory-placeholderconfigurer]]
|
[[beans-factory-placeholderconfigurer]]
|
||||||
=== Example: The Class Name Substitution `PropertySourcesPlaceholderConfigurer`
|
=== Example: Property Placeholder Substitution with `PropertySourcesPlaceholderConfigurer`
|
||||||
|
|
||||||
You can use the `PropertySourcesPlaceholderConfigurer` to externalize property values
|
You can use the `PropertySourcesPlaceholderConfigurer` to externalize property values
|
||||||
from a bean definition in a separate file by using the standard Java `Properties` format.
|
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,
|
The example shows properties configured from an external `Properties` file. At runtime,
|
||||||
a `PropertySourcesPlaceholderConfigurer` is applied to the metadata that replaces some
|
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.
|
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:
|
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
|
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 same applies for other placeholder values that match keys in the properties file. The
|
||||||
The `PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
|
`PropertySourcesPlaceholderConfigurer` checks for placeholders in most properties and
|
||||||
attributes of a bean definition. Furthermore, you can customize the placeholder prefix, suffix,
|
attributes of a bean definition. Furthermore, you can customize the placeholder prefix,
|
||||||
default value separator, and escape character.
|
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 the `context` namespace, you can configure property placeholders
|
||||||
with a dedicated configuration element. You can provide one or more locations as a
|
with a dedicated configuration element. You can provide one or more locations as a
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ NOTE: If you use Spring Boot, you should probably use
|
|||||||
instead of `@Value` annotations.
|
instead of `@Value` annotations.
|
||||||
|
|
||||||
As an alternative, you can customize the property placeholder prefix by declaring the
|
As an alternative, you can customize the property placeholder prefix by declaring the
|
||||||
following configuration bean:
|
following `PropertySourcesPlaceholderConfigurer` bean:
|
||||||
|
|
||||||
[source,kotlin,indent=0]
|
[source,kotlin,indent=0]
|
||||||
----
|
----
|
||||||
@@ -200,8 +200,10 @@ following configuration bean:
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
You can customize existing code (such as Spring Boot actuators or `@LocalServerPort`)
|
You can support components (such as Spring Boot actuators or `@LocalServerPort`) that use
|
||||||
that uses the `${...}` syntax, with configuration beans, as the following example shows:
|
the standard `${...}` syntax alongside components that use the custom `%{...}` syntax by
|
||||||
|
declaring multiple `PropertySourcesPlaceholderConfigurer` beans, as the following example
|
||||||
|
shows:
|
||||||
|
|
||||||
[source,kotlin,indent=0]
|
[source,kotlin,indent=0]
|
||||||
----
|
----
|
||||||
@@ -215,6 +217,9 @@ that uses the `${...}` syntax, with configuration beans, as the following exampl
|
|||||||
fun defaultPropertyConfigurer() = PropertySourcesPlaceholderConfigurer()
|
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]]
|
[[checked-exceptions]]
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.springframework.beans.factory.BeanDefinitionStoreException;
|
|||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
import org.springframework.beans.factory.BeanNameAware;
|
import org.springframework.beans.factory.BeanNameAware;
|
||||||
|
import org.springframework.core.env.AbstractPropertyResolver;
|
||||||
import org.springframework.util.StringValueResolver;
|
import org.springframework.util.StringValueResolver;
|
||||||
import org.springframework.util.SystemPropertyUtils;
|
import org.springframework.util.SystemPropertyUtils;
|
||||||
|
|
||||||
@@ -86,6 +87,7 @@ import org.springframework.util.SystemPropertyUtils;
|
|||||||
*
|
*
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 3.1
|
* @since 3.1
|
||||||
* @see PropertyPlaceholderConfigurer
|
* @see PropertyPlaceholderConfigurer
|
||||||
* @see org.springframework.context.support.PropertySourcesPlaceholderConfigurer
|
* @see org.springframework.context.support.PropertySourcesPlaceholderConfigurer
|
||||||
@@ -102,7 +104,11 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
|||||||
/** Default value separator: {@value}. */
|
/** Default value separator: {@value}. */
|
||||||
public static final String DEFAULT_VALUE_SEPARATOR = SystemPropertyUtils.VALUE_SEPARATOR;
|
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;
|
public static final Character DEFAULT_ESCAPE_CHARACTER = SystemPropertyUtils.ESCAPE_CHARACTER;
|
||||||
|
|
||||||
|
|
||||||
@@ -115,8 +121,10 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
|||||||
/** Defaults to {@value #DEFAULT_VALUE_SEPARATOR}. */
|
/** Defaults to {@value #DEFAULT_VALUE_SEPARATOR}. */
|
||||||
protected @Nullable String valueSeparator = DEFAULT_VALUE_SEPARATOR;
|
protected @Nullable String valueSeparator = DEFAULT_VALUE_SEPARATOR;
|
||||||
|
|
||||||
/** Defaults to {@link #DEFAULT_ESCAPE_CHARACTER}. */
|
/**
|
||||||
protected @Nullable Character escapeCharacter = DEFAULT_ESCAPE_CHARACTER;
|
* The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}.
|
||||||
|
*/
|
||||||
|
protected @Nullable Character escapeCharacter = AbstractPropertyResolver.getDefaultEscapeCharacter();
|
||||||
|
|
||||||
protected boolean trimValues = false;
|
protected boolean trimValues = false;
|
||||||
|
|
||||||
@@ -160,6 +168,7 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi
|
|||||||
* {@linkplain #setPlaceholderPrefix(String) placeholder prefix} and the
|
* {@linkplain #setPlaceholderPrefix(String) placeholder prefix} and the
|
||||||
* {@linkplain #setValueSeparator(String) value separator}, or {@code null}
|
* {@linkplain #setValueSeparator(String) value separator}, or {@code null}
|
||||||
* if no escaping should take place.
|
* if no escaping should take place.
|
||||||
|
* <p>The default is determined by {@link AbstractPropertyResolver#getDefaultEscapeCharacter()}.
|
||||||
* @since 6.2
|
* @since 6.2
|
||||||
*/
|
*/
|
||||||
public void setEscapeCharacter(@Nullable Character escapeCharacter) {
|
public void setEscapeCharacter(@Nullable Character escapeCharacter) {
|
||||||
|
|||||||
@@ -16,12 +16,15 @@
|
|||||||
|
|
||||||
package org.springframework.context.support;
|
package org.springframework.context.support;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Properties;
|
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.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Scope;
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.core.SpringProperties;
|
||||||
import org.springframework.core.convert.support.DefaultConversionService;
|
import org.springframework.core.convert.support.DefaultConversionService;
|
||||||
|
import org.springframework.core.env.AbstractPropertyResolver;
|
||||||
import org.springframework.core.env.EnumerablePropertySource;
|
import org.springframework.core.env.EnumerablePropertySource;
|
||||||
import org.springframework.core.env.MutablePropertySources;
|
import org.springframework.core.env.MutablePropertySources;
|
||||||
import org.springframework.core.env.PropertySource;
|
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.core.testfixture.env.MockPropertySource;
|
||||||
import org.springframework.mock.env.MockEnvironment;
|
import org.springframework.mock.env.MockEnvironment;
|
||||||
import org.springframework.util.PlaceholderResolutionException;
|
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.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
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.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||||
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
|
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition;
|
||||||
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition;
|
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}.
|
* 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 static class OptionalTestBean {
|
||||||
|
|
||||||
private Optional<String> name;
|
private Optional<String> name;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import org.apache.commons.logging.Log;
|
|||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import org.springframework.core.SpringProperties;
|
||||||
import org.springframework.core.convert.ConversionService;
|
import org.springframework.core.convert.ConversionService;
|
||||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||||
import org.springframework.core.convert.support.DefaultConversionService;
|
import org.springframework.core.convert.support.DefaultConversionService;
|
||||||
@@ -37,10 +38,51 @@ import org.springframework.util.SystemPropertyUtils;
|
|||||||
*
|
*
|
||||||
* @author Chris Beams
|
* @author Chris Beams
|
||||||
* @author Juergen Hoeller
|
* @author Juergen Hoeller
|
||||||
|
* @author Sam Brannen
|
||||||
* @since 3.1
|
* @since 3.1
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
|
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JVM system property used to change the <em>default</em> escape character
|
||||||
|
* for property placeholder support: {@value}.
|
||||||
|
* <p>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 '@'}.
|
||||||
|
* <pre style="code">-Dspring.placeholder.escapeCharacter.default=@</pre>
|
||||||
|
* <p>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.
|
||||||
|
* <pre style="code">-Dspring.placeholder.escapeCharacter.default=</pre>
|
||||||
|
* <p>If the property is not set, {@code '\'} will be used as the default
|
||||||
|
* escape character.
|
||||||
|
* <p>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
|
||||||
|
*/
|
||||||
|
static volatile @Nullable Character defaultEscapeCharacter = UNDEFINED_ESCAPE_CHARACTER;
|
||||||
|
|
||||||
|
|
||||||
protected final Log logger = LogFactory.getLog(getClass());
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
private volatile @Nullable ConfigurableConversionService conversionService;
|
private volatile @Nullable ConfigurableConversionService conversionService;
|
||||||
@@ -57,7 +99,7 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
|||||||
|
|
||||||
private @Nullable String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
|
private @Nullable String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
|
||||||
|
|
||||||
private @Nullable Character escapeCharacter = SystemPropertyUtils.ESCAPE_CHARACTER;
|
private @Nullable Character escapeCharacter = getDefaultEscapeCharacter();
|
||||||
|
|
||||||
private final Set<String> requiredProperties = new LinkedHashSet<>();
|
private final Set<String> requiredProperties = new LinkedHashSet<>();
|
||||||
|
|
||||||
@@ -119,9 +161,8 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
* <p>The default is {@code '\'}.
|
* <p>The default is determined by {@link #getDefaultEscapeCharacter()}.
|
||||||
* @since 6.2
|
* @since 6.2
|
||||||
* @see SystemPropertyUtils#ESCAPE_CHARACTER
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setEscapeCharacter(@Nullable Character escapeCharacter) {
|
public void setEscapeCharacter(@Nullable Character escapeCharacter) {
|
||||||
@@ -279,4 +320,59 @@ public abstract class AbstractPropertyResolver implements ConfigurablePropertyRe
|
|||||||
*/
|
*/
|
||||||
protected abstract @Nullable String getPropertyAsRawString(String key);
|
protected abstract @Nullable String getPropertyAsRawString(String key);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default {@linkplain #setEscapeCharacter(Character) escape character}
|
||||||
|
* to use when parsing strings for property placeholder resolution.
|
||||||
|
* <p>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.
|
||||||
|
* <p>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
|
||||||
|
*/
|
||||||
|
public static @Nullable 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
120
spring-core/src/test/java/org/springframework/core/env/AbstractPropertyResolverTests.java
vendored
Normal file
120
spring-core/src/test/java/org/springframework/core/env/AbstractPropertyResolverTests.java
vendored
Normal file
@@ -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.jspecify.annotations.Nullable;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.core.SpringProperties;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user