Skip to content

Commit 40a78d8

Browse files
committed
Distinguish between unsatisfied constructor and factory method deps
Previously, an UnsatisfiedDependencyException was thrown when the dependencies (arguments) of a constructor or a factory method could not be satisfied. This had two drawbacks: 1. The exception message for a failure for a factory method mentioned constructor arguments. 2. The catcher of the exception had no way, short of parsing the message, to determine the exact nature of the failure. This commit introduces two new UnsatisfiedDependencyException subclasses – UnsatisfiedFactoryMethodDependencyException and UnsatisfiedConstructorDependencyException. Each new exception provides getters to access the method or constructor, and the index and type of the unsatisfied argument.
1 parent 5c87afc commit 40a78d8

File tree

5 files changed

+236
-18
lines changed

5 files changed

+236
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans.factory;
18+
19+
import java.lang.reflect.Constructor;
20+
21+
import org.springframework.beans.BeansException;
22+
23+
/**
24+
* A specialization of {@link UnsatisfiedDependencyException} that is thrown
25+
* when a bean constructor has an unsatisfied dependency.
26+
*
27+
* @author Andy Wilkinson
28+
* @since 4.3.0
29+
*/
30+
@SuppressWarnings("serial")
31+
public class UnsatisfiedConstructorDependencyException extends UnsatisfiedDependencyException {
32+
33+
private final Constructor<?> constructor;
34+
35+
private final Class<?> argumentType;
36+
37+
private final int argumentIndex;
38+
39+
/**
40+
* Create a new UnsatisfiedConstructorDependencyException.
41+
* @param constructor the constructor with the unsatisfied dependency
42+
* @param resourceDescription description of the resource that the bean definition came from
43+
* @param beanName the name of the bean requested
44+
* @param argIndex the index of the constructor argument that couldn't be satisfied
45+
* @param argType the type of the constructor argument that couldn't be satisfied
46+
* @param cause the cause
47+
*/
48+
public UnsatisfiedConstructorDependencyException(
49+
Constructor<?> constructor, String resourceDescription, String beanName, int argIndex, Class<?> argType, BeansException cause) {
50+
super(resourceDescription, beanName, argIndex, argType, cause);
51+
this.constructor = constructor;
52+
this.argumentIndex = argIndex;
53+
this.argumentType = argType;
54+
}
55+
56+
/**
57+
* Create a new UnsatisfiedConstructorDependencyException.
58+
* @param constructor the constructor with the unsatisfied dependency
59+
* @param resourceDescription description of the resource that the bean definition came from
60+
* @param beanName the name of the bean requested
61+
* @param argIndex the index of the constructor argument that couldn't be satisfied
62+
* @param argType the type of the constructor argument that couldn't be satisfied
63+
* @param msg the detail message
64+
*/
65+
public UnsatisfiedConstructorDependencyException(
66+
Constructor<?> constructor, String resourceDescription, String beanName, int argIndex, Class<?> argType, String msg) {
67+
super(resourceDescription, beanName, argIndex, argType, msg);
68+
this.constructor = constructor;
69+
this.argumentType = argType;
70+
this.argumentIndex = argIndex;
71+
}
72+
73+
/**
74+
* Return the constructor with the dependency that couldn't be satisfied.
75+
*/
76+
public Constructor<?> getConstructor() {
77+
return constructor;
78+
}
79+
80+
/**
81+
* Return the type of the argument the couldn't be satisfied.
82+
*/
83+
public Class<?> getArgumentType() {
84+
return argumentType;
85+
}
86+
87+
/**
88+
* Return the index of the argument that couldn't be satisfied.
89+
* @return the parameterIndex
90+
*/
91+
public int getArgumentIndex() {
92+
return argumentIndex;
93+
}
94+
95+
}

spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,8 @@ public UnsatisfiedDependencyException(
9292
initCause(ex);
9393
}
9494

