From 4344832a479831e7e67de1a9e81d52d023cd59ac Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 23 Mar 2009 14:13:41 +0000 Subject: [PATCH] qualifier annotations and @Value can be used at method level as well (applying to all parameters); fixed EL evaluation of prepared constructor arguments for repeated prototype creation --- ...erAnnotationAutowireCandidateResolver.java | 152 ++++++++++++------ .../beans/factory/annotation/Value.java | 4 +- .../factory/support/ConstructorResolver.java | 81 +++++----- .../ApplicationContextExpressionTests.java | 49 +++++- .../springframework/core/MethodParameter.java | 13 +- 5 files changed, 204 insertions(+), 95 deletions(-) diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java index 6b1cbc3ed6..0f62ce5b4e 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.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. @@ -22,11 +22,13 @@ import java.util.Map; import java.util.Set; import org.springframework.beans.SimpleTypeConverter; +import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.AutowireCandidateResolver; +import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -111,7 +113,7 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan /** - * Determine if the provided bean definition is an autowire candidate. + * Determine whether the provided bean definition is an autowire candidate. *

To be considered a candidate the bean's autowire-candidate * attribute must not have been set to 'false'. Also if an annotation on * the field or parameter to be autowired is recognized by this bean factory @@ -120,68 +122,38 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan * the same qualifier or match by meta attributes. A "value" attribute will * fallback to match against the bean name or an alias if a qualifier or * attribute does not match. + * @see Qualifier */ public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { if (!bdHolder.getBeanDefinition().isAutowireCandidate()) { // if explicitly false, do not proceed with qualifier check return false; } - if (descriptor == null || ObjectUtils.isEmpty(descriptor.getAnnotations())) { + if (descriptor == null) { // no qualification necessary return true; } - AbstractBeanDefinition bd = (AbstractBeanDefinition) bdHolder.getBeanDefinition(); + boolean match = checkQualifiers(bdHolder, descriptor.getAnnotations()); + if (match && descriptor.getMethodParameter() != null) { + match = checkQualifiers(bdHolder, descriptor.getMethodParameter().getAnnotations()); + } + return match; + } + + /** + * Match the given qualifier annotations against the candidate bean definition. + */ + protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) { + if (ObjectUtils.isEmpty(annotationsToSearch)) { + return true; + } SimpleTypeConverter typeConverter = new SimpleTypeConverter(); - Annotation[] annotations = descriptor.getAnnotations(); - for (Annotation annotation : annotations) { + for (Annotation annotation : annotationsToSearch) { Class type = annotation.annotationType(); if (isQualifier(type)) { - AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName()); - if (qualifier == null) { - qualifier = bd.getQualifier(ClassUtils.getShortName(type)); - } - if (qualifier == null && bd.hasBeanClass()) { - // look for matching annotation on the target class - Class beanClass = bd.getBeanClass(); - Annotation targetAnnotation = beanClass.getAnnotation(type); - if (targetAnnotation != null && targetAnnotation.equals(annotation)) { - return true; - } - } - Map attributes = AnnotationUtils.getAnnotationAttributes(annotation); - if (attributes.isEmpty() && qualifier == null) { - // if no attributes, the qualifier must be present + if (!checkQualifier(bdHolder, annotation, typeConverter)) { return false; } - for (Map.Entry entry : attributes.entrySet()) { - String attributeName = entry.getKey(); - Object expectedValue = entry.getValue(); - Object actualValue = null; - // check qualifier first - if (qualifier != null) { - actualValue = qualifier.getAttribute(attributeName); - } - if (actualValue == null) { - // fall back on bean definition attribute - actualValue = bd.getAttribute(attributeName); - } - if (actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) && - (expectedValue.equals(bdHolder.getBeanName()) || - ObjectUtils.containsElement(bdHolder.getAliases(), expectedValue))) { - // fall back on bean name (or alias) match - continue; - } - if (actualValue == null && qualifier != null) { - // fall back on default, but only if the qualifier is present - actualValue = AnnotationUtils.getDefaultValue(annotation, attributeName); - } - if (actualValue != null) { - actualValue = typeConverter.convertIfNecessary(actualValue, expectedValue.getClass()); - } - if (!expectedValue.equals(actualValue)) { - return false; - } - } } } return true; @@ -190,7 +162,7 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan /** * Checks whether the given annotation type is a recognized qualifier type. */ - private boolean isQualifier(Class annotationType) { + protected boolean isQualifier(Class annotationType) { for (Class qualifierType : this.qualifierTypes) { if (annotationType.equals(qualifierType) || annotationType.isAnnotationPresent(qualifierType)) { return true; @@ -199,8 +171,84 @@ public class QualifierAnnotationAutowireCandidateResolver implements AutowireCan return false; } + /** + * Match the given qualifier annotation against the candidate bean definition. + */ + protected boolean checkQualifier( + BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) { + + Class type = annotation.annotationType(); + AbstractBeanDefinition bd = (AbstractBeanDefinition) bdHolder.getBeanDefinition(); + AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName()); + if (qualifier == null) { + qualifier = bd.getQualifier(ClassUtils.getShortName(type)); + } + if (qualifier == null && bd.hasBeanClass()) { + // look for matching annotation on the target class + Class beanClass = bd.getBeanClass(); + Annotation targetAnnotation = beanClass.getAnnotation(type); + if (targetAnnotation != null && targetAnnotation.equals(annotation)) { + return true; + } + } + Map attributes = AnnotationUtils.getAnnotationAttributes(annotation); + if (attributes.isEmpty() && qualifier == null) { + // if no attributes, the qualifier must be present + return false; + } + for (Map.Entry entry : attributes.entrySet()) { + String attributeName = entry.getKey(); + Object expectedValue = entry.getValue(); + Object actualValue = null; + // check qualifier first + if (qualifier != null) { + actualValue = qualifier.getAttribute(attributeName); + } + if (actualValue == null) { + // fall back on bean definition attribute + actualValue = bd.getAttribute(attributeName); + } + if (actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) && + (expectedValue.equals(bdHolder.getBeanName()) || + ObjectUtils.containsElement(bdHolder.getAliases(), expectedValue))) { + // fall back on bean name (or alias) match + continue; + } + if (actualValue == null && qualifier != null) { + // fall back on default, but only if the qualifier is present + actualValue = AnnotationUtils.getDefaultValue(annotation, attributeName); + } + if (actualValue != null) { + actualValue = typeConverter.convertIfNecessary(actualValue, expectedValue.getClass()); + } + if (!expectedValue.equals(actualValue)) { + return false; + } + } + return true; + } + + + /** + * Determine whether the given dependency carries a value annotation. + * @see Value + */ public Object getSuggestedValue(DependencyDescriptor descriptor) { - for (Annotation annotation : descriptor.getAnnotations()) { + Object value = findValue(descriptor.getAnnotations()); + if (value == null) { + MethodParameter methodParam = descriptor.getMethodParameter(); + if (methodParam != null) { + value = findValue(methodParam.getAnnotations()); + } + } + return value; + } + + /** + * Determine a suggested value from any of the given candidate annotations. + */ + protected Object findValue(Annotation[] annotationsToSearch) { + for (Annotation annotation : annotationsToSearch) { if (this.valueAnnotationType.isInstance(annotation)) { Object value = AnnotationUtils.getValue(annotation); if (value == null) { diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Value.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Value.java index da290eccaf..b411c0330d 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Value.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/annotation/Value.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. @@ -34,7 +34,7 @@ import java.lang.annotation.Target; * @see org.springframework.beans.factory.support.AutowireCandidateResolver#getSuggestedValue */ @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) public @interface Value { /** diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index 262a3b08be..303ff0fcb0 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -17,6 +17,7 @@ package org.springframework.beans.factory.support; import java.lang.reflect.Constructor; +import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashSet; @@ -120,24 +121,7 @@ class ConstructorResolver { // Found a cached constructor... argsToUse = mbd.resolvedConstructorArguments; if (argsToUse == null) { - Class[] paramTypes = constructorToUse.getParameterTypes(); - Object[] argsToResolve = mbd.preparedConstructorArguments; - TypeConverter converter = (this.typeConverter != null ? this.typeConverter : bw); - BeanDefinitionValueResolver valueResolver = - new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); - argsToUse = new Object[argsToResolve.length]; - for (int i = 0; i < argsToResolve.length; i++) { - Object argValue = argsToResolve[i]; - MethodParameter methodParam = new MethodParameter(constructorToUse, i); - GenericTypeResolver.resolveParameterType(methodParam, constructorToUse.getDeclaringClass()); - if (argValue instanceof AutowiredArgumentMarker) { - argValue = resolveAutowiredArgument(methodParam, beanName, null, converter); - } - else if (argValue instanceof BeanMetadataElement) { - argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue); - } - argsToUse[i] = converter.convertIfNecessary(argValue, paramTypes[i], methodParam); - } + argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse); } } } @@ -317,24 +301,7 @@ class ConstructorResolver { // Found a cached factory method... argsToUse = mbd.resolvedConstructorArguments; if (argsToUse == null) { - Class[] paramTypes = factoryMethodToUse.getParameterTypes(); - Object[] argsToResolve = mbd.preparedConstructorArguments; - TypeConverter converter = (this.typeConverter != null ? this.typeConverter : bw); - BeanDefinitionValueResolver valueResolver = - new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); - argsToUse = new Object[argsToResolve.length]; - for (int i = 0; i < argsToResolve.length; i++) { - Object argValue = argsToResolve[i]; - MethodParameter methodParam = new MethodParameter(factoryMethodToUse, i); - GenericTypeResolver.resolveParameterType(methodParam, factoryClass); - if (argValue instanceof AutowiredArgumentMarker) { - argValue = resolveAutowiredArgument(methodParam, beanName, null, converter); - } - else if (argValue instanceof BeanMetadataElement) { - argValue = valueResolver.resolveValueIfNecessary("factory method argument", argValue); - } - argsToUse[i] = converter.convertIfNecessary(argValue, paramTypes[i], methodParam); - } + argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse); } } } @@ -615,6 +582,48 @@ class ConstructorResolver { return args; } + /** + * Resolve the prepared arguments stored in the given bean definition. + */ + private Object[] resolvePreparedArguments( + String beanName, RootBeanDefinition mbd, BeanWrapper bw, Member methodOrCtor) { + + Class[] paramTypes = (methodOrCtor instanceof Method ? + ((Method) methodOrCtor).getParameterTypes() : ((Constructor) methodOrCtor).getParameterTypes()); + Object[] argsToResolve = mbd.preparedConstructorArguments; + TypeConverter converter = (this.typeConverter != null ? this.typeConverter : bw); + BeanDefinitionValueResolver valueResolver = + new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); + Object[] resolvedArgs = new Object[argsToResolve.length]; + for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) { + Object argValue = argsToResolve[argIndex]; + MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, argIndex); + GenericTypeResolver.resolveParameterType(methodParam, methodOrCtor.getDeclaringClass()); + if (argValue instanceof AutowiredArgumentMarker) { + argValue = resolveAutowiredArgument(methodParam, beanName, null, converter); + } + else if (argValue instanceof BeanMetadataElement) { + argValue = valueResolver.resolveValueIfNecessary("constructor argument", argValue); + } + else if (argValue instanceof String) { + argValue = this.beanFactory.evaluateBeanDefinitionString((String) argValue, mbd); + } + Class paramType = paramTypes[argIndex]; + try { + resolvedArgs[argIndex] = converter.convertIfNecessary(argValue, paramType, methodParam); + } + catch (TypeMismatchException ex) { + String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method"); + throw new UnsatisfiedDependencyException( + mbd.getResourceDescription(), beanName, argIndex, paramType, + "Could not convert " + methodType + " argument value of type [" + + ObjectUtils.nullSafeClassName(argValue) + + "] to required type [" + paramType.getName() + "]: " + ex.getMessage()); + } + } + return resolvedArgs; + } + /** * Template method for resolving the specified argument which is supposed to be autowired. */ 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 6faf23bd96..e826679971 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 @@ -106,6 +106,11 @@ public class ApplicationContextExpressionTests { bd5.setScope("myScope"); ac.registerBeanDefinition("tb5", bd5); + GenericBeanDefinition bd6 = new GenericBeanDefinition(); + bd6.setBeanClass(PropertyValueTestBean.class); + bd6.setScope("myScope"); + ac.registerBeanDefinition("tb6", bd6); + System.getProperties().put("country", "UK"); try { ac.refresh(); @@ -138,6 +143,12 @@ public class ApplicationContextExpressionTests { assertEquals(42, tb5.age); assertEquals("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); + assertSame(tb0, tb6.tb); } finally { System.getProperties().remove("country"); @@ -153,12 +164,13 @@ public class ApplicationContextExpressionTests { GenericApplicationContext ac = new GenericApplicationContext(); RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class); rbd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); - rbd.getPropertyValues().addPropertyValue("name", "juergen"); + rbd.getConstructorArgumentValues().addGenericArgumentValue("#{systemProperties.name}"); rbd.getPropertyValues().addPropertyValue("country", "#{systemProperties.country}"); ac.registerBeanDefinition("test", rbd); ac.refresh(); StopWatch sw = new StopWatch(); sw.start("prototype"); + System.getProperties().put("name", "juergen"); System.getProperties().put("country", "UK"); try { for (int i = 0; i < 100000; i++) { @@ -170,6 +182,7 @@ public class ApplicationContextExpressionTests { } finally { System.getProperties().remove("country"); + System.getProperties().remove("name"); } System.out.println(sw.getTotalTimeMillis()); assertTrue("Prototype creation took too long: " + sw.getTotalTimeMillis(), sw.getTotalTimeMillis() < 6000); @@ -230,7 +243,7 @@ public class ApplicationContextExpressionTests { public void configure( @Qualifier("original") TestBean tb, @Value("XXX#{tb0.name}YYY#{mySpecialAttr}ZZZ") String name, - @Value("#{mySpecialAttr}")int age, + @Value("#{mySpecialAttr}") int age, @Value("#{systemProperties.country}") String country) { this.name = name; this.age = age; @@ -239,4 +252,36 @@ public class ApplicationContextExpressionTests { } } + + public static class PropertyValueTestBean { + + public String name; + + public int age; + + public String country; + + public TestBean tb; + + @Value("XXX#{tb0.name}YYY#{mySpecialAttr}ZZZ") + public void setName(String name) { + this.name = name; + } + + @Value("#{mySpecialAttr}") + public void setAge(int age) { + this.age = age; + } + + @Value("#{systemProperties.country}") + public void setCountry(String country) { + this.country = country; + } + + @Qualifier("original") + public void setTb(TestBean tb) { + this.tb = tb; + } + } + } 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 17607e6ec2..13c9841127 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 @@ -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. @@ -19,8 +19,8 @@ package org.springframework.core; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; -import java.lang.reflect.TypeVariable; import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.HashMap; import java.util.Map; @@ -177,7 +177,14 @@ public class MethodParameter { } /** - * Return the annotations associated with the method/constructor parameter. + * Return the annotations associated with the target method/constructor itself. + */ + public Annotation[] getAnnotations() { + return (this.method != null ? this.method.getAnnotations() : this.constructor.getAnnotations()); + } + + /** + * Return the annotations associated with the specific method/constructor parameter. */ public Annotation[] getParameterAnnotations() { if (this.parameterAnnotations == null) {