Consistent throwing of BeanInstantiationException for factory methods, including a hint about circular references

Issue: SPR-12317
This commit is contained in:
Juergen Hoeller 2014-10-21 21:40:43 +02:00
parent 1ad7a03357
commit ad62b2afb1
6 changed files with 148 additions and 82 deletions

View File

@ -45,7 +45,7 @@ public class BeanInstantiationException extends FatalBeanException {
* @param cause the root cause * @param cause the root cause
*/ */
public BeanInstantiationException(Class<?> beanClass, String msg, Throwable cause) { public BeanInstantiationException(Class<?> beanClass, String msg, Throwable cause) {
super("Could not instantiate bean class [" + beanClass.getName() + "]: " + msg, cause); super("Failed to instantiate [" + beanClass.getName() + "]: " + msg, cause);
this.beanClass = beanClass; this.beanClass = beanClass;
} }

View File

@ -68,18 +68,16 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
@Override @Override
protected Object instantiateWithMethodInjection(RootBeanDefinition beanDefinition, String beanName, protected Object instantiateWithMethodInjection(RootBeanDefinition bd, String beanName, BeanFactory owner) {
BeanFactory owner) { return instantiateWithMethodInjection(bd, beanName, owner, null);
return instantiateWithMethodInjection(beanDefinition, beanName, owner, null, null);
} }
@Override @Override
protected Object instantiateWithMethodInjection(RootBeanDefinition beanDefinition, String beanName, protected Object instantiateWithMethodInjection(RootBeanDefinition bd, String beanName, BeanFactory owner,
BeanFactory owner, Constructor<?> ctor, Object[] args) { Constructor<?> ctor, Object... args) {
// Must generate CGLIB subclass. // Must generate CGLIB subclass...
return new CglibSubclassCreator(beanDefinition, owner).instantiate(ctor, args); return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
} }
@ -110,7 +108,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
* Ignored if the {@code ctor} parameter is {@code null}. * Ignored if the {@code ctor} parameter is {@code null}.
* @return new instance of the dynamically generated subclass * @return new instance of the dynamically generated subclass
*/ */
Object instantiate(Constructor<?> ctor, Object[] args) { public Object instantiate(Constructor<?> ctor, Object... args) {
Class<?> subclass = createEnhancedSubclass(this.beanDefinition); Class<?> subclass = createEnhancedSubclass(this.beanDefinition);
Object instance; Object instance;
if (ctor == null) { if (ctor == null) {
@ -122,8 +120,8 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
instance = enhancedSubclassConstructor.newInstance(args); instance = enhancedSubclassConstructor.newInstance(args);
} }
catch (Exception ex) { catch (Exception ex) {
throw new BeanInstantiationException(this.beanDefinition.getBeanClass(), String.format( throw new BeanInstantiationException(this.beanDefinition.getBeanClass(),
"Failed to invoke constructor for CGLIB enhanced subclass [%s]", subclass.getName()), ex); "Failed to invoke constructor for CGLIB enhanced subclass [" + subclass.getName() + "]", ex);
} }
} }
// SPR-10785: set callbacks directly on the instance instead of in the // SPR-10785: set callbacks directly on the instance instead of in the
@ -159,11 +157,11 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
private final RootBeanDefinition beanDefinition; private final RootBeanDefinition beanDefinition;
CglibIdentitySupport(RootBeanDefinition beanDefinition) { public CglibIdentitySupport(RootBeanDefinition beanDefinition) {
this.beanDefinition = beanDefinition; this.beanDefinition = beanDefinition;
} }
RootBeanDefinition getBeanDefinition() { public RootBeanDefinition getBeanDefinition() {
return this.beanDefinition; return this.beanDefinition;
} }
@ -187,7 +185,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
private static final Log logger = LogFactory.getLog(MethodOverrideCallbackFilter.class); private static final Log logger = LogFactory.getLog(MethodOverrideCallbackFilter.class);
MethodOverrideCallbackFilter(RootBeanDefinition beanDefinition) { public MethodOverrideCallbackFilter(RootBeanDefinition beanDefinition) {
super(beanDefinition); super(beanDefinition);
} }
@ -220,7 +218,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
private final BeanFactory owner; private final BeanFactory owner;
LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) { public LookupOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
super(beanDefinition); super(beanDefinition);
this.owner = owner; this.owner = owner;
} }
@ -248,7 +246,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt
private final BeanFactory owner; private final BeanFactory owner;
ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) { public ReplaceOverrideMethodInterceptor(RootBeanDefinition beanDefinition, BeanFactory owner) {
super(beanDefinition); super(beanDefinition);
this.owner = owner; this.owner = owner;
} }

