Closed
Description
Affects: 5.1.4.RELEASE
This relates to spring-projects/spring-boot#15898.
When a FactoryBean
is defined via component scanning, it is instantiated more aggressively than when it is defined via a @Bean
method. The following tests should illustrate the differing behaviour:
package com.example.demo;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
public class AggressiveFactoryBeanInstantiationTests {
@Test
public void componentScannedFactoryBean() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(ComponentScanConfiguration.class);
context.addBeanFactoryPostProcessor((factory) -> {
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, String.class);
});
context.refresh();
}
}
@Test
public void beanMethodFactoryBean() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(BeanMethodConfiguration.class);
context.addBeanFactoryPostProcessor((factory) -> {
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(factory, String.class);
});
context.refresh();
}
}
@Configuration
static class BeanMethodConfiguration {
@Bean
public SimpleFactoryBean simpleFactoryBean(ApplicationContext applicationContext) {
return new SimpleFactoryBean(applicationContext);
}
}
@Configuration
@ComponentScan
static class ComponentScanConfiguration {
}
@Component
static class SimpleFactoryBean implements FactoryBean<Object> {
public SimpleFactoryBean(ApplicationContext applicationContext) {
}
@Override
public Object getObject() throws Exception {
return new Object();
}
@Override
public Class<?> getObjectType() {
return Object.class;
}
}
}
beanMethodFactoryBean
will successfully refresh the application context. componentScannedFactoryBean
fails as an attempt is made to create SimpleFactoryBean
using a non-existent default constructor:
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: file [/Users/awilkinson/dev/workspaces/spring-projects/spring-boot/2.1.x/gh-15898/target/test-classes/com/example/demo/AggressiveFactoryBeanInstantiationTests$BeanMethodConfiguration.class]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'aggressiveFactoryBeanInstantiationTests.SimpleFactoryBean' defined in file [/Users/awilkinson/dev/workspaces/spring-projects/spring-boot/2.1.x/gh-15898/target/test-classes/com/example/demo/AggressiveFactoryBeanInstantiationTests$SimpleFactoryBean.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.demo.AggressiveFactoryBeanInstantiationTests$SimpleFactoryBean]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.example.demo.AggressiveFactoryBeanInstantiationTests$SimpleFactoryBean.<init>()
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:454)
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:316)
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:275)
at org.springframework.context.annotation.ComponentScanAnnotationParser.parse(ComponentScanAnnotationParser.java:132)
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:287)
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:242)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:191)
at org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:295)
at org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:242)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:199)
at org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:167)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:315)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:691)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:528)
at com.example.demo.AggressiveFactoryBeanInstantiationTests.componentScannedFactoryBean(AggressiveFactoryBeanInstantiationTests.java:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'aggressiveFactoryBeanInstantiationTests.SimpleFactoryBean' defined in file [/Users/awilkinson/dev/workspaces/spring-projects/spring-boot/2.1.x/gh-15898/target/test-classes/com/example/demo/AggressiveFactoryBeanInstantiationTests$SimpleFactoryBean.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.demo.AggressiveFactoryBeanInstantiationTests$SimpleFactoryBean]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.example.demo.AggressiveFactoryBeanInstantiationTests$SimpleFactoryBean.<init>()
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1270)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1164)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getSingletonFactoryBeanForTypeCheck(AbstractAutowireCapableBeanFactory.java:974)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getTypeForFactoryBean(AbstractAutowireCapableBeanFactory.java:848)
at org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch(AbstractBeanFactory.java:574)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:514)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:477)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:598)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:590)
at org.springframework.boot.context.TypeExcludeFilter.match(TypeExcludeFilter.java:65)
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.isCandidateComponent(ClassPathScanningCandidateComponentProvider.java:492)
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.scanCandidateComponents(ClassPathScanningCandidateComponentProvider.java:431)
... 40 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.demo.AggressiveFactoryBeanInstantiationTests$SimpleFactoryBean]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.example.demo.AggressiveFactoryBeanInstantiationTests$SimpleFactoryBean.<init>()
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:83)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1262)
... 51 more
Caused by: java.lang.NoSuchMethodException: com.example.demo.AggressiveFactoryBeanInstantiationTests$SimpleFactoryBean.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:78)
... 52 more
In the @Bean
method case, the attempt to determine the type produced by the FactoryBean
is abandoned earlier. As far as I can tell that's due to the following logic: