Skip to content

Distinguish between unsatisfied constructor and factory method deps #965

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* 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.reflect.Constructor;

import org.springframework.beans.BeansException;

/**
* A specialization of {@link UnsatisfiedDependencyException} that is thrown
* when a bean constructor has an unsatisfied dependency.
*
* @author Andy Wilkinson
* @since 4.3.0
*/
@SuppressWarnings("serial")
public class UnsatisfiedConstructorDependencyException extends UnsatisfiedDependencyException {

private final Constructor<?> constructor;

private final Class<?> argumentType;

private final int argumentIndex;

/**
* Create a new UnsatisfiedConstructorDependencyException.
* @param constructor the constructor with the unsatisfied dependency
* @param resourceDescription description of the resource that the bean definition came from
* @param beanName the name of the bean requested
* @param argIndex the index of the constructor argument that couldn't be satisfied
* @param argType the type of the constructor argument that couldn't be satisfied
* @param cause the cause
*/
public UnsatisfiedConstructorDependencyException(
Constructor<?> constructor, String resourceDescription, String beanName, int argIndex, Class<?> argType, BeansException cause) {
super(resourceDescription, beanName, argIndex, argType, cause);
this.constructor = constructor;
this.argumentIndex = argIndex;
this.argumentType = argType;
}

/**
* Create a new UnsatisfiedConstructorDependencyException.
* @param constructor the constructor with the unsatisfied dependency
* @param resourceDescription description of the resource that the bean definition came from
* @param beanName the name of the bean requested
* @param argIndex the index of the constructor argument that couldn't be satisfied
* @param argType the type of the constructor argument that couldn't be satisfied
* @param msg the detail message
*/
public UnsatisfiedConstructorDependencyException(
Constructor<?> constructor, String resourceDescription, String beanName, int argIndex, Class<?> argType, String msg) {
super(resourceDescription, beanName, argIndex, argType, msg);
this.constructor = constructor;
this.argumentType = argType;
this.argumentIndex = argIndex;
}

/**
* Return the constructor with the dependency that couldn't be satisfied.
*/
public Constructor<?> getConstructor() {
return constructor;
}

/**
* Return the type of the argument the couldn't be satisfied.
*/
public Class<?> getArgumentType() {
return argumentType;
}

/**
* Return the index of the argument that couldn't be satisfied.
* @return the parameterIndex
*/
public int getArgumentIndex() {
return argumentIndex;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,8 @@ public UnsatisfiedDependencyException(
initCause(ex);
}

protected UnsatisfiedDependencyException(String message, Throwable cause, String resourceDescription, String beanName) {
super(resourceDescription, beanName, message, cause);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* 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.reflect.Method;

import org.springframework.beans.BeansException;
import org.springframework.util.ClassUtils;

/**
* A specialization of {@link UnsatisfiedDependencyException} that is thrown
* when a bean factory method has an unsatisfied dependency.
*
* @author Andy Wilkinson
* @since 4.3.0
*/
@SuppressWarnings("serial")
public class UnsatisfiedFactoryMethodDependencyException extends UnsatisfiedDependencyException {

private final Method factoryMethod;

private final Class<?> argumentType;

private final int argumentIndex;

/**
* Create a new UnsatisfiedFactoryMethodDependencyException.
* @param factoryMethod the factory method with the unsatisfied dependency
* @param resourceDescription description of the resource that the bean definition came from
* @param beanName the name of the bean requested
* @param argIndex the index of the method argument that couldn't be satisfied
* @param argType the type of the method argument that couldn't be satisfied
* @param msg the detail message
*/
public UnsatisfiedFactoryMethodDependencyException(
Method factoryMethod, String resourceDescription, String beanName, int argIndex, Class<?> argType, String msg) {
this(factoryMethod, resourceDescription, beanName, argIndex, argType, msg, null);
}

/**
* Create a new UnsatisfiedFactoryMethodDependencyException.
* @param factoryMethod the factory method with the unsatisfied dependency
* @param resourceDescription description of the resource that the bean definition came from
* @param beanName the name of the bean requested
* @param argIndex the index of the method argument that couldn't be satisfied
* @param argType the type of the method argument that couldn't be satisfied
* @param cause the cause
*/
public UnsatisfiedFactoryMethodDependencyException(
Method factoryMethod, String resourceDescription, String beanName, int argIndex, Class<?> argType, BeansException cause) {
this(factoryMethod, resourceDescription, beanName, argIndex, argType, null, cause);
}

private UnsatisfiedFactoryMethodDependencyException(
Method factoryMethod, String resourceDescription, String beanName, int argIndex, Class<?> argType, String msg, BeansException cause) {
super("Unsatisfied dependency expressed through argument with index " +
argIndex + " of type [" + ClassUtils.getQualifiedName(argType) + "]" +
(msg != null ? ": " + msg : ""), cause, resourceDescription, beanName);
this.factoryMethod = factoryMethod;
this.argumentType = argType;
this.argumentIndex = argIndex;
}

/**
* Return the factory method with the dependency that couldn't be satisfied.
*/
public Method getFactoryMethod() {
return factoryMethod;
}

/**
* Return the type of the argument the couldn't be satisfied.
*/
public Class<?> getArgumentType() {
return argumentType;
}

/**
* Return the index of the argument that couldn't be satisfied.
* @return the parameterIndex
*/
public int getArgumentIndex() {
return argumentIndex;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.UnsatisfiedConstructorDependencyException;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.UnsatisfiedFactoryMethodDependencyException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.DependencyDescriptor;
Expand Down Expand Up @@ -717,10 +720,8 @@ private ArgumentsHolder createArgumentArray(
// }
}
catch (TypeMismatchException ex) {
throw new UnsatisfiedDependencyException(
mbd.getResourceDescription(), beanName, paramIndex, paramType,
"Could not convert " + methodType + " argument value of type [" +
ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
throw createUnsatisfiedDependencyException(methodOrCtor, mbd, beanName, paramIndex, paramType,
"Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
"] to required type [" + paramType.getName() + "]: " + ex.getMessage());
}
}
Expand All @@ -731,10 +732,8 @@ private ArgumentsHolder createArgumentArray(
// 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?");
throw createUnsatisfiedDependencyException(methodOrCtor, mbd, beanName, paramIndex, paramType,
"Ambiguous argument types - did you specify the correct bean references as arguments?");
}
try {
MethodParameter param = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
Expand All @@ -745,8 +744,12 @@ private ArgumentsHolder createArgumentArray(
args.resolveNecessary = true;
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(
mbd.getResourceDescription(), beanName, paramIndex, paramType, ex);
if (methodOrCtor instanceof Constructor) {
throw new UnsatisfiedConstructorDependencyException((Constructor<?>)methodOrCtor, mbd.getResourceDescription(), beanName, paramIndex, paramType, ex);
}
else {
throw new UnsatisfiedFactoryMethodDependencyException((Method)methodOrCtor, mbd.getResourceDescription(), beanName, paramIndex, paramType, ex);
}
}
}
}
Expand All @@ -762,6 +765,18 @@ private ArgumentsHolder createArgumentArray(
return args;
}

private UnsatisfiedDependencyException createUnsatisfiedDependencyException(Object methodOrCtor, BeanDefinition mbd,
String beanName, int paramIndex, Class<?> paramType, String message) {
if (methodOrCtor instanceof Constructor) {
return new UnsatisfiedConstructorDependencyException((Constructor<?>)methodOrCtor, mbd.getResourceDescription(),
beanName, paramIndex, paramType, message);
}
else {
return new UnsatisfiedFactoryMethodDependencyException((Method)methodOrCtor, mbd.getResourceDescription(),
beanName, paramIndex, paramType, message);
}
}

/**
* Resolve the prepared arguments stored in the given bean definition.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,23 @@ public class Spr5475Tests {

@Test
public void noArgFactoryMethodInvokedWithOneArg() {
assertExceptionMessageForMisconfiguredFactoryMethod(
assertExceptionForMisconfiguredFactoryMethod(
rootBeanDefinition(Foo.class)
.setFactoryMethod("noArgFactory")
.addConstructorArgValue("bogusArg").getBeanDefinition(),
BeanCreationException.class,
"Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(String)'. " +
"Check that a method with the specified name and arguments exists and that it is static.");
}

@Test
public void noArgFactoryMethodInvokedWithTwoArgs() {
assertExceptionMessageForMisconfiguredFactoryMethod(
assertExceptionForMisconfiguredFactoryMethod(
rootBeanDefinition(Foo.class)
.setFactoryMethod("noArgFactory")
.addConstructorArgValue("bogusArg1")
.addConstructorArgValue("bogusArg2".getBytes()).getBeanDefinition(),
BeanCreationException.class,
"Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(String,byte[])'. " +
"Check that a method with the specified name and arguments exists and that it is static.");
}
Expand All @@ -49,13 +51,13 @@ public void noArgFactoryMethodInvokedWithTwoArgsAndTypesSpecified() {
cav.addIndexedArgumentValue(1, "bogusArg2".getBytes());
def.setConstructorArgumentValues(cav);

assertExceptionMessageForMisconfiguredFactoryMethod(
def,
assertExceptionForMisconfiguredFactoryMethod(
def, BeanCreationException.class,
"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.");
}

private void assertExceptionMessageForMisconfiguredFactoryMethod(BeanDefinition bd, String expectedMessage) {
private void assertExceptionForMisconfiguredFactoryMethod(BeanDefinition bd, Class<? extends Throwable> expectedType, String expectedMessage) {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.registerBeanDefinition("foo", bd);

Expand All @@ -64,19 +66,21 @@ private void assertExceptionMessageForMisconfiguredFactoryMethod(BeanDefinition
fail("should have failed with BeanCreationException due to incorrectly invoked factory method");
} catch (BeanCreationException ex) {
assertThat(ex.getMessage(), equalTo(expectedMessage));
assertThat(ex, instanceOf(expectedType));
}
}

@Test
public void singleArgFactoryMethodInvokedWithNoArgs() {
// 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(
assertExceptionForMisconfiguredFactoryMethod(
rootBeanDefinition(Foo.class)
.setFactoryMethod("singleArgFactory").getBeanDefinition(),
UnsatisfiedFactoryMethodDependencyException.class,
"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 argument with index 0 of type [java.lang.String]: " +
"Ambiguous argument types - did you specify the correct bean references as arguments?");
}


Expand Down