Consistent UnsatisfiedDependencyException exposure with injection point metadata

Issue: SPR-13968
This commit is contained in:
Juergen Hoeller 2016-02-25 21:36:49 +01:00
parent 4c964473b1
commit b6dd8a9233
10 changed files with 428 additions and 188 deletions

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * @param msg the detail message
*/ */
public BeanCreationException(String beanName, String msg) { 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; this.beanName = beanName;
} }
@ -86,7 +86,7 @@ public class BeanCreationException extends FatalBeanException {
* @param msg the detail message * @param msg the detail message
*/ */
public BeanCreationException(String resourceDescription, String beanName, String msg) { 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); (resourceDescription != null ? " defined in " + resourceDescription : "") + ": " + msg);
this.resourceDescription = resourceDescription; this.resourceDescription = resourceDescription;
this.beanName = beanName; this.beanName = beanName;

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * factory-aware initialization code fails. BeansExceptions thrown by
* bean factory methods themselves should simply be propagated as-is. * bean factory methods themselves should simply be propagated as-is.
* *
* <p>Note that non-factory-aware initialization methods like afterPropertiesSet() * <p>Note that {@code afterPropertiesSet()} or a custom "init-method"
* or a custom "init-method" can throw any exception. * can throw any exception.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @since 13.11.2003 * @since 13.11.2003

View File

@ -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.
* <p>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.
* <p>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.
* <p>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());
}
}

View File

