diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java index 587cab16f54..de3dbc25c3a 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/TypedStringValue.java @@ -44,6 +44,8 @@ public class TypedStringValue implements BeanMetadataElement { private String specifiedTypeName; + private volatile boolean dynamic; + /** * Create a new {@link TypedStringValue} for the given String value. @@ -185,6 +187,21 @@ public class TypedStringValue implements BeanMetadataElement { return this.specifiedTypeName; } + /** + * Mark this value as dynamic, i.e. as containing an expression + * and hence not being subject to caching. + */ + public void setDynamic() { + this.dynamic = true; + } + + /** + * Return whether this value has been marked as dynamic. + */ + public boolean isDynamic() { + return this.dynamic; + } + @Override public boolean equals(Object other) { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index f10b56af256..6d232c5642a 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -1311,7 +1311,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } deepCopy.add(pv); } - else if (originalValue instanceof TypedStringValue && convertible && + else if (convertible && originalValue instanceof TypedStringValue && + !((TypedStringValue) originalValue).isDynamic() && !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) { pv.setConvertedValue(convertedValue); deepCopy.add(pv); diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java index 3095ee42716..28de671c6ce 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java @@ -167,10 +167,10 @@ class BeanDefinitionValueResolver { Object propKey = propEntry.getKey(); Object propValue = propEntry.getValue(); if (propKey instanceof TypedStringValue) { - propKey = evaluate(((TypedStringValue) propKey).getValue()); + propKey = evaluate((TypedStringValue) propKey); } if (propValue instanceof TypedStringValue) { - propValue = evaluate(((TypedStringValue) propValue).getValue()); + propValue = evaluate((TypedStringValue) propValue); } copy.put(propKey, propValue); } @@ -179,7 +179,7 @@ class BeanDefinitionValueResolver { else if (value instanceof TypedStringValue) { // Convert value to target type here. TypedStringValue typedStringValue = (TypedStringValue) value; - Object valueObject = evaluate(typedStringValue.getValue()); + Object valueObject = evaluate(typedStringValue); try { Class resolvedTargetType = resolveTargetType(typedStringValue); if (resolvedTargetType != null) { @@ -201,6 +201,19 @@ class BeanDefinitionValueResolver { } } + /** + * Evaluate the given value as an expression, if necessary. + * @param value the candidate value (may be an expression) + * @return the resolved value + */ + protected Object evaluate(TypedStringValue value) { + Object result = this.beanFactory.evaluateBeanDefinitionString(value.getValue(), this.beanDefinition); + if (result != value.getValue()) { + value.setDynamic(); + } + return result; + } + /** * Evaluate the given value as an expression, if necessary. * @param value the candidate value (may be an expression) diff --git a/org.springframework.context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java b/org.springframework.context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java index 80a981850a2..cbcc1ab96fe 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java +++ b/org.springframework.context/src/main/java/org/springframework/context/expression/StandardBeanExpressionResolver.java @@ -32,6 +32,7 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeConverter; import org.springframework.expression.spel.support.StandardTypeLocator; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Standard implementation of the @@ -109,6 +110,9 @@ public class StandardBeanExpressionResolver implements BeanExpressionResolver { public Object evaluate(String value, BeanExpressionContext evalContext) throws BeansException { + if (!StringUtils.hasLength(value)) { + return value; + } try { Expression expr = this.expressionCache.get(value); if (expr == null) { diff --git a/org.springframework.context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java b/org.springframework.context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java index 43d9fefec53..3cf9cede758 100644 --- a/org.springframework.context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java +++ b/org.springframework.context/src/test/java/org/springframework/context/expression/ApplicationContextExpressionTests.java @@ -16,8 +16,8 @@ package org.springframework.context.expression; -import java.util.Properties; import java.io.Serializable; +import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -31,14 +31,15 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; import org.springframework.beans.factory.config.Scope; +import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.context.support.GenericApplicationContext; -import org.springframework.util.StopWatch; import org.springframework.util.SerializationTestUtils; +import org.springframework.util.StopWatch; /** * @author Juergen Hoeller @@ -170,6 +171,38 @@ public class ApplicationContextExpressionTests { } } + @Test + public void prototypeCreationReevaluatesExpressions() { + GenericApplicationContext ac = new GenericApplicationContext(); + AnnotationConfigUtils.registerAnnotationConfigProcessors(ac); + RootBeanDefinition rbd = new RootBeanDefinition(PrototypeTestBean.class); + rbd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + rbd.getPropertyValues().addPropertyValue("country", "#{systemProperties.country}"); + rbd.getPropertyValues().addPropertyValue("country2", new TypedStringValue("#{systemProperties.country}")); + ac.registerBeanDefinition("test", rbd); + ac.refresh(); + + try { + System.getProperties().put("name", "juergen1"); + System.getProperties().put("country", "UK1"); + PrototypeTestBean tb = (PrototypeTestBean) ac.getBean("test"); + assertEquals("juergen1", tb.getName()); + assertEquals("UK1", tb.getCountry()); + assertEquals("UK1", tb.getCountry2()); + + System.getProperties().put("name", "juergen2"); + System.getProperties().put("country", "UK2"); + tb = (PrototypeTestBean) ac.getBean("test"); + assertEquals("juergen2", tb.getName()); + assertEquals("UK2", tb.getCountry()); + assertEquals("UK2", tb.getCountry2()); + } + finally { + System.getProperties().remove("name"); + System.getProperties().remove("country"); + } + } + @Test public void prototypeCreationIsFastEnough() { if (factoryLog.isTraceEnabled() || factoryLog.isDebugEnabled()) { @@ -302,4 +335,39 @@ public class ApplicationContextExpressionTests { } } + + public static class PrototypeTestBean { + + public String name; + + public String country; + + public String country2; + + @Value("#{systemProperties.name}") + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getCountry() { + return country; + } + + public void setCountry2(String country2) { + this.country2 = country2; + } + + public String getCountry2() { + return country2; + } + } + }