是否可以将使用@Component定义的bean作为参数注入BeanFactoryPostProcessor?

时间:2013-05-15 15:25:31

标签: java spring dependency-injection annotations applicationcontext

如果需要,需要哪种配置?这不推荐吗?

带注释的类:

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;
    }
}

失败的依赖项bean:

package com.springbug.beanfactorydependencyissue;

import org.springframework.stereotype.Component;

@Component
public class DependencyBean {

}

测试用例:

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();
    }
}

具有“错误”依赖性的自定义BeanFactoryPostProcessor:

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>

为什么BeanFactoryPostProcessorConfiguration无法引用DependantBean

DependantBean中生成的AppTest实例不为空,即它是由spring创建的,但其依赖项(DependencyBean)为空。 Spring没有抱怨的事实让我相信这是春天的一个错误。是否应该支持这种用例?

是的,我正在使用spring - * - 3.1.1.RELEASE.jar 顺便说一句2:重现错误的代码也可以找到here

4 个答案:

答案 0 :(得分:5)

也许更简单和描述性的答案:

是的,可以将@Component bean用作BeanFactoryPostProcessor依赖项。

然而,在BeanFactoryPostProcessor处于活动状态之前,BeanPostProcessor的每个依赖关系都将被实例化。这些包括:

  • CommonAnnotationBeanPostProcessor - 负责@PostConstruct@Resource和其他一些注释
  • AutowiredAnnotationBeanPostProcessor - 负责@Autowired@Value注释
  • ......还有更多......

所以总结一下:

是的,可以使用@Component bean作为BeanFactoryPostProcessor依赖项,但它们不能使用基于注释的注入(@Autowired@Resource,{{ 1}},...)以及@WebServiceRef s。

提供的其他功能

您的示例的解决方法可能是按照您的建议创建BeanPostProcessor层次结构:

  • 每个上下文都初始化并应用自己的后处理器基础结构,您仍然可以从父上下文引用依赖项。

其他方法可能(我更喜欢):

  • ApplicationContext bean上使用BeanFactoryAware接口并自行提取依赖关系(因为Spring不会注入它)。
  • 在上下文配置@ComponentBeanFactoryPostProcessor中定义与XML相关联的bean(即不要对这些bean使用@Configuration)。

答案 1 :(得分:3)

感谢对Spring的一些认真调试,我们发现了DependantBean参数 到BeanFactoryPostProcessorConfiguration引起了其他(缝合不相关的)bean的急切初始化。 但是当春天处于BeanFactoryPostProcessor阶段时,BeanPostProcessors尚未准备就绪。

阅读BeanFactoryPostProcessor的javadoc(感谢@Pavel指出这一点)完全解释了这个问题:

  

BeanFactoryPostProcessor可以与bean定义交互并修改bean定义,但绝不能与bean实例交互。   这样做可能会导致bean过早实例化,违反容器并导致意外的副作用。   如果需要bean实例交互,请考虑实现{@link BeanPostProcessor}。

解决方案:

稍加修改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>

新的bootstrapContext.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.bootstrap" />
</beans>

Contexts.java :(注意引导程序是常规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();
    }
}

BeanFactoryPostProcessorConfiguration没有DependantBean作为参数:

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
            {

            }
        };
    }
}

让它工作的最后一件事是将DependantBeanDependencyBean移到bootstrap包中。 实现了从数据库中读取@Value属性的目标。在重用bean的旧定义而不重复bean时。

答案 2 :(得分:0)

你需要像这样给你的组件一个id

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

然后使用引用

 <ref bean="myClass">

答案 3 :(得分:0)

尝试使用Spring Util名称空间并指定value-type。请参阅此question