9

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.

jontejj
  • 2,800
  • 1
  • 25
  • 27
  • There is something fishy going on in CommonAnnotationBeanPostProcessor. Can you switch to @Autowired annotations instead of @Resource? Does it help? – Pavel Horal May 16 '13 at 03:06
  • Good theory but myDependency is still null. I'm surprised that spring eats it up and just continue without even reporting a failed injection. – jontejj May 16 '13 at 07:06
  • When it works `ApplicationContextAwareProcessor AbstractApplicationContext$BeanPostProcessorChecker ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor CamelBeanPostProcessor CoreNamespacePostProcessor CommonAnnotationBeanPostProcessor AutowiredAnnotationBeanPostProcessor RequiredAnnotationBeanPostProcessor AbstractApplicationContext$ApplicationListenerDetector` are registered as bean processors but when it doesn't only `ApplicationContextAwareProcessor` is registered. – jontejj May 16 '13 at 10:58
  • Random findings now include that `testClass` is not part of `hashCode` or `equals` in `MergedContextConfiguration` – jontejj May 16 '13 at 11:45
  • There are some non-standard post processors. So the example in the question is not complete. Are you sure you are not in jar-hell situation? Are you using Maven? – Pavel Horal May 16 '13 at 16:47
  • Yeah, I should have mentioned that this code is only an extract and that this is what I've tried with to recreate the issue. There may be something else in the codebase that is causing the issue. We're using ant (much to my annoyance). What do you mean with jar-hell? That several versions of Spring is on the classpath? – jontejj May 17 '13 at 07:02
  • I've tried to put a breakpoint in the MyClass constructor and it's only executed once. – jontejj May 17 '13 at 07:05
  • "That several versions of Spring is on the classpath?" Yes... I will try to reproduce this myself based on your information. Looks very strange. – Pavel Horal May 17 '13 at 07:08
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/30111/discussion-between-jontejj-and-pavel-horal) – jontejj May 17 '13 at 07:31

4 Answers4

7

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).
Pavel Horal
  • 17,782
  • 3
  • 65
  • 89
  • Great suggestions. The parent application context solution worked like a charm as we could reuse existing bean definitions. If the project weren't so big I might have gone with your suggestions though. – jontejj May 28 '13 at 21:54
3

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.

jontejj
  • 2,800
  • 1
  • 25
  • 27
0

You need to give an id to your component like this

 @Component("myClass")
 public class MyClass implements MyInterface
  {
   @Resource
   private MyDependency myDependency; //Isn't initialized correctly when listOfMyClassBeans references myClass

   //Implementation skipped for brevity's sake...
  }

and then use the reference

 <ref bean="myClass">
NullPointerException
  • 3,732
  • 5
  • 28
  • 62
0

Try using Spring Util name space and specify value-type. Refer to this question

Community
  • 1
  • 1
FourOfAKind
  • 2,298
  • 5
  • 31
  • 34
  • Sure a better typed list is better but why would that solve this issue? The bean is referenced to with "myClass" which isn't ambiguous. – jontejj May 15 '13 at 21:53