@ -31,6 +31,9 @@ import org.springframework.util.ClassUtils;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class UnsatisfiedDependencyException extends BeanCreationException { public class UnsatisfiedDependencyException extends BeanCreationException {
private InjectionPoint injectionPoint;
/** /**
* Create a new UnsatisfiedDependencyException. * Create a new UnsatisfiedDependencyException.
* @param resourceDescription description of the resource that the bean definition came from * @param resourceDescription description of the resource that the bean definition came from
@ -60,6 +63,36 @@ public class UnsatisfiedDependencyException extends BeanCreationException {
initCause(ex); 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. * Create a new UnsatisfiedDependencyException.
* @param resourceDescription description of the resource that the bean definition came from * @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 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 ctorArgType the type of the constructor argument that couldn't be satisfied
* @param msg the detail message * @param msg the detail message
* @deprecated in favor of {@link #UnsatisfiedDependencyException(String, String, InjectionPoint, String)}
*/ */
@Deprecated
public UnsatisfiedDependencyException( public UnsatisfiedDependencyException(
String resourceDescription, String beanName, int ctorArgIndex, Class<?> ctorArgType, String msg) { 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 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 ctorArgType the type of the constructor argument that couldn't be satisfied
* @param ex the bean creation exception that indicated the unsatisfied dependency * @param ex the bean creation exception that indicated the unsatisfied dependency
* @deprecated in favor of {@link #UnsatisfiedDependencyException(String, String, InjectionPoint, BeansException)}
*/ */
@Deprecated
public UnsatisfiedDependencyException( public UnsatisfiedDependencyException(
String resourceDescription, String beanName, int ctorArgIndex, Class<?> ctorArgType, BeansException ex) { String resourceDescription, String beanName, int ctorArgIndex, Class<?> ctorArgType, BeansException ex) {
@ -92,4 +129,13 @@ public class UnsatisfiedDependencyException extends BeanCreationException {
initCause(ex); initCause(ex);
} }
/**
* Return the injection point (field or method/constructor parameter), if known.
* @since 4.3
*/
public InjectionPoint getInjectionPoint() {
return this.injectionPoint;
}
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; 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.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
@ -347,6 +349,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
try { try {
metadata.inject(bean, beanName, pvs); metadata.inject(bean, beanName, pvs);
} }
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) { catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex); throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
} }
@ -365,6 +370,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
try { try {
metadata.inject(bean, null, null); metadata.inject(bean, null, null);
} }
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) { catch (Throwable ex) {
throw new BeanCreationException("Injection of autowired dependencies failed for class [" + clazz + "]", ex); throw new BeanCreationException("Injection of autowired dependencies failed for class [" + clazz + "]", ex);
} }
@ -549,7 +557,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
@Override @Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
Field field = (Field) this.member; Field field = (Field) this.member;
try {
Object value; Object value;
if (this.cached) { if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue); value = resolvedCachedArgument(beanName, this.cachedFieldValue);
@ -559,7 +566,12 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
desc.setContainingClass(bean.getClass()); desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<String>(1); Set<String> autowiredBeanNames = new LinkedHashSet<String>(1);
TypeConverter typeConverter = beanFactory.getTypeConverter(); TypeConverter typeConverter = beanFactory.getTypeConverter();
try {
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) { synchronized (this) {
if (!this.cached) { if (!this.cached) {
if (value != null || this.required) { if (value != null || this.required) {
@ -586,10 +598,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
field.set(bean, value); field.set(bean, value);
} }
} }
catch (Throwable ex) {
throw new BeanCreationException("Could not autowire field: " + field, ex);
}
}
} }
@ -615,7 +623,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
return; return;
} }
Method method = (Method) this.member; Method method = (Method) this.member;
try {
Object[] arguments; Object[] arguments;
if (this.cached) { if (this.cached) {
// Shortcut for avoiding synchronization... // Shortcut for avoiding synchronization...
@ -629,20 +636,25 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
TypeConverter typeConverter = beanFactory.getTypeConverter(); TypeConverter typeConverter = beanFactory.getTypeConverter();
for (int i = 0; i < arguments.length; i++) { for (int i = 0; i < arguments.length; i++) {
MethodParameter methodParam = new MethodParameter(method, i); MethodParameter methodParam = new MethodParameter(method, i);
DependencyDescriptor desc = new DependencyDescriptor(methodParam, this.required); DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
desc.setContainingClass(bean.getClass()); currDesc.setContainingClass(bean.getClass());
descriptors[i] = desc; descriptors[i] = currDesc;
Object arg = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); try {
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeanNames, typeConverter);
if (arg == null && !this.required) { if (arg == null && !this.required) {
arguments = null; arguments = null;
break; break;
} }
arguments[i] = arg; arguments[i] = arg;
} }
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
}
}
synchronized (this) { synchronized (this) {
if (!this.cached) { if (!this.cached) {
if (arguments != null) { if (arguments != null) {
this.cachedMethodArguments = new Object[arguments.length]; this.cachedMethodArguments = new Object[paramTypes.length];
for (int i = 0; i < arguments.length; i++) { for (int i = 0; i < arguments.length; i++) {
this.cachedMethodArguments[i] = descriptors[i]; this.cachedMethodArguments[i] = descriptors[i];
} }
@ -667,15 +679,13 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
} }
} }
if (arguments != null) { if (arguments != null) {
try {
ReflectionUtils.makeAccessible(method); ReflectionUtils.makeAccessible(method);
method.invoke(bean, arguments); method.invoke(bean, arguments);
} }
} catch (InvocationTargetException ex){
catch (InvocationTargetException ex) {
throw ex.getTargetException(); throw ex.getTargetException();
} }
catch (Throwable ex) {
throw new BeanCreationException("Could not autowire method: " + method, ex);
} }
} }

View File