95+
protected UnsatisfiedDependencyException(String message, Throwable cause, String resourceDescription, String beanName) {
96+
super(resourceDescription, beanName, message, cause);
97+
}
98+
9599
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans.factory;
18+
19+
import java.lang.reflect.Method;
20+
21+
import org.springframework.beans.BeansException;
22+
import org.springframework.util.ClassUtils;
23+
24+
/**
25+
* A specialization of {@link UnsatisfiedDependencyException} that is thrown
26+
* when a bean factory method has an unsatisfied dependency.
27+
*
28+
* @author Andy Wilkinson
29+
* @since 4.3.0
30+
*/
31+
@SuppressWarnings("serial")
32+
public class UnsatisfiedFactoryMethodDependencyException extends UnsatisfiedDependencyException {
33+
34+
private final Method factoryMethod;
35+
36+
private final Class<?> argumentType;
37+
38+
private final int argumentIndex;
39+
40+
/**
41+
* Create a new UnsatisfiedFactoryMethodDependencyException.
42+
* @param factoryMethod the factory method with the unsatisfied dependency
43+
* @param resourceDescription description of the resource that the bean definition came from
44+
* @param beanName the name of the bean requested
45+
* @param argIndex the index of the method argument that couldn't be satisfied
46+
* @param argType the type of the method argument that couldn't be satisfied
47+
* @param msg the detail message
48+
*/
49+
public UnsatisfiedFactoryMethodDependencyException(
50+
Method factoryMethod, String resourceDescription, String beanName, int argIndex, Class<?> argType, String msg) {
51+
this(factoryMethod, resourceDescription, beanName, argIndex, argType, msg, null);
52+
}
53+
54+
/**
55+
* Create a new UnsatisfiedFactoryMethodDependencyException.
56+
* @param factoryMethod the factory method with the unsatisfied dependency
57+
* @param resourceDescription description of the resource that the bean definition came from
58+
* @param beanName the name of the bean requested
59+
* @param argIndex the index of the method argument that couldn't be satisfied
60+
* @param argType the type of the method argument that couldn't be satisfied
61+
* @param cause the cause
62+
*/
63+
public UnsatisfiedFactoryMethodDependencyException(
64+
Method factoryMethod, String resourceDescription, String beanName, int argIndex, Class<?> argType, BeansException cause) {
65+
this(factoryMethod, resourceDescription, beanName, argIndex, argType, null, cause);
66+
}
67+
68+
private UnsatisfiedFactoryMethodDependencyException(
69+
Method factoryMethod, String resourceDescription, String beanName, int argIndex, Class<?> argType, String msg, BeansException cause) {
70+
super("Unsatisfied dependency expressed through argument with index " +
71+
argIndex + " of type [" + ClassUtils.getQualifiedName(argType) + "]" +
72+
(msg != null ? ": " + msg : ""), cause, resourceDescription, beanName);
73+
this.factoryMethod = factoryMethod;
74+
this.argumentType = argType;
75+
this.argumentIndex = argIndex;
76+
}
77+
78+
/**
79+
* Return the factory method with the dependency that couldn't be satisfied.
80+
*/
81+
public Method getFactoryMethod() {
82+
return factoryMethod;
83+
}
84+
85+
/**
86+
* Return the type of the argument the couldn't be satisfied.
87+
*/
88+
public Class<?> getArgumentType() {
89+
return argumentType;
90+
}
91+
92+
/**
93+
* Return the index of the argument that couldn't be satisfied.
94+
* @return the parameterIndex
95+
*/
96+
public int getArgumentIndex() {
97+
return argumentIndex;
98+
}
99+
100+
}

spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@
4040
import org.springframework.beans.TypeMismatchException;
4141
import org.springframework.beans.factory.BeanCreationException;
4242
import org.springframework.beans.factory.BeanDefinitionStoreException;
43+
import org.springframework.beans.factory.UnsatisfiedConstructorDependencyException;
4344
import org.springframework.beans.factory.UnsatisfiedDependencyException;
45+
import org.springframework.beans.factory.UnsatisfiedFactoryMethodDependencyException;
46+
import org.springframework.beans.factory.config.BeanDefinition;
4447
import org.springframework.beans.factory.config.ConstructorArgumentValues;
4548
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
4649
import org.springframework.beans.factory.config.DependencyDescriptor;
@@ -717,10 +720,8 @@ private ArgumentsHolder createArgumentArray(
717720
// }
718721
}
719722
catch (TypeMismatchException ex) {
720-
throw new UnsatisfiedDependencyException(
721-
mbd.getResourceDescription(), beanName, paramIndex, paramType,
722-
"Could not convert " + methodType + " argument value of type [" +
723-
ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
723+
throw createUnsatisfiedDependencyException(methodOrCtor, mbd, beanName, paramIndex, paramType,
724+
"Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
724725
"] to required type [" + paramType.getName() + "]: " + ex.getMessage());
725726
}
726727
}
@@ -731,10 +732,8 @@ private ArgumentsHolder createArgumentArray(
731732
// No explicit match found: we're either supposed to autowire or
732733
// have to fail creating an argument array for the given constructor.
733734
if (!autowiring) {
734-
throw new UnsatisfiedDependencyException(
735-
mbd.getResourceDescription(), beanName, paramIndex, paramType,
736-
"Ambiguous " + methodType + " argument types - " +
737-
"did you specify the correct bean references as " + methodType + " arguments?");
735+
throw createUnsatisfiedDependencyException(methodOrCtor, mbd, beanName, paramIndex, paramType,
736+
"Ambiguous argument types - did you specify the correct bean references as arguments?");
738737
}
739738
try {
740739
MethodParameter param = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
@@ -745,8 +744,12 @@ private ArgumentsHolder createArgumentArray(
745744
args.resolveNecessary = true;
746745
}
747746
catch (BeansException ex) {
748-
throw new UnsatisfiedDependencyException(
749-
mbd.getResourceDescription(), beanName, paramIndex, paramType, ex);
747+
if (methodOrCtor instanceof Constructor) {
748+
throw new UnsatisfiedConstructorDependencyException((Constructor<?>)methodOrCtor, mbd.getResourceDescription(), beanName, paramIndex, paramType, ex);
749+
}
750+
else {
751+
throw new UnsatisfiedFactoryMethodDependencyException((Method)methodOrCtor, mbd.getResourceDescription(), beanName, paramIndex, paramType, ex);
752+
}
750753
}
751754
}
752755
}
@@ -762,6 +765,18 @@ private ArgumentsHolder createArgumentArray(
762765
return args;
763766
}
764767

