diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java index 706bfdedd8..43f8158d97 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -181,6 +181,12 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single */ TypeConverter getTypeConverter(); + /** + * Add a String resolver for embedded values such as annotation attributes. + * @param valueResolver the String resolver to apply to embedded values + */ + void addEmbeddedValueResolver(StringValueResolver valueResolver); + /** * Add a new BeanPostProcessor that will get applied to beans created * by this factory. To be invoked during factory configuration. diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index d2d36879b8..9eec915cd6 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -21,6 +21,7 @@ import java.lang.reflect.Field; import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.MethodParameter; +import org.springframework.core.ParameterNameDiscoverer; import org.springframework.util.Assert; /** @@ -127,6 +128,26 @@ public class DependencyDescriptor { } + /** + * Initialize parameter name discovery for the underlying method parameter, if any. + *

This method does not actually try to retrieve the parameter name at + * this point; it just allows discovery to happen when the application calls + * {@link #getDependencyName()} (if ever). + */ + public void initParameterNameDiscovery(ParameterNameDiscoverer parameterNameDiscoverer) { + if (this.methodParameter != null) { + this.methodParameter.initParameterNameDiscovery(parameterNameDiscoverer); + } + } + + /** + * Determine the name of the wrapped parameter/field. + * @return the declared name (never null) + */ + public String getDependencyName() { + return (this.field != null ? this.field.getName() : this.methodParameter.getParameterName()); + } + /** * Determine the declared (non-generic) type of the wrapped parameter/field. * @return the declared type (never null) 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 6825a9445a..0b19c629b8 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -273,6 +273,9 @@ public class PropertyPlaceholderConfigurer extends PropertyResourceConfigurer // New in Spring 2.5: resolve placeholders in alias target names and aliases as well. beanFactoryToProcess.resolveAliases(valueResolver); + + // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes. + beanFactoryToProcess.addEmbeddedValueResolver(valueResolver); } /** diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index c89d7b801e..a2fc4f1ed4 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -58,11 +59,13 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.config.Scope; +import org.springframework.beans.factory.config.DependencyProxyBuilder; import org.springframework.core.DecoratingClassLoader; import org.springframework.core.NamedThreadLocal; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; +import org.springframework.util.StringValueResolver; /** * Abstract base class for {@link org.springframework.beans.factory.BeanFactory} @@ -123,6 +126,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp private final Map> customEditors = new HashMap>(4); + /** String resolvers to apply e.g. to annotation attribute values */ + private final List embeddedValueResolvers = new LinkedList(); + /** BeanPostProcessors to apply in createBean */ private final List beanPostProcessors = new ArrayList(); @@ -650,6 +656,24 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp } } + public void addEmbeddedValueResolver(StringValueResolver valueResolver) { + Assert.notNull(valueResolver, "StringValueResolver must not be null"); + this.embeddedValueResolvers.add(valueResolver); + } + + /** + * Resolve the given embedded value, e.g. an annotation attribute. + * @param value the value to resolve + * @return the resolved value (may be the original value as-is) + */ + protected String resolveEmbeddedValue(String value) { + String result = value; + for (StringValueResolver resolver : this.embeddedValueResolvers) { + result = resolver.resolveStringValue(result); + } + return result; + } + public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) { Assert.notNull(beanPostProcessor, "BeanPostProcessor must not be null"); this.beanPostProcessors.add(beanPostProcessor); diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 2ebbaae00e..399f11a9a7 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -44,6 +44,7 @@ import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -88,6 +89,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto /** Whether to allow eager class loading even for lazy-init beans */ private boolean allowEagerClassLoading = true; + /** Resolver strategy for method parameter names */ + private ParameterNameDiscoverer parameterNameDiscoverer; + /** Resolver to use for checking if a bean definition is an autowire candidate */ private AutowireCandidateResolver autowireCandidateResolver = new SimpleAutowireCandidateResolver(); @@ -148,6 +152,17 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto this.allowEagerClassLoading = allowEagerClassLoading; } + /** + * Set the ParameterNameDiscoverer to use for resolving method parameter + * names if needed (e.g. for default qualifier values on autowired methods). + *

Default is none. A typical candidate is + * {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}, + * which implies an ASM dependency and hence isn't set as the default. + */ + public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { + this.parameterNameDiscoverer = parameterNameDiscoverer; + } + /** * Set a custom autowire candidate resolver for this BeanFactory to use * when deciding whether a bean definition should be considered as a @@ -581,12 +596,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto public Object resolveDependency(DependencyDescriptor descriptor, String beanName, Set autowiredBeanNames, TypeConverter typeConverter) throws BeansException { + descriptor.initParameterNameDiscovery(this.parameterNameDiscoverer); Class type = descriptor.getDependencyType(); Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { if (value instanceof String) { - value = evaluateBeanDefinitionString((String) value, getMergedBeanDefinition(beanName)); + String strVal = resolveEmbeddedValue((String) value); + value = evaluateBeanDefinitionString(strVal, getMergedBeanDefinition(beanName)); } return typeConverter.convertIfNecessary(value, type); } @@ -665,7 +682,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return null; } if (matchingBeans.size() > 1) { - String primaryBeanName = determinePrimaryCandidate(matchingBeans, type); + String primaryBeanName = determinePrimaryCandidate(matchingBeans, descriptor); if (primaryBeanName == null) { throw new NoSuchBeanDefinitionException(type, "expected single matching bean but found " + matchingBeans.size() + ": " + matchingBeans.keySet()); @@ -730,19 +747,26 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto * @param type the required type * @return the name of the primary candidate, or null if none found */ - protected String determinePrimaryCandidate(Map candidateBeans, Class type) { + protected String determinePrimaryCandidate(Map candidateBeans, DependencyDescriptor descriptor) { String primaryBeanName = null; + String fallbackBeanName = null; for (Map.Entry entry : candidateBeans.entrySet()) { String candidateBeanName = entry.getKey(); - if (isPrimary(candidateBeanName, entry.getValue())) { + Object beanInstance = entry.getValue(); + if (isPrimary(candidateBeanName, beanInstance)) { if (primaryBeanName != null) { - throw new NoSuchBeanDefinitionException(type, + throw new NoSuchBeanDefinitionException(descriptor.getDependencyType(), "more than one 'primary' bean found among candidates: " + candidateBeans.keySet()); } primaryBeanName = candidateBeanName; } + if (primaryBeanName == null && + (this.resolvableDependencies.values().contains(beanInstance) || + matchesBeanName(candidateBeanName, descriptor.getDependencyName()))) { + fallbackBeanName = candidateBeanName; + } } - return primaryBeanName; + return (primaryBeanName != null ? primaryBeanName : fallbackBeanName); } /** @@ -756,14 +780,20 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (containsBeanDefinition(beanName)) { return getMergedLocalBeanDefinition(beanName).isPrimary(); } - if (this.resolvableDependencies.values().contains(beanInstance)) { - return true; - } BeanFactory parentFactory = getParentBeanFactory(); return (parentFactory instanceof DefaultListableBeanFactory && ((DefaultListableBeanFactory) parentFactory).isPrimary(beanName, beanInstance)); } + /** + * Determine whether the given candidate name matches the bean name or the aliases + * stored in this bean definition. + */ + protected boolean matchesBeanName(String beanName, String candidateName) { + return (candidateName != null && + (candidateName.equals(beanName) || ObjectUtils.containsElement(getAliases(beanName), candidateName))); + } + /** * Raise a NoSuchBeanDefinitionException for an unresolvable dependency. */ diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java index 4d76a1d696..2eee9bf0d2 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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,6 +24,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextException; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; /** * Base class for {@link org.springframework.context.ApplicationContext} @@ -93,7 +94,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding */ public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) { - this.allowBeanDefinitionOverriding = Boolean.valueOf(allowBeanDefinitionOverriding); + this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding; } /** @@ -104,7 +105,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowCircularReferences */ public void setAllowCircularReferences(boolean allowCircularReferences) { - this.allowCircularReferences = Boolean.valueOf(allowCircularReferences); + this.allowCircularReferences = allowCircularReferences; } @@ -201,6 +202,7 @@ public abstract class AbstractRefreshableApplicationContext extends AbstractAppl if (this.allowCircularReferences != null) { beanFactory.setAllowCircularReferences(this.allowCircularReferences); } + beanFactory.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer()); beanFactory.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); } diff --git a/org.springframework.context/src/main/java/org/springframework/context/support/GenericApplicationContext.java b/org.springframework.context/src/main/java/org/springframework/context/support/GenericApplicationContext.java index b8fab005bb..11b5370f37 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/support/GenericApplicationContext.java +++ b/org.springframework.context/src/main/java/org/springframework/context/support/GenericApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -26,6 +26,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternResolver; @@ -98,6 +99,7 @@ public class GenericApplicationContext extends AbstractApplicationContext implem */ public GenericApplicationContext() { this.beanFactory = new DefaultListableBeanFactory(); + this.beanFactory.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer()); this.beanFactory.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); } diff --git a/org.springframework.context/src/test/java/org/springframework/beans/factory/xml/QualifierAnnotationTests.java b/org.springframework.context/src/test/java/org/springframework/beans/factory/xml/QualifierAnnotationTests.java index d7319eb171..797f5a1273 100644 --- a/org.springframework.context/src/test/java/org/springframework/beans/factory/xml/QualifierAnnotationTests.java +++ b/org.springframework.context/src/test/java/org/springframework/beans/factory/xml/QualifierAnnotationTests.java @@ -17,22 +17,22 @@ package org.springframework.beans.factory.xml; import static java.lang.String.format; -import static org.junit.Assert.*; -import static org.springframework.util.ClassUtils.convertClassNameToResourcePath; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Properties; +import static org.junit.Assert.*; import org.junit.Test; + import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver; import org.springframework.beans.factory.support.BeanDefinitionReader; import org.springframework.context.support.StaticApplicationContext; +import static org.springframework.util.ClassUtils.*; /** * @author Mark Fisher @@ -86,6 +86,30 @@ public final class QualifierAnnotationTests { assertTrue(testBean.myProps != null && testBean.myProps.isEmpty()); } + @Test + public void testQualifiedByFieldName() { + StaticApplicationContext context = new StaticApplicationContext(); + BeanDefinitionReader reader = new XmlBeanDefinitionReader(context); + reader.loadBeanDefinitions(CONFIG_LOCATION); + context.registerSingleton("testBean", QualifiedByFieldNameTestBean.class); + context.refresh(); + QualifiedByFieldNameTestBean testBean = (QualifiedByFieldNameTestBean) context.getBean("testBean"); + Person person = testBean.getLarry(); + assertEquals("LarryBean", person.getName()); + } + + @Test + public void testQualifiedByParameterName() { + StaticApplicationContext context = new StaticApplicationContext(); + BeanDefinitionReader reader = new XmlBeanDefinitionReader(context); + reader.loadBeanDefinitions(CONFIG_LOCATION); + context.registerSingleton("testBean", QualifiedByParameterNameTestBean.class); + context.refresh(); + QualifiedByParameterNameTestBean testBean = (QualifiedByParameterNameTestBean) context.getBean("testBean"); + Person person = testBean.getLarry(); + assertEquals("LarryBean", person.getName()); + } + @Test public void testQualifiedByAlias() { StaticApplicationContext context = new StaticApplicationContext(); @@ -203,6 +227,32 @@ public final class QualifierAnnotationTests { } + private static class QualifiedByFieldNameTestBean { + + @Autowired + private Person larryBean; + + public Person getLarry() { + return larryBean; + } + } + + + private static class QualifiedByParameterNameTestBean { + + private Person larryBean; + + @Autowired + public void setLarryBean(Person larryBean) { + this.larryBean = larryBean; + } + + public Person getLarry() { + return larryBean; + } + } + + private static class QualifiedByAliasTestBean { @Autowired @Qualifier("stooge") 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 e826679971..69ccfc2f95 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,6 +16,8 @@ package org.springframework.context.expression; +import java.util.Properties; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import static org.junit.Assert.*; @@ -26,6 +28,7 @@ import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.annotation.Autowired; 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.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -70,6 +73,12 @@ public class ApplicationContextExpressionTests { } }); + PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer(); + Properties placeholders = new Properties(); + placeholders.setProperty("code", "123"); + ppc.setProperties(placeholders); + ac.addBeanFactoryPostProcessor(ppc); + GenericBeanDefinition bd0 = new GenericBeanDefinition(); bd0.setBeanClass(TestBean.class); bd0.getPropertyValues().addPropertyValue("name", "myName"); @@ -88,7 +97,7 @@ public class ApplicationContextExpressionTests { bd2.setScope("myScope"); bd2.getPropertyValues().addPropertyValue("name", "{ XXX#{tb0.name}YYY#{mySpecialAttr}ZZZ }"); bd2.getPropertyValues().addPropertyValue("age", "#{mySpecialAttr}"); - bd2.getPropertyValues().addPropertyValue("country", "#{systemProperties.country}"); + bd2.getPropertyValues().addPropertyValue("country", "${code} #{systemProperties.country}"); ac.registerBeanDefinition("tb2", bd2); GenericBeanDefinition bd3 = new GenericBeanDefinition(); @@ -124,30 +133,30 @@ public class ApplicationContextExpressionTests { TestBean tb2 = ac.getBean("tb2", TestBean.class); assertEquals("{ XXXmyNameYYY42ZZZ }", tb2.getName()); assertEquals(42, tb2.getAge()); - assertEquals("UK", tb2.getCountry()); + assertEquals("123 UK", tb2.getCountry()); ValueTestBean tb3 = ac.getBean("tb3", ValueTestBean.class); assertEquals("XXXmyNameYYY42ZZZ", tb3.name); assertEquals(42, tb3.age); - assertEquals("UK", tb3.country); + assertEquals("123 UK", tb3.country); assertSame(tb0, tb3.tb); ConstructorValueTestBean tb4 = ac.getBean("tb4", ConstructorValueTestBean.class); assertEquals("XXXmyNameYYY42ZZZ", tb4.name); assertEquals(42, tb4.age); - assertEquals("UK", tb4.country); + assertEquals("123 UK", tb4.country); assertSame(tb0, tb4.tb); MethodValueTestBean tb5 = ac.getBean("tb5", MethodValueTestBean.class); assertEquals("XXXmyNameYYY42ZZZ", tb5.name); assertEquals(42, tb5.age); - assertEquals("UK", tb5.country); + assertEquals("123 UK", tb5.country); assertSame(tb0, tb5.tb); PropertyValueTestBean tb6 = ac.getBean("tb6", PropertyValueTestBean.class); assertEquals("XXXmyNameYYY42ZZZ", tb6.name); assertEquals(42, tb6.age); - assertEquals("UK", tb6.country); + assertEquals("123 UK", tb6.country); assertSame(tb0, tb6.tb); } finally { @@ -197,7 +206,7 @@ public class ApplicationContextExpressionTests { @Autowired @Value("#{mySpecialAttr}") public int age; - @Value("#{systemProperties.country}") + @Value("${code} #{systemProperties.country}") public String country; @Qualifier("original") @@ -218,9 +227,9 @@ public class ApplicationContextExpressionTests { @Autowired public ConstructorValueTestBean( @Value("XXX#{tb0.name}YYY#{mySpecialAttr}ZZZ") String name, - @Value("#{mySpecialAttr}")int age, + @Value("#{mySpecialAttr}") int age, @Qualifier("original") TestBean tb, - @Value("#{systemProperties.country}") String country) { + @Value("${code} #{systemProperties.country}") String country) { this.name = name; this.age = age; this.country = country; @@ -244,7 +253,7 @@ public class ApplicationContextExpressionTests { @Qualifier("original") TestBean tb, @Value("XXX#{tb0.name}YYY#{mySpecialAttr}ZZZ") String name, @Value("#{mySpecialAttr}") int age, - @Value("#{systemProperties.country}") String country) { + @Value("${code} #{systemProperties.country}") String country) { this.name = name; this.age = age; this.country = country; @@ -273,7 +282,7 @@ public class ApplicationContextExpressionTests { this.age = age; } - @Value("#{systemProperties.country}") + @Value("${code} #{systemProperties.country}") public void setCountry(String country) { this.country = country; } diff --git a/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java b/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java index 13c9841127..04a96f440a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java @@ -148,6 +148,13 @@ public class MethodParameter { return this.constructor; } + /** + * Return the class that declares the underlying Method or Constructor. + */ + public Class getDeclaringClass() { + return (this.method != null ? this.method.getDeclaringClass() : this.constructor.getDeclaringClass()); + } + /** * Return the index of the method/constructor parameter. * @return the parameter index (never negative)