@ -19,7 +19,6 @@ package org.springframework.beans.factory.config;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.Serializable; import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
@ -27,13 +26,13 @@ import java.util.Map;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.GenericTypeResolver; import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
/** /**
* Descriptor for a specific dependency that is about to be injected. * Descriptor for a specific dependency that is about to be injected.
@ -44,15 +43,9 @@ import org.springframework.util.Assert;
* @since 2.5 * @since 2.5
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class DependencyDescriptor implements Serializable { public class DependencyDescriptor extends InjectionPoint implements Serializable {
private transient MethodParameter methodParameter; private final Class<?> declaringClass;
private transient Field field;
private Class<?> declaringClass;
private Class<?> containingClass;
private String methodName; private String methodName;
@ -68,7 +61,7 @@ public class DependencyDescriptor implements Serializable {
private int nestingLevel = 1; 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 * eagerly resolving potential target beans for type matching
*/ */
public DependencyDescriptor(MethodParameter methodParameter, boolean required, boolean eager) { public DependencyDescriptor(MethodParameter methodParameter, boolean required, boolean eager) {
Assert.notNull(methodParameter, "MethodParameter must not be null"); super(methodParameter);
this.methodParameter = methodParameter;
this.declaringClass = methodParameter.getDeclaringClass(); this.declaringClass = methodParameter.getDeclaringClass();
this.containingClass = methodParameter.getContainingClass();
if (this.methodParameter.getMethod() != null) { if (this.methodParameter.getMethod() != null) {
this.methodName = methodParameter.getMethod().getName(); this.methodName = methodParameter.getMethod().getName();
this.parameterTypes = methodParameter.getMethod().getParameterTypes(); this.parameterTypes = methodParameter.getMethod().getParameterTypes();
@ -101,6 +92,7 @@ public class DependencyDescriptor implements Serializable {
this.parameterTypes = methodParameter.getConstructor().getParameterTypes(); this.parameterTypes = methodParameter.getConstructor().getParameterTypes();
} }
this.parameterIndex = methodParameter.getParameterIndex(); this.parameterIndex = methodParameter.getParameterIndex();
this.containingClass = methodParameter.getContainingClass();
this.required = required; this.required = required;
this.eager = eager; this.eager = eager;
} }
@ -123,8 +115,7 @@ public class DependencyDescriptor implements Serializable {
* eagerly resolving potential target beans for type matching * eagerly resolving potential target beans for type matching
*/ */
public DependencyDescriptor(Field field, boolean required, boolean eager) { public DependencyDescriptor(Field field, boolean required, boolean eager) {
Assert.notNull(field, "Field must not be null"); super(field);
this.field = field;
this.declaringClass = field.getDeclaringClass(); this.declaringClass = field.getDeclaringClass();
this.fieldName = field.getName(); this.fieldName = field.getName();
this.required = required; this.required = required;
@ -136,39 +127,19 @@ public class DependencyDescriptor implements Serializable {
* @param original the original descriptor to create a copy from * @param original the original descriptor to create a copy from
*/ */
public DependencyDescriptor(DependencyDescriptor original) { public DependencyDescriptor(DependencyDescriptor original) {
this.methodParameter = (original.methodParameter != null ? new MethodParameter(original.methodParameter) : null); super(original);
this.field = original.field;
this.declaringClass = original.declaringClass; this.declaringClass = original.declaringClass;
this.containingClass = original.containingClass;
this.methodName = original.methodName; this.methodName = original.methodName;
this.parameterTypes = original.parameterTypes; this.parameterTypes = original.parameterTypes;
this.parameterIndex = original.parameterIndex; this.parameterIndex = original.parameterIndex;
this.fieldName = original.fieldName; this.fieldName = original.fieldName;
this.containingClass = original.containingClass;
this.required = original.required; this.required = original.required;
this.eager = original.eager; this.eager = original.eager;
this.nestingLevel = original.nestingLevel; this.nestingLevel = original.nestingLevel;
this.fieldAnnotations = original.fieldAnnotations;
} }
/**
* Return the wrapped MethodParameter, if any.
* <p>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.
* <p>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. * Return whether this dependency is required.
*/ */
@ -358,19 +329,18 @@ public class DependencyDescriptor implements Serializable {
GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter)); GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter));
} }
/**
* Obtain the annotations associated with the wrapped parameter/field, if any. @Override
*/ public boolean equals(Object other) {
public Annotation[] getAnnotations() { if (this == other) {
if (this.field != null) { return true;
if (this.fieldAnnotations == null) {
this.fieldAnnotations = this.field.getAnnotations();
} }
return this.fieldAnnotations; if (!super.equals(other)) {
} return false;
else {
return this.methodParameter.getParameterAnnotations();
} }
DependencyDescriptor otherDesc = (DependencyDescriptor) other;
return (this.required == otherDesc.required && this.eager == otherDesc.eager &&
this.nestingLevel == otherDesc.nestingLevel && this.containingClass == otherDesc.containingClass);
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.TypeMismatchException;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
@ -159,8 +160,7 @@ class ConstructorResolver {
Set<Constructor<?>> ambiguousConstructors = null; Set<Constructor<?>> ambiguousConstructors = null;
LinkedList<UnsatisfiedDependencyException> causes = null; LinkedList<UnsatisfiedDependencyException> causes = null;
for (int i = 0; i < candidates.length; i++) { for (Constructor<?> candidate : candidates) {
Constructor<?> candidate = candidates[i];
Class<?>[] paramTypes = candidate.getParameterTypes(); Class<?>[] paramTypes = candidate.getParameterTypes();
if (constructorToUse != null && argsToUse.length > paramTypes.length) { if (constructorToUse != null && argsToUse.length > paramTypes.length) {
@ -665,7 +665,6 @@ class ConstructorResolver {
BeanWrapper bw, Class<?>[] paramTypes, String[] paramNames, Object methodOrCtor, BeanWrapper bw, Class<?>[] paramTypes, String[] paramNames, Object methodOrCtor,
boolean autowiring) throws UnsatisfiedDependencyException { boolean autowiring) throws UnsatisfiedDependencyException {
String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method");
TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?
this.beanFactory.getCustomTypeConverter() : bw); this.beanFactory.getCustomTypeConverter() : bw);
@ -700,9 +699,9 @@ class ConstructorResolver {
ConstructorArgumentValues.ValueHolder sourceHolder = ConstructorArgumentValues.ValueHolder sourceHolder =
(ConstructorArgumentValues.ValueHolder) valueHolder.getSource(); (ConstructorArgumentValues.ValueHolder) valueHolder.getSource();
Object sourceValue = sourceHolder.getValue(); Object sourceValue = sourceHolder.getValue();
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
try { try {
convertedValue = converter.convertIfNecessary(originalValue, paramType, convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam);
MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex));
// TODO re-enable once race condition has been found (SPR-7423) // TODO re-enable once race condition has been found (SPR-7423)
/* /*
if (originalValue == sourceValue || sourceValue instanceof TypedStringValue) { if (originalValue == sourceValue || sourceValue instanceof TypedStringValue) {
@ -718,8 +717,8 @@ class ConstructorResolver {
} }
catch (TypeMismatchException ex) { catch (TypeMismatchException ex) {
throw new UnsatisfiedDependencyException( throw new UnsatisfiedDependencyException(
mbd.getResourceDescription(), beanName, paramIndex, paramType, mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
"Could not convert " + methodType + " argument value of type [" + "Could not convert argument value of type [" +
ObjectUtils.nullSafeClassName(valueHolder.getValue()) + ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
"] to required type [" + paramType.getName() + "]: " + ex.getMessage()); "] to required type [" + paramType.getName() + "]: " + ex.getMessage());
} }
@ -728,17 +727,18 @@ class ConstructorResolver {
args.rawArguments[paramIndex] = originalValue; args.rawArguments[paramIndex] = originalValue;
} }
else { else {
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
// No explicit match found: we're either supposed to autowire or // No explicit match found: we're either supposed to autowire or
// have to fail creating an argument array for the given constructor. // have to fail creating an argument array for the given constructor.
if (!autowiring) { if (!autowiring) {
throw new UnsatisfiedDependencyException( throw new UnsatisfiedDependencyException(
mbd.getResourceDescription(), beanName, paramIndex, paramType, mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
"Ambiguous " + methodType + " argument types - " + "Ambiguous argument values for parameter of type [" + paramType.getName() +
"did you specify the correct bean references as " + methodType + " arguments?"); "] - did you specify the correct bean references as arguments?");
} }
try { try {
MethodParameter param = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex); Object autowiredArgument =
Object autowiredArgument = resolveAutowiredArgument(param, beanName, autowiredBeanNames, converter); resolveAutowiredArgument(methodParam, beanName, autowiredBeanNames, converter);
args.rawArguments[paramIndex] = autowiredArgument; args.rawArguments[paramIndex] = autowiredArgument;
args.arguments[paramIndex] = autowiredArgument; args.arguments[paramIndex] = autowiredArgument;
args.preparedArguments[paramIndex] = new AutowiredArgumentMarker(); args.preparedArguments[paramIndex] = new AutowiredArgumentMarker();
@ -746,7 +746,7 @@ class ConstructorResolver {
} }
catch (BeansException ex) { catch (BeansException ex) {
throw new UnsatisfiedDependencyException( 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); this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
if (this.beanFactory.logger.isDebugEnabled()) { if (this.beanFactory.logger.isDebugEnabled()) {
this.beanFactory.logger.debug("Autowiring by type from bean name '" + beanName + 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); resolvedArgs[argIndex] = converter.convertIfNecessary(argValue, paramType, methodParam);
} }
catch (TypeMismatchException ex) { catch (TypeMismatchException ex) {
String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method");
throw new UnsatisfiedDependencyException( throw new UnsatisfiedDependencyException(
mbd.getResourceDescription(), beanName, argIndex, paramType, mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
"Could not convert " + methodType + " argument value of type [" + "Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(argValue) +
ObjectUtils.nullSafeClassName(argValue) +
"] to required type [" + paramType.getName() + "]: " + ex.getMessage()); "] to required type [" + paramType.getName() + "]: " + ex.getMessage());
} }
} }

View File

@ -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; package org.springframework.beans.factory;
import org.junit.Test; import org.junit.Test;
@ -49,8 +65,7 @@ public class Spr5475Tests {
cav.addIndexedArgumentValue(1, "bogusArg2".getBytes()); cav.addIndexedArgumentValue(1, "bogusArg2".getBytes());
def.setConstructorArgumentValues(cav); def.setConstructorArgumentValues(cav);
assertExceptionMessageForMisconfiguredFactoryMethod( assertExceptionMessageForMisconfiguredFactoryMethod(def,
def,
"Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(CharSequence,byte[])'. " + "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."); "Check that a method with the specified name and arguments exists and that it is static.");
} }
@ -62,7 +77,8 @@ public class Spr5475Tests {
try { try {
factory.preInstantiateSingletons(); factory.preInstantiateSingletons();
fail("should have failed with BeanCreationException due to incorrectly invoked factory method"); fail("should have failed with BeanCreationException due to incorrectly invoked factory method");
} catch (BeanCreationException ex) { }
catch (BeanCreationException ex) {
assertThat(ex.getMessage(), equalTo(expectedMessage)); 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 // 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 // where a no-arg factory method is called with arguments. Adding this test just to document the difference
assertExceptionMessageForMisconfiguredFactoryMethod( assertExceptionMessageForMisconfiguredFactoryMethod(
rootBeanDefinition(Foo.class) rootBeanDefinition(Foo.class).
.setFactoryMethod("singleArgFactory").getBeanDefinition(), setFactoryMethod("singleArgFactory").getBeanDefinition(),
"Error creating bean with name 'foo': " + "Error creating bean with name 'foo': " +
"Unsatisfied dependency expressed through constructor argument with index 0 of type [java.lang.String]: " + "Unsatisfied dependency expressed through method 'singleArgFactory' parameter 0: " +
"Ambiguous factory method argument types - did you specify the correct bean references as factory method arguments?"); "Ambiguous argument values for parameter of type [java.lang.String] - " +
"did you specify the correct bean references as arguments?");
} }
static class Foo { static class Foo {
static Foo noArgFactory() { static Foo noArgFactory() {
return new Foo(); return new Foo();
} }

View File

@ -83,6 +83,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("testBean", new GenericBeanDefinition()); bf.registerBeanDefinition("testBean", new GenericBeanDefinition());
try { try {
bf.getBean("testBean"); bf.getBean("testBean");
fail("Should have thrown BeanCreationException");
} }
catch (BeanCreationException ex) { catch (BeanCreationException ex) {
assertTrue(ex.getRootCause() instanceof IllegalStateException); assertTrue(ex.getRootCause() instanceof IllegalStateException);
@ -635,6 +636,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
} }
catch (UnsatisfiedDependencyException ex) { catch (UnsatisfiedDependencyException ex) {
// expected // expected
assertSame(ConstructorWithoutFallbackBean.class, ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
} }
} }
@ -838,8 +840,9 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.getBean("annotatedBean"); bf.getBean("annotatedBean");
fail("should have failed, more than one bean of type"); fail("should have failed, more than one bean of type");
} }
catch (BeanCreationException ex) { catch (UnsatisfiedDependencyException ex) {
// expected // expected
assertSame(MapMethodInjectionBean.class, ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
} }
bf.destroySingletons(); bf.destroySingletons();
} }
@ -1164,7 +1167,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb); bf.registerSingleton("testBean", tb);
CustomAnnotationRequiredFieldResourceInjectionBean bean = (CustomAnnotationRequiredFieldResourceInjectionBean) bf.getBean("customBean"); CustomAnnotationRequiredFieldResourceInjectionBean bean =
(CustomAnnotationRequiredFieldResourceInjectionBean) bf.getBean("customBean");
assertSame(tb, bean.getTestBean()); assertSame(tb, bean.getTestBean());
bf.destroySingletons(); bf.destroySingletons();
} }
@ -1183,10 +1187,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
try { try {
bf.getBean("customBean"); bf.getBean("customBean");
fail("expected BeanCreationException; no dependency available for required field"); fail("Should have thrown UnsatisfiedDependencyException");
} }
catch (BeanCreationException ex) { catch (UnsatisfiedDependencyException ex) {
// expected // expected
assertSame(CustomAnnotationRequiredFieldResourceInjectionBean.class,
ex.getInjectionPoint().getField().getDeclaringClass());
} }
bf.destroySingletons(); bf.destroySingletons();
} }
@ -1209,10 +1215,13 @@ public class AutowiredAnnotationBeanPostProcessorTests {
try { try {
bf.getBean("customBean"); bf.getBean("customBean");
fail("expected BeanCreationException; multiple beans of dependency type available"); fail("Should have thrown UnsatisfiedDependencyException");
} }
catch (BeanCreationException ex) { catch (UnsatisfiedDependencyException ex) {
// expected // expected
ex.printStackTrace();
assertSame(CustomAnnotationRequiredFieldResourceInjectionBean.class,
ex.getInjectionPoint().getField().getDeclaringClass());
} }
bf.destroySingletons(); bf.destroySingletons();
} }
@ -1231,7 +1240,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb); bf.registerSingleton("testBean", tb);
CustomAnnotationRequiredMethodResourceInjectionBean bean = (CustomAnnotationRequiredMethodResourceInjectionBean) bf.getBean("customBean"); CustomAnnotationRequiredMethodResourceInjectionBean bean =
(CustomAnnotationRequiredMethodResourceInjectionBean) bf.getBean("customBean");
assertSame(tb, bean.getTestBean()); assertSame(tb, bean.getTestBean());
bf.destroySingletons(); bf.destroySingletons();
} }
@ -1250,10 +1260,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
try { try {
bf.getBean("customBean"); bf.getBean("customBean");
fail("expected BeanCreationException; no dependency available for required method"); fail("Should have thrown UnsatisfiedDependencyException");
} }
catch (BeanCreationException e) { catch (UnsatisfiedDependencyException ex) {
// expected // expected
assertSame(CustomAnnotationRequiredMethodResourceInjectionBean.class,
ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
} }
bf.destroySingletons(); bf.destroySingletons();
} }
@ -1276,10 +1288,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
try { try {
bf.getBean("customBean"); bf.getBean("customBean");
fail("expected BeanCreationException; multiple beans of dependency type available"); fail("Should have thrown UnsatisfiedDependencyException");
} }
catch (BeanCreationException ex) { catch (UnsatisfiedDependencyException ex) {
// expected // expected
assertSame(CustomAnnotationRequiredMethodResourceInjectionBean.class,
ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
} }
bf.destroySingletons(); bf.destroySingletons();
} }
@ -1298,7 +1312,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb); bf.registerSingleton("testBean", tb);
CustomAnnotationOptionalFieldResourceInjectionBean bean = (CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean"); CustomAnnotationOptionalFieldResourceInjectionBean bean =
(CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean");
assertSame(tb, bean.getTestBean3()); assertSame(tb, bean.getTestBean3());
assertNull(bean.getTestBean()); assertNull(bean.getTestBean());
assertNull(bean.getTestBean2()); assertNull(bean.getTestBean2());
@ -1317,7 +1332,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("customBean", new RootBeanDefinition( bf.registerBeanDefinition("customBean", new RootBeanDefinition(
CustomAnnotationOptionalFieldResourceInjectionBean.class)); CustomAnnotationOptionalFieldResourceInjectionBean.class));
CustomAnnotationOptionalFieldResourceInjectionBean bean = (CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean"); CustomAnnotationOptionalFieldResourceInjectionBean bean =
(CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean");
assertNull(bean.getTestBean3()); assertNull(bean.getTestBean3());
assertNull(bean.getTestBean()); assertNull(bean.getTestBean());
assertNull(bean.getTestBean2()); assertNull(bean.getTestBean2());
@ -1342,10 +1358,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
try { try {
bf.getBean("customBean"); bf.getBean("customBean");
fail("expected BeanCreationException; multiple beans of dependency type available"); fail("Should have thrown UnsatisfiedDependencyException");
} }
catch (BeanCreationException ex) { catch (UnsatisfiedDependencyException ex) {
// expected // expected
assertSame(CustomAnnotationOptionalFieldResourceInjectionBean.class,
ex.getInjectionPoint().getField().getDeclaringClass());
} }
bf.destroySingletons(); bf.destroySingletons();
} }
@ -1364,7 +1382,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
TestBean tb = new TestBean(); TestBean tb = new TestBean();
bf.registerSingleton("testBean", tb); bf.registerSingleton("testBean", tb);
CustomAnnotationOptionalMethodResourceInjectionBean bean = (CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean"); CustomAnnotationOptionalMethodResourceInjectionBean bean =
(CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean");
assertSame(tb, bean.getTestBean3()); assertSame(tb, bean.getTestBean3());
assertNull(bean.getTestBean()); assertNull(bean.getTestBean());
assertNull(bean.getTestBean2()); assertNull(bean.getTestBean2());
@ -1383,7 +1402,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
bf.registerBeanDefinition("customBean", new RootBeanDefinition( bf.registerBeanDefinition("customBean", new RootBeanDefinition(
CustomAnnotationOptionalMethodResourceInjectionBean.class)); CustomAnnotationOptionalMethodResourceInjectionBean.class));
CustomAnnotationOptionalMethodResourceInjectionBean bean = (CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean"); CustomAnnotationOptionalMethodResourceInjectionBean bean =
(CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean");
assertNull(bean.getTestBean3()); assertNull(bean.getTestBean3());
assertNull(bean.getTestBean()); assertNull(bean.getTestBean());
assertNull(bean.getTestBean2()); assertNull(bean.getTestBean2());
@ -1408,10 +1428,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
try { try {
bf.getBean("customBean"); bf.getBean("customBean");
fail("expected BeanCreationException; multiple beans of dependency type available"); fail("Should have thrown UnsatisfiedDependencyException");
} }
catch (BeanCreationException ex) { catch (UnsatisfiedDependencyException ex) {
// expected // expected
assertSame(CustomAnnotationOptionalMethodResourceInjectionBean.class,
ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
} }
bf.destroySingletons(); bf.destroySingletons();
} }
@ -2644,7 +2666,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
@Target({ElementType.METHOD, ElementType.FIELD}) @Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public static @interface MyAutowired { public @interface MyAutowired {
boolean optional() default false; boolean optional() default false;
} }

View File

@ -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 * @return the Method or Constructor as Member
*/ */
public Member getMember() { public Member getMember() {
@ -196,7 +203,9 @@ public class MethodParameter {
} }
/** /**
* Returns the wrapped annotated element. * Return the wrapped annotated element.
* <p>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 * @return the Method or Constructor as AnnotatedElement
*/ */
public AnnotatedElement getAnnotatedElement() { 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 index of the method/constructor parameter.
* @return the parameter index (-1 in case of the return type) * @return the parameter index (-1 in case of the return type)
@ -577,6 +579,12 @@ public class MethodParameter {
return (getMember().hashCode() * 31 + this.parameterIndex); return (getMember().hashCode() * 31 + this.parameterIndex);
} }
@Override
public String toString() {
return (this.method != null ? "method '" + this.method.getName() + "'" : "constructor") +
" parameter " + this.parameterIndex;
}
@Override @Override
public MethodParameter clone() { public MethodParameter clone() {
return new MethodParameter(this); return new MethodParameter(this);