768+
private UnsatisfiedDependencyException createUnsatisfiedDependencyException(Object methodOrCtor, BeanDefinition mbd,
769+
String beanName, int paramIndex, Class<?> paramType, String message) {
770+
if (methodOrCtor instanceof Constructor) {
771+
return new UnsatisfiedConstructorDependencyException((Constructor<?>)methodOrCtor, mbd.getResourceDescription(),
772+
beanName, paramIndex, paramType, message);
773+
}
774+
else {
775+
return new UnsatisfiedFactoryMethodDependencyException((Method)methodOrCtor, mbd.getResourceDescription(),
776+
beanName, paramIndex, paramType, message);
777+
}
778+
}
779+
765780
/**
766781
* Resolve the prepared arguments stored in the given bean definition.
767782
*/

spring-beans/src/test/java/org/springframework/beans/factory/Spr5475Tests.java

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,23 @@ public class Spr5475Tests {
2121

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

3233
@Test
3334
public void noArgFactoryMethodInvokedWithTwoArgs() {
34-
assertExceptionMessageForMisconfiguredFactoryMethod(
35+
assertExceptionForMisconfiguredFactoryMethod(
3536
rootBeanDefinition(Foo.class)
3637
.setFactoryMethod("noArgFactory")
3738
.addConstructorArgValue("bogusArg1")
3839
.addConstructorArgValue("bogusArg2".getBytes()).getBeanDefinition(),
40+
BeanCreationException.class,
3941
"Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(String,byte[])'. " +
4042
"Check that a method with the specified name and arguments exists and that it is static.");
4143
}
@@ -49,13 +51,13 @@ public void noArgFactoryMethodInvokedWithTwoArgsAndTypesSpecified() {
4951
cav.addIndexedArgumentValue(1, "bogusArg2".getBytes());
5052
def.setConstructorArgumentValues(cav);
5153

52-
assertExceptionMessageForMisconfiguredFactoryMethod(
53-
def,
54+
assertExceptionForMisconfiguredFactoryMethod(
55+
def, BeanCreationException.class,
5456
"Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(CharSequence,byte[])'. " +
5557
"Check that a method with the specified name and arguments exists and that it is static.");
5658
}
5759

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

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

7073
@Test
7174
public void singleArgFactoryMethodInvokedWithNoArgs() {
7275
// calling a factory method that accepts arguments without any arguments emits an exception unlike cases
7376
// where a no-arg factory method is called with arguments. Adding this test just to document the difference
74-
assertExceptionMessageForMisconfiguredFactoryMethod(
77+
assertExceptionForMisconfiguredFactoryMethod(
7578
rootBeanDefinition(Foo.class)
7679
.setFactoryMethod("singleArgFactory").getBeanDefinition(),
80+
UnsatisfiedFactoryMethodDependencyException.class,
7781
"Error creating bean with name 'foo': " +
78-
"Unsatisfied dependency expressed through constructor argument with index 0 of type [java.lang.String]: " +
79-
"Ambiguous factory method argument types - did you specify the correct bean references as factory method arguments?");
82+
"Unsatisfied dependency expressed through argument with index 0 of type [java.lang.String]: " +
83+
"Ambiguous argument types - did you specify the correct bean references as arguments?");
8084
}
8185

8286

0 commit comments

Comments
 (0)