Jakub Danek opened SPR-15616 and commented
While writing XML configuration parser for spring session I ran into the following problem: beans implementing SmartLifecycle interface are not picked up by DefaultLifecycleProcessor when created via FactoryBean.
To my understanding, this is not correct behaviour and the issue lies in the two following methods:
DefaultLifecycleProcessor.getLifecycleBeans()
protected Map<String, Lifecycle> getLifecycleBeans() {
Map<String, Lifecycle> beans = new LinkedHashMap<>();
String[] beanNames = this.beanFactory.getBeanNamesForType(Lifecycle.class, false, false);
for (String beanName : beanNames) {
String beanNameToRegister = BeanFactoryUtils.transformedBeanName(beanName);
boolean isFactoryBean = this.beanFactory.isFactoryBean(beanNameToRegister);
String beanNameToCheck = (isFactoryBean ? BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName);
if ((this.beanFactory.containsSingleton(beanNameToRegister) &&
(!isFactoryBean || Lifecycle.class.isAssignableFrom(this.beanFactory.getType(beanNameToCheck)))) ||
SmartLifecycle.class.isAssignableFrom(this.beanFactory.getType(beanNameToCheck))) {
Lifecycle bean = this.beanFactory.getBean(beanNameToCheck, Lifecycle.class);
if (bean != this) {
beans.put(beanNameToRegister, bean);
}
}
}
return beans;
}
AbstractBeanFactory.getType(String name)
@Override
public Class<?> getType(String name) throws NoSuchBeanDefinitionException {
String beanName = transformedBeanName(name);
// Check manually registered singletons.
Object beanInstance = getSingleton(beanName, false);
if (beanInstance != null) {
if (beanInstance instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) {
return getTypeForFactoryBean((FactoryBean<?>) beanInstance);
}
else {
return beanInstance.getClass();
}
}
else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) {
// null instance registered
return null;
}
...
}
Given how the code adds and removes the factorybean name prefix, the condition beanInstance instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)
is false and therefore the method AbstractBeanFactory.getType returns the class of the factory instead of the class of the bean which the factory creates.
The spring-session master branch works with Spring Framework 5.0, the attached showcase project where the issue is reproduced uses Spring 4.3.5.
In case I haven't missed anything, and this really is a bug, I'm happy to fix the issue. Yet I'm not sure which of the two methods actually misbehaves :).
Affects: 4.3.5, 5.0 RC1
Attachments: - spring-issue.tar.gz (1.67 kB)
Comment From: dlipofsky
I've encounter the same problem in 4.3.18 and 4.3.22 - is there is a fix or work-around? I tried both SmartLifecycle
and ApplicationListener<ContextRefreshedEvent>
and in both cases it didn't get called when created by FactoryBean.getObject()
Comment From: snicoll
Thanks for the sample and sorry it took so long to look at it. I've upgraded the sample to the latest release and I can see indeed that the bean produced by the BeanFactory
cannot participate in the lifecycle. I was wondering if maybe the type exposed by the factorybean was not precise enough but that's not the case here.
@jhoeller any idea?
Comment From: snicoll
I've discussed this with @jhoeller and he reminded me that the managed instance is the FactoryBean
, not the instance that it produces. If you need lifecycle callbacks for the user-exposed handle, you can implement those on the FactoryBean
and delegate the call to it.
Comment From: Donzz
I've discussed this with @jhoeller and he reminded me that the managed instance is the
FactoryBean
, not the instance that it produces. If you need lifecycle callbacks for the user-exposed handle, you can implement those on theFactoryBean
and delegate the call to it.
It isn't obviously. There is no mention of FactoryBean in doc for getLifecycleBeans method. Also why are singleton non-FactoryBean instances managed and prototype non-FactoryBean instances not? Anyway it could be good to provide such implementation for non-singleton instances too.