From 5c76ff5ef68c995852b82996501458a7defd914f Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 18 Jan 2022 16:00:50 +0100 Subject: [PATCH] Ensure unresolvable placeholders can be ignored with @Value Prior to this commit, if a PropertySourcesPlaceholderConfigurer bean was configured with its ignoreUnresolvablePlaceholders flag set to true, unresolvable placeholders in an @Value annotation were not ignored, resulting in a BeanCreationException for the bean using @Value. For example, given a property declared as `my.app.var = ${var}` without a corresponding `var` property declared, an attempt to resolve `@Value("${my.app.var}")` resulted in the following exception. java.lang.IllegalArgumentException: Could not resolve placeholder 'var' in value "${var}" This commit fixes this by modifying PropertySourcesPlaceholderConfigurer's postProcessBeanFactory(...) method so that a local PropertyResolver is created if the ignoreUnresolvablePlaceholders flag is set to true. The local PropertyResolver then enforces that flag, since the Environment in the ApplicationContext is most likely not configured with ignoreUnresolvablePlaceholders set to true. Closes gh-27947 --- .../PropertySourcesPlaceholderConfigurer.java | 20 ++++- ...ertySourcesPlaceholderConfigurerTests.java | 73 ++++++++++++++++++- 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java b/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java index a0907a1f3a1..6b5824cea5e 100644 --- a/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java +++ b/spring-context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -24,10 +24,12 @@ import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.PlaceholderConfigurerSupport; import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurablePropertyResolver; import org.springframework.core.env.Environment; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertyResolver; import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySourcesPropertyResolver; @@ -57,6 +59,7 @@ import org.springframework.util.StringValueResolver; * * @author Chris Beams * @author Juergen Hoeller + * @author Sam Brannen * @since 3.1 * @see org.springframework.core.env.ConfigurableEnvironment * @see org.springframework.beans.factory.config.PlaceholderConfigurerSupport @@ -129,12 +132,25 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS if (this.propertySources == null) { this.propertySources = new MutablePropertySources(); if (this.environment != null) { + PropertyResolver propertyResolver = this.environment; + // If the ignoreUnresolvablePlaceholders flag is set to true, we have to create a + // local PropertyResolver to enforce that setting, since the Environment is most + // likely not configured with ignoreUnresolvablePlaceholders set to true. + // See https://github.com/spring-projects/spring-framework/issues/27947 + if (this.ignoreUnresolvablePlaceholders && (this.environment instanceof ConfigurableEnvironment)) { + ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment) this.environment; + PropertySourcesPropertyResolver resolver = + new PropertySourcesPropertyResolver(configurableEnvironment.getPropertySources()); + resolver.setIgnoreUnresolvableNestedPlaceholders(true); + propertyResolver = resolver; + } + PropertyResolver propertyResolverToUse = propertyResolver; this.propertySources.addLast( new PropertySource(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) { @Override @Nullable public String getProperty(String key) { - return this.source.getProperty(key); + return propertyResolverToUse.getProperty(key); } } ); 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 bdb41244bdd..42fe1a715e2 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 @@ -21,9 +21,14 @@ import java.util.Properties; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.testfixture.beans.TestBean; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; @@ -40,8 +45,11 @@ import static org.springframework.beans.factory.support.BeanDefinitionBuilder.ge import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition; /** + * Tests for {@link PropertySourcesPlaceholderConfigurer}. + * * @author Chris Beams * @author Juergen Hoeller + * @author Sam Brannen * @since 3.1 */ public class PropertySourcesPlaceholderConfigurerTests { @@ -159,8 +167,11 @@ public class PropertySourcesPlaceholderConfigurerTests { PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); //pc.setIgnoreUnresolvablePlaceholders(false); // the default - assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(() -> - ppc.postProcessBeanFactory(bf)); + assertThatExceptionOfType(BeanDefinitionStoreException.class) + .isThrownBy(() -> ppc.postProcessBeanFactory(bf)) + .havingCause() + .isExactlyInstanceOf(IllegalArgumentException.class) + .withMessage("Could not resolve placeholder 'my.name' in value \"${my.name}\""); } @Test @@ -177,6 +188,38 @@ public class PropertySourcesPlaceholderConfigurerTests { assertThat(bf.getBean(TestBean.class).getName()).isEqualTo("${my.name}"); } + @Test + // https://github.com/spring-projects/spring-framework/issues/27947 + public void ignoreUnresolvablePlaceholdersInAtValueAnnotation__falseIsDefault() { + MockPropertySource mockPropertySource = new MockPropertySource("test"); + mockPropertySource.setProperty("my.key", "${enigma}"); + @SuppressWarnings("resource") + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.getEnvironment().getPropertySources().addLast(mockPropertySource); + context.register(IgnoreUnresolvablePlaceholdersFalseConfig.class); + + assertThatExceptionOfType(BeanCreationException.class) + .isThrownBy(context::refresh) + .havingCause() + .isExactlyInstanceOf(IllegalArgumentException.class) + .withMessage("Could not resolve placeholder 'enigma' in value \"${enigma}\""); + } + + @Test + // https://github.com/spring-projects/spring-framework/issues/27947 + public void ignoreUnresolvablePlaceholdersInAtValueAnnotation_true() { + MockPropertySource mockPropertySource = new MockPropertySource("test"); + mockPropertySource.setProperty("my.key", "${enigma}"); + @SuppressWarnings("resource") + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.getEnvironment().getPropertySources().addLast(mockPropertySource); + context.register(IgnoreUnresolvablePlaceholdersTrueConfig.class); + context.refresh(); + + IgnoreUnresolvablePlaceholdersTrueConfig config = context.getBean(IgnoreUnresolvablePlaceholdersTrueConfig.class); + assertThat(config.value).isEqualTo("${enigma}"); + } + @Test @SuppressWarnings("serial") public void nestedUnresolvablePlaceholder() { @@ -402,4 +445,30 @@ public class PropertySourcesPlaceholderConfigurerTests { } } + @Configuration + static class IgnoreUnresolvablePlaceholdersFalseConfig { + + @Value("${my.key}") + String value; + + @Bean + static PropertySourcesPlaceholderConfigurer pspc() { + return new PropertySourcesPlaceholderConfigurer(); + } + } + + @Configuration + static class IgnoreUnresolvablePlaceholdersTrueConfig { + + @Value("${my.key}") + String value; + + @Bean + static PropertySourcesPlaceholderConfigurer pspc() { + PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer(); + pspc.setIgnoreUnresolvablePlaceholders(true); + return pspc; + } + } + }