Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to inject a bean defined with @Component as an argument to a BeanFactoryPostProcessor?

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.

like image 373
jontejj Avatar asked May 15 '13 15:05

jontejj


People also ask

Can we use @component instead of @bean?

@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.

Does @component creates a bean?

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.

Where can I use @bean annotation?

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.

What is @component annotation in Spring boot?

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.


2 Answers

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 annotations
  • AutowiredAnnotationBeanPostProcessor - responsible for @Autowired and @Value annotations
  • ...and many more...

So 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 BeanPostProcessors .


Workaround for your example might be to create ApplicationContext hierarchy as you have suggested:

  • Each context initializes and applies its own post processor infrastructure, where you still can reference dependencies from parent contexts.

Other approaches might be (which I would prefer):

  • Use BeanFactoryAware interface on your @Component bean and pull your dependency yourself (as Spring will not inject it).
  • Define beans connected with BeanFactoryPostProcessors within context configuration XML or @Configuration (i.e. don't use @Component for these beans).
like image 147
Pavel Horal Avatar answered Oct 21 '22 15:10

Pavel Horal


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.

like image 33
jontejj Avatar answered Oct 21 '22 17:10

jontejj