View File

@ -94,8 +94,8 @@ class ConstructorResolver {
* or {@code null} if none (-> use constructor argument values from bean definition) * or {@code null} if none (-> use constructor argument values from bean definition)
* @return a BeanWrapper for the new instance * @return a BeanWrapper for the new instance
*/ */
public BeanWrapper autowireConstructor( public BeanWrapper autowireConstructor(final String beanName, final RootBeanDefinition mbd,
final String beanName, final RootBeanDefinition mbd, Constructor<?>[] chosenCtors, final Object[] explicitArgs) { Constructor<?>[] chosenCtors, final Object[] explicitArgs) {
BeanWrapperImpl bw = new BeanWrapperImpl(); BeanWrapperImpl bw = new BeanWrapperImpl();
this.beanFactory.initBeanWrapper(bw); this.beanFactory.initBeanWrapper(bw);
@ -275,7 +275,8 @@ class ConstructorResolver {
return bw; return bw;
} }
catch (Throwable ex) { catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex); throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean instantiation via constructor failed", ex);
} }
} }
@ -587,8 +588,8 @@ class ConstructorResolver {
}, beanFactory.getAccessControlContext()); }, beanFactory.getAccessControlContext());
} }
else { else {
beanInstance = beanFactory.getInstantiationStrategy().instantiate( beanInstance = this.beanFactory.getInstantiationStrategy().instantiate(
mbd, beanName, beanFactory, factoryBean, factoryMethodToUse, argsToUse); mbd, beanName, this.beanFactory, factoryBean, factoryMethodToUse, argsToUse);
} }
if (beanInstance == null) { if (beanInstance == null) {
@ -598,17 +599,17 @@ class ConstructorResolver {
return bw; return bw;
} }
catch (Throwable ex) { catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex); throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Bean instantiation via factory method failed", ex);
} }
} }
/** /**
* Resolve the constructor arguments for this bean into the resolvedValues object. * Resolve the constructor arguments for this bean into the resolvedValues object.
* This may involve looking up other beans. * This may involve looking up other beans.
* This method is also used for handling invocations of static factory methods. * <p>This method is also used for handling invocations of static factory methods.
*/ */
private int resolveConstructorArguments( private int resolveConstructorArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw,
String beanName, RootBeanDefinition mbd, BeanWrapper bw,
ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) { ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) {
TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2014 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.
@ -29,55 +29,56 @@ import org.springframework.beans.factory.BeanFactory;
* including using CGLIB to create subclasses on the fly to support Method Injection. * including using CGLIB to create subclasses on the fly to support Method Injection.
* *
* @author Rod Johnson * @author Rod Johnson
* @author Juergen Hoeller
* @since 1.1 * @since 1.1
*/ */
public interface InstantiationStrategy { public interface InstantiationStrategy {
/** /**
* Return an instance of the bean with the given name in this factory. * Return an instance of the bean with the given name in this factory.
* @param beanDefinition the bean definition * @param bd the bean definition
* @param beanName name of the bean when it's created in this context. * @param beanName the name of the bean when it's created in this context.
* The name can be {@code null} if we're autowiring a bean that * The name can be {@code null} if we're autowiring a bean which doesn't
* doesn't belong to the factory. * belong to the factory.
* @param owner owning BeanFactory * @param owner the owning BeanFactory
* @return a bean instance for this bean definition * @return a bean instance for this bean definition
* @throws BeansException if the instantiation failed * @throws BeansException if the instantiation attempt failed
*/ */
Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner)
throws BeansException; throws BeansException;
/** /**
* Return an instance of the bean with the given name in this factory, * Return an instance of the bean with the given name in this factory,
* creating it via the given constructor. * creating it via the given constructor.
* @param beanDefinition the bean definition * @param bd the bean definition
* @param beanName name of the bean when it's created in this context. * @param beanName the name of the bean when it's created in this context.
* The name can be {@code null} if we're autowiring a bean * The name can be {@code null} if we're autowiring a bean which doesn't
* that doesn't belong to the factory. * belong to the factory.
* @param owner owning BeanFactory * @param owner the owning BeanFactory
* @param ctor the constructor to use * @param ctor the constructor to use
* @param args the constructor arguments to apply * @param args the constructor arguments to apply
* @return a bean instance for this bean definition * @return a bean instance for this bean definition
* @throws BeansException if the instantiation failed * @throws BeansException if the instantiation attempt failed
*/ */
Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner,
Constructor<?> ctor, Object[] args) throws BeansException; Constructor<?> ctor, Object... args) throws BeansException;
/** /**
* Return an instance of the bean with the given name in this factory, * Return an instance of the bean with the given name in this factory,
* creating it via the given factory method. * creating it via the given factory method.
* @param beanDefinition bean definition * @param bd the bean definition
* @param beanName name of the bean when it's created in this context. * @param beanName the name of the bean when it's created in this context.
* The name can be {@code null} if we're autowiring a bean * The name can be {@code null} if we're autowiring a bean which doesn't
* that doesn't belong to the factory. * belong to the factory.
* @param owner owning BeanFactory * @param owner the owning BeanFactory
* @param factoryBean the factory bean instance to call the factory method on, * @param factoryBean the factory bean instance to call the factory method on,
* or {@code null} in case of a static factory method * or {@code null} in case of a static factory method
* @param factoryMethod the factory method to use * @param factoryMethod the factory method to use
* @param args the factory method arguments to apply * @param args the factory method arguments to apply
* @return a bean instance for this bean definition * @return a bean instance for this bean definition
* @throws BeansException if the instantiation failed * @throws BeansException if the instantiation attempt failed
*/ */
Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner,
Object factoryBean, Method factoryMethod, Object[] args) throws BeansException; Object factoryBean, Method factoryMethod, Object... args) throws BeansException;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2012 the original author or authors. * Copyright 2002-2014 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.
@ -25,8 +25,8 @@ import java.security.PrivilegedExceptionAction;
import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -56,14 +56,14 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
@Override @Override
public Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) { public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
// Don't override the class with CGLIB if no overrides. // Don't override the class with CGLIB if no overrides.
if (beanDefinition.getMethodOverrides().isEmpty()) { if (bd.getMethodOverrides().isEmpty()) {
Constructor<?> constructorToUse; Constructor<?> constructorToUse;
synchronized (beanDefinition.constructorArgumentLock) { synchronized (bd.constructorArgumentLock) {
constructorToUse = (Constructor<?>) beanDefinition.resolvedConstructorOrFactoryMethod; constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
if (constructorToUse == null) { if (constructorToUse == null) {
final Class<?> clazz = beanDefinition.getBeanClass(); final Class<?> clazz = bd.getBeanClass();
if (clazz.isInterface()) { if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface"); throw new BeanInstantiationException(clazz, "Specified class is an interface");
} }
@ -79,7 +79,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
else { else {
constructorToUse = clazz.getDeclaredConstructor((Class[]) null); constructorToUse = clazz.getDeclaredConstructor((Class[]) null);
} }
beanDefinition.resolvedConstructorOrFactoryMethod = constructorToUse; bd.resolvedConstructorOrFactoryMethod = constructorToUse;
} }
catch (Exception ex) { catch (Exception ex) {
throw new BeanInstantiationException(clazz, "No default constructor found", ex); throw new BeanInstantiationException(clazz, "No default constructor found", ex);
@ -90,7 +90,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
} }
else { else {
// Must generate CGLIB subclass. // Must generate CGLIB subclass.
return instantiateWithMethodInjection(beanDefinition, beanName, owner); return instantiateWithMethodInjection(bd, beanName, owner);
} }
} }
@ -100,18 +100,15 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
* the Method Injection specified in the given RootBeanDefinition. * the Method Injection specified in the given RootBeanDefinition.
* Instantiation should use a no-arg constructor. * Instantiation should use a no-arg constructor.
*/ */
protected Object instantiateWithMethodInjection( protected Object instantiateWithMethodInjection(RootBeanDefinition bd, String beanName, BeanFactory owner) {
RootBeanDefinition beanDefinition, String beanName, BeanFactory owner) { throw new UnsupportedOperationException("Method Injection not supported in SimpleInstantiationStrategy");
throw new UnsupportedOperationException(
"Method Injection not supported in SimpleInstantiationStrategy");
} }
@Override @Override
public Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner,
final Constructor<?> ctor, Object[] args) { final Constructor<?> ctor, Object... args) {
if (beanDefinition.getMethodOverrides().isEmpty()) { if (bd.getMethodOverrides().isEmpty()) {
if (System.getSecurityManager() != null) { if (System.getSecurityManager() != null) {
// use own privileged to change accessibility (when security is on) // use own privileged to change accessibility (when security is on)
AccessController.doPrivileged(new PrivilegedAction<Object>() { AccessController.doPrivileged(new PrivilegedAction<Object>() {
@ -125,7 +122,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
return BeanUtils.instantiateClass(ctor, args); return BeanUtils.instantiateClass(ctor, args);
} }
else { else {
return instantiateWithMethodInjection(beanDefinition, beanName, owner, ctor, args); return instantiateWithMethodInjection(bd, beanName, owner, ctor, args);
} }
} }
@ -135,16 +132,15 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
* the Method Injection specified in the given RootBeanDefinition. * the Method Injection specified in the given RootBeanDefinition.
* Instantiation should use the given constructor and parameters. * Instantiation should use the given constructor and parameters.
*/ */
protected Object instantiateWithMethodInjection(RootBeanDefinition beanDefinition, protected Object instantiateWithMethodInjection(RootBeanDefinition bd, String beanName, BeanFactory owner,
String beanName, BeanFactory owner, Constructor<?> ctor, Object[] args) { Constructor<?> ctor, Object... args) {
throw new UnsupportedOperationException( throw new UnsupportedOperationException("Method Injection not supported in SimpleInstantiationStrategy");
"Method Injection not supported in SimpleInstantiationStrategy");
} }
@Override @Override
public Object instantiate(RootBeanDefinition beanDefinition, String beanName, BeanFactory owner, public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner,
Object factoryBean, final Method factoryMethod, Object[] args) { Object factoryBean, final Method factoryMethod, Object... args) {
try { try {
if (System.getSecurityManager() != null) { if (System.getSecurityManager() != null) {
@ -175,17 +171,22 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy {
} }
} }
catch (IllegalArgumentException ex) { catch (IllegalArgumentException ex) {
throw new BeanDefinitionStoreException( throw new BeanInstantiationException(factoryMethod.getReturnType(),
"Illegal arguments to factory method [" + factoryMethod + "]; " + "Illegal arguments to factory method '" + factoryMethod.getName() + "'; " +
"args: " + StringUtils.arrayToCommaDelimitedString(args)); "args: " + StringUtils.arrayToCommaDelimitedString(args), ex);
} }
catch (IllegalAccessException ex) { catch (IllegalAccessException ex) {
throw new BeanDefinitionStoreException( throw new BeanInstantiationException(factoryMethod.getReturnType(),
"Cannot access factory method [" + factoryMethod + "]; is it public?"); "Cannot access factory method '" + factoryMethod.getName() + "'; is it public?", ex);
} }
catch (InvocationTargetException ex) { catch (InvocationTargetException ex) {
throw new BeanDefinitionStoreException( String msg = "Factory method '" + factoryMethod.getName() + "' threw exception";
"Factory method [" + factoryMethod + "] threw exception", ex.getTargetException()); if (bd.getFactoryBeanName() != null && owner instanceof ConfigurableBeanFactory &&
((ConfigurableBeanFactory) owner).isCurrentlyInCreation(bd.getFactoryBeanName())) {
msg = "Circular reference involving containing bean '" + bd.getFactoryBeanName() + "' - consider " +
"declaring the factory method as static for independence from its containing instance. " + msg;
}
throw new BeanInstantiationException(factoryMethod.getReturnType(), msg, ex.getTargetException());
} }
} }

View File

@ -27,6 +27,7 @@ import org.junit.Test;
import org.springframework.aop.scope.ScopedObject; import org.springframework.aop.scope.ScopedObject;
import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -429,6 +430,34 @@ public class ConfigurationClassPostProcessorTests {
beanFactory.preInstantiateSingletons(); beanFactory.preInstantiateSingletons();
} }
@Test
public void testCircularDependency() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(beanFactory);
beanFactory.addBeanPostProcessor(bpp);
beanFactory.registerBeanDefinition("configClass1", new RootBeanDefinition(A.class));
beanFactory.registerBeanDefinition("configClass2", new RootBeanDefinition(AStrich.class));
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
try {
beanFactory.preInstantiateSingletons();
fail("Should have thrown BeanCreationException");
}
catch (BeanCreationException ex) {
assertTrue(ex.getMessage().contains("Circular reference"));
}
}
@Test
public void testCircularDependencyWithApplicationContext() {
try {
new AnnotationConfigApplicationContext(A.class, AStrich.class);
fail("Should have thrown BeanCreationException");
}
catch (BeanCreationException ex) {
assertTrue(ex.getMessage().contains("Circular reference"));
}
}
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -849,5 +878,41 @@ public class ConfigurationClassPostProcessorTests {
return new ServiceBean("message"); return new ServiceBean("message");
} }
} }
}
@Configuration
public static class A {
@Autowired(required=true)
Z z;
@Bean
public B b() {
if (z == null) {
throw new NullPointerException("z is null");
}
return new B(z);
}
}
@Configuration
public static class AStrich {
@Autowired
B b;
@Bean
public Z z() {
return new Z();
}
}
public static class B {
public B(Z z) {
}
}
public static class Z {
}
}