And if so which configuration is needed? Is this not recommended?
The annotated class:
package com.springbug.beanfactorydependencyissue;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class DependantBean {
@Resource
DependencyBean dependencyBean; // Isn't initialized correctly
public DependencyBean getDependencyBean() {
return dependencyBean;
}
}
The dependency bean that fails:
package com.springbug.beanfactorydependencyissue;
import org.springframework.stereotype.Component;
@Component
public class DependencyBean {
}
Testcase:
package com.springbug.beanfactorydependencyissue;
import static org.fest.assertions.Assertions.assertThat;
import javax.annotation.Resource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.Test;
import com.springbug.beanfactorydependencyissue.DependantBean;
@ContextConfiguration(locations = "/applicationContext.xml")
public class AppTest extends AbstractTestNGSpringContextTests {
@Resource
private DependantBean annotatedBean;
@Test
public void testThatDependencyIsInjected() {
// Fails as dependency injection of annotatedBean.dependencyBean does not work
assertThat(annotatedBean.getDependencyBean()).isNotNull();
}
}
A custom BeanFactoryPostProcessor with the "faulty" dependency:
package com.springbug.beanfactorydependencyissue;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanFactoryPostProcessorConfiguration {
/**
* The {@link DependantBean} here causes the bug, can
* {@link BeanFactoryPostProcessor} have regular beans as dependencies?
*/
@Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor(
DependantBean dependantBean) {
return new BeanFactoryPostProcessor() {
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory)
throws BeansException {
}
};
}
}
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.springbug.beanfactorydependencyissue" />
</beans>
Why can't BeanFactoryPostProcessorConfiguration
reference DependantBean
?
The resulting DependantBean
instance in AppTest
is not null, i.e it's created by spring, but its dependencies (DependencyBean
) are null. The fact that Spring doesn't complain at all leads me to believe that this is a bug within spring. Should this use-case be supported or not?
Btw, I'm using spring-*-3.1.1.RELEASE.jar Btw 2: the code to reproduce the bug can also be found here.
@Component is a class-level annotation, but @Bean is at the method level, so @Component is only an option when a class's source code is editable. @Bean can always be used, but it's more verbose. @Component is compatible with Spring's auto-detection, but @Bean requires manual class instantiation.
No. It is used to explicitly declare a single bean, rather than letting Spring do it automatically. If any class is annotated with @Component it will be automatically detect by using classpath scan. We should use @bean, if you want specific implementation based on dynamic condition.
One of the most important annotations in spring is the @Bean annotation which is applied on a method to specify that it returns a bean to be managed by Spring context. Spring Bean annotation is usually declared in Configuration classes methods. This annotation is also a part of the spring core framework.
Spring Component annotation is used to denote a class as Component. It means that Spring framework will autodetect these classes for dependency injection when annotation-based configuration and classpath scanning is used.
Maybe more simpler and descriptive answer:
Yes, it is possible to use @Component
bean as BeanFactoryPostProcessor
dependency.
However every dependency of a BeanFactoryPostProcessor
will be instantiated before any BeanPostProcessor
is active. And these include:
CommonAnnotationBeanPostProcessor
- responsible for @PostConstruct
, @Resource
and some other annotationsAutowiredAnnotationBeanPostProcessor
- responsible for @Autowired
and @Value
annotationsSo tu sum it up:
Yes, it is possible to use @Component
bean as BeanFactoryPostProcessor
dependency, but they can not use annotation based injection (@Autowired
, @Resource
, @WebServiceRef
, ...) and other features provided by BeanPostProcessor
s .
Workaround for your example might be to create ApplicationContext
hierarchy as you have suggested:
Other approaches might be (which I would prefer):
BeanFactoryAware
interface on your @Component
bean and pull your dependency yourself (as Spring will not inject it).BeanFactoryPostProcessor
s within context configuration XML
or @Configuration
(i.e. don't use @Component
for these beans).Thanks to some serious debugging of spring we found out that the DependantBean
parameter
to BeanFactoryPostProcessorConfiguration
caused eager initialization of other (seamingly unrelated) beans.
But as spring was in the BeanFactoryPostProcessor
stage the BeanPostProcessors
weren't ready.
Reading the javadoc for BeanFactoryPostProcessor (Thanks to @Pavel for pointing this out) explains the issue exactly:
BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances. Doing so may cause premature bean instantiation, violating the container and causing unintended side-effects. If bean instance interaction is required, consider implementing {@link BeanPostProcessor} instead.
The solution:
The slightly modified applicationContext.xml
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.stackoverflow.springbug.beanfactorydependencyissue.other" />
</beans>
The new bootstrapContext.xml
: (Notice that only the packages differ)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.stackoverflow.springbug.beanfactorydependencyissue.bootstrap" />
</beans>
The new Contexts.java
: (Notice that bootstrap is parent context to the regular applicationContext)
package com.stackoverflow.springbug.beanfactorydependencyissue;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
public final class Contexts
{
private static Supplier<ApplicationContext> bootstrap = Suppliers.memoize(new Supplier<ApplicationContext>(){
public ApplicationContext get()
{
return new ClassPathXmlApplicationContext("/bootstrapContext.xml");
}
});
/**
* Context for beans that are needed before initializing of other beans.
*/
public static ApplicationContext bootstrap()
{
return bootstrap.get();
}
private static Supplier<ApplicationContext> applicationContext = Suppliers.memoize(new Supplier<ApplicationContext>(){
public ApplicationContext get()
{
return new ClassPathXmlApplicationContext(new String[]{"/applicationContext.xml"}, bootstrap());
}
});
public static ApplicationContext applicationContext()
{
return applicationContext.get();
}
}
The BeanFactoryPostProcessorConfiguration
without DependantBean
as a parameter:
package com.stackoverflow.springbug.beanfactorydependencyissue.other;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.stackoverflow.springbug.beanfactorydependencyissue.Contexts;
import com.stackoverflow.springbug.beanfactorydependencyissue.bootstrap.DependantBean;
@Configuration
public class BeanFactoryPostProcessorConfiguration
{
/**
* The {@link DependantBean} here caused the bug, {@link Contexts#bootstrap()} is used as a
* workaround.
*/
@Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor()
{
final DependantBean dependantBean = Contexts.bootstrap().getBean(DependantBean.class);
System.out.println(dependantBean.getDependencyBean());
return new BeanFactoryPostProcessor(){
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
}
};
}
}
The last thing to make it work was to move DependantBean
and DependencyBean
into the bootstrap
package.
The goal was achieved to read @Value
properties from the database. While reusing the old definitions of the beans and without duplicating the beans.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With