From 431eaf6df8af2ffee240c991403c346e0cf62107 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Wed, 5 Jan 2011 22:24:37 +0000 Subject: [PATCH] Refactor PropertyPlaceholderConfigurer hierarchy PropertySourcesPlaceholderConfigurer accommodates recent changes in Environment and PropertySource APIs, e.g. no longer assuming enumerability of property names. PSPC reuses as much functionality as possible from AbstractPropertyPlaceholderConfigurer, but overrides postProcessBeanFactory() and defines its own variation on processProperties() in order to accept a PropertyResolver rather than a PropertySource. AbstractPropertyPlaceholderConfigurer introduces doProcessProperties() method to encapsulate that which is actually common, such as the visiting of each bean definition once a StringValueResolver has been created in the subclass. --- ...AbstractPropertyPlaceholderConfigurer.java | 49 +--------- .../config/PropertyPlaceholderConfigurer.java | 40 ++++++-- .../PropertySourcesPlaceholderConfigurer.java | 86 ++++++++++------- ...ertySourcesPlaceholderConfigurerTests.java | 94 +++++++++++++++++++ 4 files changed, 186 insertions(+), 83 deletions(-) diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractPropertyPlaceholderConfigurer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractPropertyPlaceholderConfigurer.java index b03e529d4c9..27dc5396f24 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractPropertyPlaceholderConfigurer.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/AbstractPropertyPlaceholderConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -16,18 +16,12 @@ package org.springframework.beans.factory.config; -import java.util.Properties; - -import org.springframework.beans.BeansException; 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.util.PropertyPlaceholderHelper; -import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; import org.springframework.util.StringValueResolver; - /** * Abstract base class for property resource configurers that resolve placeholders * in bean definition property values. Implementations pull values from a @@ -114,17 +108,12 @@ public abstract class AbstractPropertyPlaceholderConfigurer extends PropertyReso protected boolean ignoreUnresolvablePlaceholders = false; - private String nullValue; - - private String beanName; + protected String nullValue; private BeanFactory beanFactory; + private String beanName; - /** - * Return the {@code PlaceholderResolver} for this configurer. - */ - protected abstract PlaceholderResolver getPlaceholderResolver(Properties props); /** * Set the prefix that a placeholder string starts with. @@ -200,16 +189,8 @@ public abstract class AbstractPropertyPlaceholderConfigurer extends PropertyReso this.beanFactory = beanFactory; } - - /** - * Visit each bean definition in the given bean factory and attempt to replace ${...} property - * placeholders with values from the given properties. - */ - @Override - protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) - throws BeansException { - - StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props); + protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, + StringValueResolver valueResolver) { BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver); String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames(); @@ -234,24 +215,4 @@ public abstract class AbstractPropertyPlaceholderConfigurer extends PropertyReso beanFactoryToProcess.addEmbeddedValueResolver(valueResolver); } - - private class PlaceholderResolvingStringValueResolver implements StringValueResolver { - - private final PropertyPlaceholderHelper helper; - - private final PlaceholderResolver resolver; - - public PlaceholderResolvingStringValueResolver(Properties props) { - this.helper = new PropertyPlaceholderHelper( - placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders); - this.resolver = getPlaceholderResolver(props); - } - - public String resolveStringValue(String strVal) throws BeansException { - String value = this.helper.replacePlaceholders(strVal, this.resolver); - return (value.equals(nullValue) ? null : value); - } - } - - } diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java index b823d0f917a..95318a2a278 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java @@ -19,11 +19,13 @@ package org.springframework.beans.factory.config; import java.util.Properties; import java.util.Set; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.core.Constants; import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; +import org.springframework.util.StringValueResolver; /** * {@link AbstractPropertyPlaceholderConfigurer} subclass that resolves ${...} placeholders @@ -207,6 +209,37 @@ public class PropertyPlaceholderConfigurer extends AbstractPropertyPlaceholderCo } } + /** + * Visit each bean definition in the given bean factory and attempt to replace ${...} property + * placeholders with values from the given properties. + */ + @Override + protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) + throws BeansException { + + StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props); + + this.doProcessProperties(beanFactoryToProcess, valueResolver); + } + + private class PlaceholderResolvingStringValueResolver implements StringValueResolver { + + private final PropertyPlaceholderHelper helper; + + private final PlaceholderResolver resolver; + + public PlaceholderResolvingStringValueResolver(Properties props) { + this.helper = new PropertyPlaceholderHelper( + placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders); + this.resolver = new PropertyPlaceholderConfigurerResolver(props); + } + + public String resolveStringValue(String strVal) throws BeansException { + String value = this.helper.replacePlaceholders(strVal, this.resolver); + return (value.equals(nullValue) ? null : value); + } + } + /** * Parse the given String value for placeholder resolution. * @param strVal the String value to parse @@ -225,12 +258,6 @@ public class PropertyPlaceholderConfigurer extends AbstractPropertyPlaceholderCo return helper.replacePlaceholders(strVal, resolver); } - @Override - protected PlaceholderResolver getPlaceholderResolver(Properties props) { - return new PropertyPlaceholderConfigurerResolver(props); - } - - private class PropertyPlaceholderConfigurerResolver implements PlaceholderResolver { private final Properties props; @@ -244,5 +271,4 @@ public class PropertyPlaceholderConfigurer extends AbstractPropertyPlaceholderCo } } - } diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java b/org.springframework.context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java index 20677f83006..f3196453033 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.java @@ -17,24 +17,20 @@ package org.springframework.context.support; import java.io.IOException; -import java.util.Properties; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanInitializationException; import org.springframework.beans.factory.config.AbstractPropertyPlaceholderConfigurer; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; import org.springframework.context.EnvironmentAware; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.EnumerablePropertySource; +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; -import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; +import org.springframework.util.StringValueResolver; /** * Specialization of {@link AbstractPropertyPlaceholderConfigurer} @@ -45,7 +41,7 @@ import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver; * @author Chris Beams * @since 3.1 * @see AbstractPropertyPlaceholderConfigurer - * @see PropertyPlaceholderConfigurer + * @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer */ public class PropertySourcesPlaceholderConfigurer extends AbstractPropertyPlaceholderConfigurer implements EnvironmentAware { @@ -56,9 +52,13 @@ public class PropertySourcesPlaceholderConfigurer extends AbstractPropertyPlaceh */ public static final String LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME = "localProperties"; - private MutablePropertySources propertySources; + /** + * {@value} is the name given to the {@link PropertySource} that wraps the + * {@linkplain #setEnvironment environment} supplied to this configurer. + */ + public static final String ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME = "environmentProperties"; - private PropertyResolver propertyResolver; + private MutablePropertySources propertySources; private Environment environment; @@ -83,15 +83,6 @@ public class PropertySourcesPlaceholderConfigurer extends AbstractPropertyPlaceh this.propertySources = new MutablePropertySources(propertySources); } - @Override - protected PlaceholderResolver getPlaceholderResolver(Properties props) { - return new PlaceholderResolver() { - public String resolvePlaceholder(String placeholderName) { - return propertyResolver.getProperty(placeholderName); - } - }; - } - /** * {@inheritDoc} *

Processing occurs by replacing ${...} placeholders in bean definitions by resolving each @@ -113,7 +104,14 @@ public class PropertySourcesPlaceholderConfigurer extends AbstractPropertyPlaceh if (this.propertySources == null) { this.propertySources = new MutablePropertySources(); if (this.environment != null) { - this.propertySources.addAll(((ConfigurableEnvironment)this.environment).getPropertySources()); + this.propertySources.addLast( + new PropertySource(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) { + @Override + public String getProperty(String key) { + return this.source.getProperty(key); + } + } + ); } try { PropertySource localPropertySource = @@ -129,19 +127,43 @@ public class PropertySourcesPlaceholderConfigurer extends AbstractPropertyPlaceh } } - this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources); - this.processProperties(beanFactory, asProperties(this.propertySources)); + this.processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources)); + } + + /** + * Visit each bean definition in the given bean factory and attempt to replace ${...} property + * placeholders with values from the given properties. + */ + protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, + final ConfigurablePropertyResolver propertyResolver) throws BeansException { + + propertyResolver.setPlaceholderPrefix(this.placeholderPrefix); + propertyResolver.setPlaceholderSuffix(this.placeholderSuffix); + propertyResolver.setValueSeparator(this.valueSeparator); + + StringValueResolver valueResolver = new StringValueResolver() { + public String resolveStringValue(String strVal) { + String resolved = ignoreUnresolvablePlaceholders ? + propertyResolver.resolvePlaceholders(strVal) : + propertyResolver.resolveRequiredPlaceholders(strVal); + return (resolved.equals(nullValue) ? null : resolved); + } + }; + + this.doProcessProperties(beanFactoryToProcess, valueResolver); + } + + /** + * Implemented for compatibility with {@link AbstractPropertyPlaceholderConfigurer}. + * @deprecated in favor of {@link #processProperties(ConfigurableListableBeanFactory, ConfigurablePropertyResolver)} + * @throws UnsupportedOperationException + */ + @Override + @Deprecated + protected void processProperties(ConfigurableListableBeanFactory beanFactory, java.util.Properties props) + throws BeansException { + throw new UnsupportedOperationException( + "call processProperties(ConfigurableListableBeanFactory, ConfigurablePropertyResolver)"); } - public Properties asProperties(PropertySources propertySources) { - Properties mergedProps = new Properties(); - java.util.List> propertySourcesList = propertySources.asList(); - for (int i = propertySourcesList.size() -1; i >= 0; i--) { - PropertySource source = propertySourcesList.get(i); - for (String key : ((EnumerablePropertySource)source).getPropertyNames()) { - mergedProps.put(key, source.getProperty(key)); - } - } - return mergedProps; - } } diff --git a/org.springframework.context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java b/org.springframework.context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java index bae62fd624a..bc0bf3193fa 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/support/PropertySourcesPlaceholderConfigurerTests.java @@ -17,14 +17,20 @@ package org.springframework.context.support; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; import static org.springframework.beans.factory.support.BeanDefinitionBuilder.genericBeanDefinition; +import static org.springframework.beans.factory.support.BeanDefinitionBuilder.rootBeanDefinition; import java.util.Properties; import org.junit.Test; +import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.core.env.DefaultEnvironment; import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.mock.env.MockEnvironment; @@ -139,6 +145,58 @@ public class PropertySourcesPlaceholderConfigurerTests { assertThat(bf.getBean(TestBean.class).getName(), equalTo("${my.name}")); } + @Test(expected=BeanDefinitionStoreException.class) + public void ignoreUnresolvablePlaceholders_falseIsDefault() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${my.name}") + .getBeanDefinition()); + + PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer(); + //pc.setIgnoreUnresolvablePlaceholders(false); // the default + pc.postProcessBeanFactory(bf); // should throw + } + + @Test + public void ignoreUnresolvablePlaceholders_true() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${my.name}") + .getBeanDefinition()); + + PropertySourcesPlaceholderConfigurer pc = new PropertySourcesPlaceholderConfigurer(); + pc.setIgnoreUnresolvablePlaceholders(true); + pc.postProcessBeanFactory(bf); + assertThat(bf.getBean(TestBean.class).getName(), equalTo("${my.name}")); + } + + @Test + public void withNonEnumerablePropertySource() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + genericBeanDefinition(TestBean.class) + .addPropertyValue("name", "${foo}") + .getBeanDefinition()); + + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + + PropertySource ps = new PropertySource("simplePropertySource", new Object()) { + @Override + public Object getProperty(String key) { + return "bar"; + } + }; + + MockEnvironment env = new MockEnvironment(); + env.getPropertySources().addFirst(ps); + ppc.setEnvironment(env); + + ppc.postProcessBeanFactory(bf); + assertThat(bf.getBean(TestBean.class).getName(), equalTo("bar")); + } + @SuppressWarnings("serial") private void localPropertiesOverride(boolean override) { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -160,4 +218,40 @@ public class PropertySourcesPlaceholderConfigurerTests { } } + @Test + public void customPlaceholderPrefixAndSuffix() { + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setPlaceholderPrefix("@<"); + ppc.setPlaceholderSuffix(">"); + + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", + rootBeanDefinition(TestBean.class) + .addPropertyValue("name", "@") + .addPropertyValue("sex", "${key2}") + .getBeanDefinition()); + + System.setProperty("key1", "systemKey1Value"); + System.setProperty("key2", "systemKey2Value"); + ppc.setEnvironment(new DefaultEnvironment()); + ppc.postProcessBeanFactory(bf); + System.clearProperty("key1"); + System.clearProperty("key2"); + + assertThat(bf.getBean(TestBean.class).getName(), is("systemKey1Value")); + assertThat(bf.getBean(TestBean.class).getSex(), is("${key2}")); + } + + @Test + public void nullValueIsPreserved() { + PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer(); + ppc.setNullValue("customNull"); + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("testBean", rootBeanDefinition(TestBean.class) + .addPropertyValue("name", "${my.name}") + .getBeanDefinition()); + ppc.setEnvironment(new MockEnvironment().withProperty("my.name", "customNull")); + ppc.postProcessBeanFactory(bf); + assertThat(bf.getBean(TestBean.class).getName(), nullValue()); + } }