diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java index f0c2fa1dae..dc60afb462 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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. @@ -63,7 +63,7 @@ public class BeanCreationException extends FatalBeanException { * @param msg the detail message */ public BeanCreationException(String beanName, String msg) { - super("Error creating bean with name '" + beanName + "': " + msg); + super("Error creating bean" + (beanName != null ? " with name '" + beanName + "'" : "") + ": " + msg); this.beanName = beanName; } @@ -86,7 +86,7 @@ public class BeanCreationException extends FatalBeanException { * @param msg the detail message */ public BeanCreationException(String resourceDescription, String beanName, String msg) { - super("Error creating bean with name '" + beanName + "'" + + super("Error creating bean" + (beanName != null ? " with name '" + beanName + "'" : "") + (resourceDescription != null ? " defined in " + resourceDescription : "") + ": " + msg); this.resourceDescription = resourceDescription; this.beanName = beanName; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java index c52d0ae766..5c5ed79cfa 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2016 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. @@ -23,8 +23,8 @@ import org.springframework.beans.FatalBeanException; * factory-aware initialization code fails. BeansExceptions thrown by * bean factory methods themselves should simply be propagated as-is. * - *

Note that non-factory-aware initialization methods like afterPropertiesSet() - * or a custom "init-method" can throw any exception. + *

Note that {@code afterPropertiesSet()} or a custom "init-method" + * can throw any exception. * * @author Juergen Hoeller * @since 13.11.2003 diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java b/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java new file mode 100644 index 0000000000..0a3d55a931 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java @@ -0,0 +1,167 @@ +/* + * Copyright 2002-2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Member; + +import org.springframework.core.MethodParameter; +import org.springframework.util.Assert; + +/** + * A simple descriptor for an injection point, pointing to a method/constructor + * parameter or a field. Exposed by {@link UnsatisfiedDependencyException}. + * + * @author Juergen Hoeller + * @since 4.3 + * @see UnsatisfiedDependencyException#getInjectionPoint() + * @see org.springframework.beans.factory.config.DependencyDescriptor + */ +public class InjectionPoint { + + protected MethodParameter methodParameter; + + protected Field field; + + private volatile Annotation[] fieldAnnotations; + + + /** + * Create an injection point descriptor for a method or constructor parameter. + * @param methodParameter the MethodParameter to wrap + */ + public InjectionPoint(MethodParameter methodParameter) { + Assert.notNull(methodParameter, "MethodParameter must not be null"); + this.methodParameter = methodParameter; + } + + /** + * Create an injection point descriptor for a field. + * @param field the field to wrap + */ + public InjectionPoint(Field field) { + Assert.notNull(field, "Field must not be null"); + this.field = field; + } + + /** + * Copy constructor. + * @param original the original descriptor to create a copy from + */ + protected InjectionPoint(InjectionPoint original) { + this.methodParameter = (original.methodParameter != null ? + new MethodParameter(original.methodParameter) : null); + this.field = original.field; + this.fieldAnnotations = original.fieldAnnotations; + } + + /** + * Just available for serialization purposes in subclasses. + */ + protected InjectionPoint() { + } + + + /** + * Return the wrapped MethodParameter, if any. + *

Note: Either MethodParameter or Field is available. + * @return the MethodParameter, or {@code null} if none + */ + public MethodParameter getMethodParameter() { + return this.methodParameter; + } + + /** + * Return the wrapped Field, if any. + *

Note: Either MethodParameter or Field is available. + * @return the Field, or {@code null} if none + */ + public Field getField() { + return this.field; + } + + /** + * Obtain the annotations associated with the wrapped field or method/constructor parameter. + */ + public Annotation[] getAnnotations() { + if (this.field != null) { + if (this.fieldAnnotations == null) { + this.fieldAnnotations = this.field.getAnnotations(); + } + return this.fieldAnnotations; + } + else { + return this.methodParameter.getParameterAnnotations(); + } + } + + /** + * Return the type declared by the underlying field or method/constructor parameter, + * indicating the injection type. + */ + public Class getDeclaredType() { + return (this.field != null ? this.field.getType() : this.methodParameter.getParameterType()); + } + + /** + * Returns the wrapped member, containing the injection point. + * @return the Field / Method / Constructor as Member + */ + public Member getMember() { + return (this.field != null ? this.field : this.methodParameter.getMember()); + } + + /** + * Return the wrapped annotated element. + *

Note: In case of a method/constructor parameter, this exposes + * the annotations declared on the method or constructor itself + * (i.e. at the method/constructor level, not at the parameter level). + * Use {@link #getAnnotations()} to obtain parameter-level annotations in + * such a scenario, transparently with corresponding field annotations. + * @return the Field / Method / Constructor as AnnotatedElement + */ + public AnnotatedElement getAnnotatedElement() { + return (this.field != null ? this.field : this.methodParameter.getAnnotatedElement()); + } + + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (getClass() != other.getClass()) { + return false; + } + InjectionPoint otherPoint = (InjectionPoint) other; + return (this.field != null ? this.field.equals(otherPoint.field) : + this.methodParameter.equals(otherPoint.methodParameter)); + } + + @Override + public int hashCode() { + return (this.field != null ? this.field.hashCode() : this.methodParameter.hashCode()); + } + + @Override + public String toString() { + return (this.field != null ? "field '" + this.field.getName() + "'" : this.methodParameter.toString()); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java b/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java index 9ec35a7e63..0403abfc7a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java @@ -31,6 +31,9 @@ import org.springframework.util.ClassUtils; @SuppressWarnings("serial") public class UnsatisfiedDependencyException extends BeanCreationException { + private InjectionPoint injectionPoint; + + /** * Create a new UnsatisfiedDependencyException. * @param resourceDescription description of the resource that the bean definition came from @@ -60,6 +63,36 @@ public class UnsatisfiedDependencyException extends BeanCreationException { initCause(ex); } + /** + * Create a new UnsatisfiedDependencyException. + * @param resourceDescription description of the resource that the bean definition came from + * @param beanName the name of the bean requested + * @param injectionPoint the injection point (field or method/constructor parameter) + * @param msg the detail message + * @since 4.3 + */ + public UnsatisfiedDependencyException( + String resourceDescription, String beanName, InjectionPoint injectionPoint, String msg) { + + super(resourceDescription, beanName, "Unsatisfied dependency expressed through " + injectionPoint + ": " + msg); + this.injectionPoint = injectionPoint; + } + + /** + * Create a new UnsatisfiedDependencyException. + * @param resourceDescription description of the resource that the bean definition came from + * @param beanName the name of the bean requested + * @param injectionPoint the injection point (field or method/constructor parameter) + * @param ex the bean creation exception that indicated the unsatisfied dependency + * @since 4.3 + */ + public UnsatisfiedDependencyException( + String resourceDescription, String beanName, InjectionPoint injectionPoint, BeansException ex) { + + this(resourceDescription, beanName, injectionPoint, (ex != null ? ex.getMessage() : "")); + initCause(ex); + } + /** * Create a new UnsatisfiedDependencyException. * @param resourceDescription description of the resource that the bean definition came from @@ -67,7 +100,9 @@ public class UnsatisfiedDependencyException extends BeanCreationException { * @param ctorArgIndex the index of the constructor argument that couldn't be satisfied * @param ctorArgType the type of the constructor argument that couldn't be satisfied * @param msg the detail message + * @deprecated in favor of {@link #UnsatisfiedDependencyException(String, String, InjectionPoint, String)} */ + @Deprecated public UnsatisfiedDependencyException( String resourceDescription, String beanName, int ctorArgIndex, Class ctorArgType, String msg) { @@ -84,7 +119,9 @@ public class UnsatisfiedDependencyException extends BeanCreationException { * @param ctorArgIndex the index of the constructor argument that couldn't be satisfied * @param ctorArgType the type of the constructor argument that couldn't be satisfied * @param ex the bean creation exception that indicated the unsatisfied dependency + * @deprecated in favor of {@link #UnsatisfiedDependencyException(String, String, InjectionPoint, BeansException)} */ + @Deprecated public UnsatisfiedDependencyException( String resourceDescription, String beanName, int ctorArgIndex, Class ctorArgType, BeansException ex) { @@ -92,4 +129,13 @@ public class UnsatisfiedDependencyException extends BeanCreationException { initCause(ex); } + + /** + * Return the injection point (field or method/constructor parameter), if known. + * @since 4.3 + */ + public InjectionPoint getInjectionPoint() { + return this.injectionPoint; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index 90427f18ce..10a0dc76fc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -45,7 +45,9 @@ import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; @@ -347,6 +349,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean try { metadata.inject(bean, beanName, pvs); } + catch (BeanCreationException ex) { + throw ex; + } catch (Throwable ex) { throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex); } @@ -365,6 +370,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean try { metadata.inject(bean, null, null); } + catch (BeanCreationException ex) { + throw ex; + } catch (Throwable ex) { throw new BeanCreationException("Injection of autowired dependencies failed for class [" + clazz + "]", ex); } @@ -549,45 +557,45 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean @Override protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { Field field = (Field) this.member; - try { - Object value; - if (this.cached) { - value = resolvedCachedArgument(beanName, this.cachedFieldValue); - } - else { - DependencyDescriptor desc = new DependencyDescriptor(field, this.required); - desc.setContainingClass(bean.getClass()); - Set autowiredBeanNames = new LinkedHashSet(1); - TypeConverter typeConverter = beanFactory.getTypeConverter(); + Object value; + if (this.cached) { + value = resolvedCachedArgument(beanName, this.cachedFieldValue); + } + else { + DependencyDescriptor desc = new DependencyDescriptor(field, this.required); + desc.setContainingClass(bean.getClass()); + Set autowiredBeanNames = new LinkedHashSet(1); + TypeConverter typeConverter = beanFactory.getTypeConverter(); + try { value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); - synchronized (this) { - if (!this.cached) { - if (value != null || this.required) { - this.cachedFieldValue = desc; - registerDependentBeans(beanName, autowiredBeanNames); - if (autowiredBeanNames.size() == 1) { - String autowiredBeanName = autowiredBeanNames.iterator().next(); - if (beanFactory.containsBean(autowiredBeanName)) { - if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { - this.cachedFieldValue = new RuntimeBeanReference(autowiredBeanName); - } + } + catch (BeansException ex) { + throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); + } + synchronized (this) { + if (!this.cached) { + if (value != null || this.required) { + this.cachedFieldValue = desc; + registerDependentBeans(beanName, autowiredBeanNames); + if (autowiredBeanNames.size() == 1) { + String autowiredBeanName = autowiredBeanNames.iterator().next(); + if (beanFactory.containsBean(autowiredBeanName)) { + if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { + this.cachedFieldValue = new RuntimeBeanReference(autowiredBeanName); } } } - else { - this.cachedFieldValue = null; - } - this.cached = true; } + else { + this.cachedFieldValue = null; + } + this.cached = true; } } - if (value != null) { - ReflectionUtils.makeAccessible(field); - field.set(bean, value); - } } - catch (Throwable ex) { - throw new BeanCreationException("Could not autowire field: " + field, ex); + if (value != null) { + ReflectionUtils.makeAccessible(field); + field.set(bean, value); } } } @@ -615,67 +623,69 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean return; } Method method = (Method) this.member; - try { - Object[] arguments; - if (this.cached) { - // Shortcut for avoiding synchronization... - arguments = resolveCachedArguments(beanName); - } - else { - Class[] paramTypes = method.getParameterTypes(); - arguments = new Object[paramTypes.length]; - DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length]; - Set autowiredBeanNames = new LinkedHashSet(paramTypes.length); - TypeConverter typeConverter = beanFactory.getTypeConverter(); - for (int i = 0; i < arguments.length; i++) { - MethodParameter methodParam = new MethodParameter(method, i); - DependencyDescriptor desc = new DependencyDescriptor(methodParam, this.required); - desc.setContainingClass(bean.getClass()); - descriptors[i] = desc; - Object arg = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); + Object[] arguments; + if (this.cached) { + // Shortcut for avoiding synchronization... + arguments = resolveCachedArguments(beanName); + } + else { + Class[] paramTypes = method.getParameterTypes(); + arguments = new Object[paramTypes.length]; + DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length]; + Set autowiredBeanNames = new LinkedHashSet(paramTypes.length); + TypeConverter typeConverter = beanFactory.getTypeConverter(); + for (int i = 0; i < arguments.length; i++) { + MethodParameter methodParam = new MethodParameter(method, i); + DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required); + currDesc.setContainingClass(bean.getClass()); + descriptors[i] = currDesc; + try { + Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeanNames, typeConverter); if (arg == null && !this.required) { arguments = null; break; } arguments[i] = arg; } - synchronized (this) { - if (!this.cached) { - if (arguments != null) { - this.cachedMethodArguments = new Object[arguments.length]; - for (int i = 0; i < arguments.length; i++) { - this.cachedMethodArguments[i] = descriptors[i]; - } - registerDependentBeans(beanName, autowiredBeanNames); - if (autowiredBeanNames.size() == paramTypes.length) { - Iterator it = autowiredBeanNames.iterator(); - for (int i = 0; i < paramTypes.length; i++) { - String autowiredBeanName = it.next(); - if (beanFactory.containsBean(autowiredBeanName)) { - if (beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { - this.cachedMethodArguments[i] = new RuntimeBeanReference(autowiredBeanName); - } + catch (BeansException ex) { + throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex); + } + } + synchronized (this) { + if (!this.cached) { + if (arguments != null) { + this.cachedMethodArguments = new Object[paramTypes.length]; + for (int i = 0; i < arguments.length; i++) { + this.cachedMethodArguments[i] = descriptors[i]; + } + registerDependentBeans(beanName, autowiredBeanNames); + if (autowiredBeanNames.size() == paramTypes.length) { + Iterator it = autowiredBeanNames.iterator(); + for (int i = 0; i < paramTypes.length; i++) { + String autowiredBeanName = it.next(); + if (beanFactory.containsBean(autowiredBeanName)) { + if (beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { + this.cachedMethodArguments[i] = new RuntimeBeanReference(autowiredBeanName); } } } } - else { - this.cachedMethodArguments = null; - } - this.cached = true; } + else { + this.cachedMethodArguments = null; + } + this.cached = true; } } - if (arguments != null) { + } + if (arguments != null) { + try { ReflectionUtils.makeAccessible(method); method.invoke(bean, arguments); } - } - catch (InvocationTargetException ex) { - throw ex.getTargetException(); - } - catch (Throwable ex) { - throw new BeanCreationException("Could not autowire method: " + method, ex); + catch (InvocationTargetException ex){ + throw ex.getTargetException(); + } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index cadc893a1e..e5410e859c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -19,7 +19,6 @@ package org.springframework.beans.factory.config; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; -import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -27,13 +26,13 @@ import java.util.Map; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ResolvableType; -import org.springframework.util.Assert; /** * Descriptor for a specific dependency that is about to be injected. @@ -44,15 +43,9 @@ import org.springframework.util.Assert; * @since 2.5 */ @SuppressWarnings("serial") -public class DependencyDescriptor implements Serializable { +public class DependencyDescriptor extends InjectionPoint implements Serializable { - private transient MethodParameter methodParameter; - - private transient Field field; - - private Class declaringClass; - - private Class containingClass; + private final Class declaringClass; private String methodName; @@ -68,7 +61,7 @@ public class DependencyDescriptor implements Serializable { private int nestingLevel = 1; - private transient Annotation[] fieldAnnotations; + private Class containingClass; /** @@ -89,10 +82,8 @@ public class DependencyDescriptor implements Serializable { * eagerly resolving potential target beans for type matching */ public DependencyDescriptor(MethodParameter methodParameter, boolean required, boolean eager) { - Assert.notNull(methodParameter, "MethodParameter must not be null"); - this.methodParameter = methodParameter; + super(methodParameter); this.declaringClass = methodParameter.getDeclaringClass(); - this.containingClass = methodParameter.getContainingClass(); if (this.methodParameter.getMethod() != null) { this.methodName = methodParameter.getMethod().getName(); this.parameterTypes = methodParameter.getMethod().getParameterTypes(); @@ -101,6 +92,7 @@ public class DependencyDescriptor implements Serializable { this.parameterTypes = methodParameter.getConstructor().getParameterTypes(); } this.parameterIndex = methodParameter.getParameterIndex(); + this.containingClass = methodParameter.getContainingClass(); this.required = required; this.eager = eager; } @@ -123,8 +115,7 @@ public class DependencyDescriptor implements Serializable { * eagerly resolving potential target beans for type matching */ public DependencyDescriptor(Field field, boolean required, boolean eager) { - Assert.notNull(field, "Field must not be null"); - this.field = field; + super(field); this.declaringClass = field.getDeclaringClass(); this.fieldName = field.getName(); this.required = required; @@ -136,39 +127,19 @@ public class DependencyDescriptor implements Serializable { * @param original the original descriptor to create a copy from */ public DependencyDescriptor(DependencyDescriptor original) { - this.methodParameter = (original.methodParameter != null ? new MethodParameter(original.methodParameter) : null); - this.field = original.field; + super(original); this.declaringClass = original.declaringClass; - this.containingClass = original.containingClass; this.methodName = original.methodName; this.parameterTypes = original.parameterTypes; this.parameterIndex = original.parameterIndex; this.fieldName = original.fieldName; + this.containingClass = original.containingClass; this.required = original.required; this.eager = original.eager; this.nestingLevel = original.nestingLevel; - this.fieldAnnotations = original.fieldAnnotations; } - /** - * Return the wrapped MethodParameter, if any. - *

Note: Either MethodParameter or Field is available. - * @return the MethodParameter, or {@code null} if none - */ - public MethodParameter getMethodParameter() { - return this.methodParameter; - } - - /** - * Return the wrapped Field, if any. - *

Note: Either MethodParameter or Field is available. - * @return the Field, or {@code null} if none - */ - public Field getField() { - return this.field; - } - /** * Return whether this dependency is required. */ @@ -358,19 +329,18 @@ public class DependencyDescriptor implements Serializable { GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter)); } - /** - * Obtain the annotations associated with the wrapped parameter/field, if any. - */ - public Annotation[] getAnnotations() { - if (this.field != null) { - if (this.fieldAnnotations == null) { - this.fieldAnnotations = this.field.getAnnotations(); - } - return this.fieldAnnotations; + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; } - else { - return this.methodParameter.getParameterAnnotations(); + if (!super.equals(other)) { + return false; } + DependencyDescriptor otherDesc = (DependencyDescriptor) other; + return (this.required == otherDesc.required && this.eager == otherDesc.eager && + this.nestingLevel == otherDesc.nestingLevel && this.containingClass == otherDesc.containingClass); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index a0285be6a3..a3ae158217 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -40,6 +40,7 @@ import org.springframework.beans.TypeConverter; import org.springframework.beans.TypeMismatchException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; @@ -151,7 +152,7 @@ class ConstructorResolver { catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() + - "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); + "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); } } AutowireUtils.sortConstructors(candidates); @@ -159,8 +160,7 @@ class ConstructorResolver { Set> ambiguousConstructors = null; LinkedList causes = null; - for (int i = 0; i < candidates.length; i++) { - Constructor candidate = candidates[i]; + for (Constructor candidate : candidates) { Class[] paramTypes = candidate.getParameterTypes(); if (constructorToUse != null && argsToUse.length > paramTypes.length) { @@ -665,7 +665,6 @@ class ConstructorResolver { BeanWrapper bw, Class[] paramTypes, String[] paramNames, Object methodOrCtor, boolean autowiring) throws UnsatisfiedDependencyException { - String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method"); TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? this.beanFactory.getCustomTypeConverter() : bw); @@ -700,9 +699,9 @@ class ConstructorResolver { ConstructorArgumentValues.ValueHolder sourceHolder = (ConstructorArgumentValues.ValueHolder) valueHolder.getSource(); Object sourceValue = sourceHolder.getValue(); + MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex); try { - convertedValue = converter.convertIfNecessary(originalValue, paramType, - MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex)); + convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam); // TODO re-enable once race condition has been found (SPR-7423) /* if (originalValue == sourceValue || sourceValue instanceof TypedStringValue) { @@ -718,8 +717,8 @@ class ConstructorResolver { } catch (TypeMismatchException ex) { throw new UnsatisfiedDependencyException( - mbd.getResourceDescription(), beanName, paramIndex, paramType, - "Could not convert " + methodType + " argument value of type [" + + mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), + "Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(valueHolder.getValue()) + "] to required type [" + paramType.getName() + "]: " + ex.getMessage()); } @@ -728,17 +727,18 @@ class ConstructorResolver { args.rawArguments[paramIndex] = originalValue; } else { + MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex); // No explicit match found: we're either supposed to autowire or // have to fail creating an argument array for the given constructor. if (!autowiring) { throw new UnsatisfiedDependencyException( - mbd.getResourceDescription(), beanName, paramIndex, paramType, - "Ambiguous " + methodType + " argument types - " + - "did you specify the correct bean references as " + methodType + " arguments?"); + mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), + "Ambiguous argument values for parameter of type [" + paramType.getName() + + "] - did you specify the correct bean references as arguments?"); } try { - MethodParameter param = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex); - Object autowiredArgument = resolveAutowiredArgument(param, beanName, autowiredBeanNames, converter); + Object autowiredArgument = + resolveAutowiredArgument(methodParam, beanName, autowiredBeanNames, converter); args.rawArguments[paramIndex] = autowiredArgument; args.arguments[paramIndex] = autowiredArgument; args.preparedArguments[paramIndex] = new AutowiredArgumentMarker(); @@ -746,7 +746,7 @@ class ConstructorResolver { } catch (BeansException ex) { throw new UnsatisfiedDependencyException( - mbd.getResourceDescription(), beanName, paramIndex, paramType, ex); + mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), ex); } } } @@ -755,7 +755,8 @@ class ConstructorResolver { this.beanFactory.registerDependentBean(autowiredBeanName, beanName); if (this.beanFactory.logger.isDebugEnabled()) { this.beanFactory.logger.debug("Autowiring by type from bean name '" + beanName + - "' via " + methodType + " to bean named '" + autowiredBeanName + "'"); + "' via " + (methodOrCtor instanceof Constructor ? "constructor" : "factory method") + + " to bean named '" + autowiredBeanName + "'"); } } @@ -793,11 +794,9 @@ class ConstructorResolver { 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) + + mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), + "Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(argValue) + "] to required type [" + paramType.getName() + "]: " + ex.getMessage()); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java b/spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java index f61119bc40..32fa684760 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java @@ -1,3 +1,19 @@ +/* + * Copyright 2002-2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.beans.factory; import org.junit.Test; @@ -49,8 +65,7 @@ public class Spr5475Tests { cav.addIndexedArgumentValue(1, "bogusArg2".getBytes()); def.setConstructorArgumentValues(cav); - assertExceptionMessageForMisconfiguredFactoryMethod( - def, + assertExceptionMessageForMisconfiguredFactoryMethod(def, "Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(CharSequence,byte[])'. " + "Check that a method with the specified name and arguments exists and that it is static."); } @@ -62,7 +77,8 @@ public class Spr5475Tests { try { factory.preInstantiateSingletons(); fail("should have failed with BeanCreationException due to incorrectly invoked factory method"); - } catch (BeanCreationException ex) { + } + catch (BeanCreationException ex) { assertThat(ex.getMessage(), equalTo(expectedMessage)); } } @@ -72,15 +88,17 @@ public class Spr5475Tests { // calling a factory method that accepts arguments without any arguments emits an exception unlike cases // where a no-arg factory method is called with arguments. Adding this test just to document the difference assertExceptionMessageForMisconfiguredFactoryMethod( - rootBeanDefinition(Foo.class) - .setFactoryMethod("singleArgFactory").getBeanDefinition(), + rootBeanDefinition(Foo.class). + setFactoryMethod("singleArgFactory").getBeanDefinition(), "Error creating bean with name 'foo': " + - "Unsatisfied dependency expressed through constructor argument with index 0 of type [java.lang.String]: " + - "Ambiguous factory method argument types - did you specify the correct bean references as factory method arguments?"); + "Unsatisfied dependency expressed through method 'singleArgFactory' parameter 0: " + + "Ambiguous argument values for parameter of type [java.lang.String] - " + + "did you specify the correct bean references as arguments?"); } static class Foo { + static Foo noArgFactory() { return new Foo(); } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 928737e6ea..633791c303 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -83,6 +83,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { bf.registerBeanDefinition("testBean", new GenericBeanDefinition()); try { bf.getBean("testBean"); + fail("Should have thrown BeanCreationException"); } catch (BeanCreationException ex) { assertTrue(ex.getRootCause() instanceof IllegalStateException); @@ -635,6 +636,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { } catch (UnsatisfiedDependencyException ex) { // expected + assertSame(ConstructorWithoutFallbackBean.class, ex.getInjectionPoint().getMethodParameter().getDeclaringClass()); } } @@ -838,8 +840,9 @@ public class AutowiredAnnotationBeanPostProcessorTests { bf.getBean("annotatedBean"); fail("should have failed, more than one bean of type"); } - catch (BeanCreationException ex) { + catch (UnsatisfiedDependencyException ex) { // expected + assertSame(MapMethodInjectionBean.class, ex.getInjectionPoint().getMethodParameter().getDeclaringClass()); } bf.destroySingletons(); } @@ -1164,7 +1167,8 @@ public class AutowiredAnnotationBeanPostProcessorTests { TestBean tb = new TestBean(); bf.registerSingleton("testBean", tb); - CustomAnnotationRequiredFieldResourceInjectionBean bean = (CustomAnnotationRequiredFieldResourceInjectionBean) bf.getBean("customBean"); + CustomAnnotationRequiredFieldResourceInjectionBean bean = + (CustomAnnotationRequiredFieldResourceInjectionBean) bf.getBean("customBean"); assertSame(tb, bean.getTestBean()); bf.destroySingletons(); } @@ -1183,10 +1187,12 @@ public class AutowiredAnnotationBeanPostProcessorTests { try { bf.getBean("customBean"); - fail("expected BeanCreationException; no dependency available for required field"); + fail("Should have thrown UnsatisfiedDependencyException"); } - catch (BeanCreationException ex) { + catch (UnsatisfiedDependencyException ex) { // expected + assertSame(CustomAnnotationRequiredFieldResourceInjectionBean.class, + ex.getInjectionPoint().getField().getDeclaringClass()); } bf.destroySingletons(); } @@ -1209,10 +1215,13 @@ public class AutowiredAnnotationBeanPostProcessorTests { try { bf.getBean("customBean"); - fail("expected BeanCreationException; multiple beans of dependency type available"); + fail("Should have thrown UnsatisfiedDependencyException"); } - catch (BeanCreationException ex) { + catch (UnsatisfiedDependencyException ex) { // expected + ex.printStackTrace(); + assertSame(CustomAnnotationRequiredFieldResourceInjectionBean.class, + ex.getInjectionPoint().getField().getDeclaringClass()); } bf.destroySingletons(); } @@ -1231,7 +1240,8 @@ public class AutowiredAnnotationBeanPostProcessorTests { TestBean tb = new TestBean(); bf.registerSingleton("testBean", tb); - CustomAnnotationRequiredMethodResourceInjectionBean bean = (CustomAnnotationRequiredMethodResourceInjectionBean) bf.getBean("customBean"); + CustomAnnotationRequiredMethodResourceInjectionBean bean = + (CustomAnnotationRequiredMethodResourceInjectionBean) bf.getBean("customBean"); assertSame(tb, bean.getTestBean()); bf.destroySingletons(); } @@ -1250,10 +1260,12 @@ public class AutowiredAnnotationBeanPostProcessorTests { try { bf.getBean("customBean"); - fail("expected BeanCreationException; no dependency available for required method"); + fail("Should have thrown UnsatisfiedDependencyException"); } - catch (BeanCreationException e) { + catch (UnsatisfiedDependencyException ex) { // expected + assertSame(CustomAnnotationRequiredMethodResourceInjectionBean.class, + ex.getInjectionPoint().getMethodParameter().getDeclaringClass()); } bf.destroySingletons(); } @@ -1276,10 +1288,12 @@ public class AutowiredAnnotationBeanPostProcessorTests { try { bf.getBean("customBean"); - fail("expected BeanCreationException; multiple beans of dependency type available"); + fail("Should have thrown UnsatisfiedDependencyException"); } - catch (BeanCreationException ex) { + catch (UnsatisfiedDependencyException ex) { // expected + assertSame(CustomAnnotationRequiredMethodResourceInjectionBean.class, + ex.getInjectionPoint().getMethodParameter().getDeclaringClass()); } bf.destroySingletons(); } @@ -1298,7 +1312,8 @@ public class AutowiredAnnotationBeanPostProcessorTests { TestBean tb = new TestBean(); bf.registerSingleton("testBean", tb); - CustomAnnotationOptionalFieldResourceInjectionBean bean = (CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean"); + CustomAnnotationOptionalFieldResourceInjectionBean bean = + (CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean"); assertSame(tb, bean.getTestBean3()); assertNull(bean.getTestBean()); assertNull(bean.getTestBean2()); @@ -1317,7 +1332,8 @@ public class AutowiredAnnotationBeanPostProcessorTests { bf.registerBeanDefinition("customBean", new RootBeanDefinition( CustomAnnotationOptionalFieldResourceInjectionBean.class)); - CustomAnnotationOptionalFieldResourceInjectionBean bean = (CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean"); + CustomAnnotationOptionalFieldResourceInjectionBean bean = + (CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean"); assertNull(bean.getTestBean3()); assertNull(bean.getTestBean()); assertNull(bean.getTestBean2()); @@ -1342,10 +1358,12 @@ public class AutowiredAnnotationBeanPostProcessorTests { try { bf.getBean("customBean"); - fail("expected BeanCreationException; multiple beans of dependency type available"); + fail("Should have thrown UnsatisfiedDependencyException"); } - catch (BeanCreationException ex) { + catch (UnsatisfiedDependencyException ex) { // expected + assertSame(CustomAnnotationOptionalFieldResourceInjectionBean.class, + ex.getInjectionPoint().getField().getDeclaringClass()); } bf.destroySingletons(); } @@ -1364,7 +1382,8 @@ public class AutowiredAnnotationBeanPostProcessorTests { TestBean tb = new TestBean(); bf.registerSingleton("testBean", tb); - CustomAnnotationOptionalMethodResourceInjectionBean bean = (CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean"); + CustomAnnotationOptionalMethodResourceInjectionBean bean = + (CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean"); assertSame(tb, bean.getTestBean3()); assertNull(bean.getTestBean()); assertNull(bean.getTestBean2()); @@ -1383,7 +1402,8 @@ public class AutowiredAnnotationBeanPostProcessorTests { bf.registerBeanDefinition("customBean", new RootBeanDefinition( CustomAnnotationOptionalMethodResourceInjectionBean.class)); - CustomAnnotationOptionalMethodResourceInjectionBean bean = (CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean"); + CustomAnnotationOptionalMethodResourceInjectionBean bean = + (CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean"); assertNull(bean.getTestBean3()); assertNull(bean.getTestBean()); assertNull(bean.getTestBean2()); @@ -1408,10 +1428,12 @@ public class AutowiredAnnotationBeanPostProcessorTests { try { bf.getBean("customBean"); - fail("expected BeanCreationException; multiple beans of dependency type available"); + fail("Should have thrown UnsatisfiedDependencyException"); } - catch (BeanCreationException ex) { + catch (UnsatisfiedDependencyException ex) { // expected + assertSame(CustomAnnotationOptionalMethodResourceInjectionBean.class, + ex.getInjectionPoint().getMethodParameter().getDeclaringClass()); } bf.destroySingletons(); } @@ -2644,7 +2666,7 @@ public class AutowiredAnnotationBeanPostProcessorTests { @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) - public static @interface MyAutowired { + public @interface MyAutowired { boolean optional() default false; } diff --git a/spring-core/src/main/java/org/springframework/core/MethodParameter.java b/spring-core/src/main/java/org/springframework/core/MethodParameter.java index f3dbbfb14b..2338866dc2 100644 --- a/spring-core/src/main/java/org/springframework/core/MethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/MethodParameter.java @@ -180,7 +180,14 @@ public class MethodParameter { } /** - * Returns the wrapped member. + * Return the class that declares the underlying Method or Constructor. + */ + public Class getDeclaringClass() { + return getMember().getDeclaringClass(); + } + + /** + * Return the wrapped member. * @return the Method or Constructor as Member */ public Member getMember() { @@ -196,7 +203,9 @@ public class MethodParameter { } /** - * Returns the wrapped annotated element. + * Return the wrapped annotated element. + *

Note: This method exposes the annotations declared on the method/constructor + * itself (i.e. at the method/constructor level, not at the parameter level). * @return the Method or Constructor as AnnotatedElement */ public AnnotatedElement getAnnotatedElement() { @@ -211,13 +220,6 @@ public class MethodParameter { } } - /** - * Return the class that declares the underlying Method or Constructor. - */ - public Class getDeclaringClass() { - return getMember().getDeclaringClass(); - } - /** * Return the index of the method/constructor parameter. * @return the parameter index (-1 in case of the return type) @@ -577,6 +579,12 @@ public class MethodParameter { return (getMember().hashCode() * 31 + this.parameterIndex); } + @Override + public String toString() { + return (this.method != null ? "method '" + this.method.getName() + "'" : "constructor") + + " parameter " + this.parameterIndex; + } + @Override public MethodParameter clone() { return new MethodParameter(this);