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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16569091/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 23:19:54  来源:igfitidea点击:

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

javaspringdependency-injectionannotationsapplicationcontext

提问by jontejj

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:

失败的依赖bean:

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:

具有“错误”依赖项的自定义 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:

<?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 BeanFactoryPostProcessorConfigurationreference DependantBean?

为什么不能BeanFactoryPostProcessorConfiguration参考DependantBean

The resulting DependantBeaninstance in AppTestis 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?

DependantBeanin 中的结果实例AppTest不为空,即它是由 spring 创建的,但其依赖项 ( DependencyBean) 为空。Spring 根本没有抱怨的事实让我相信这是 Spring 中的一个错误。是否应该支持此用例?

Btw, I'm using spring-*-3.1.1.RELEASE.jar Btw 2: the code to reproduce the bug can also be found here.

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

采纳答案by jontejj

Thanks to some serious debugging of spring we found out that the DependantBeanparameter to BeanFactoryPostProcessorConfigurationcaused eager initialization of other (seamingly unrelated) beans. But as spring was in the BeanFactoryPostProcessorstage the BeanPostProcessorsweren't ready.

感谢 spring 的一些认真调试,我们发现该DependantBean参数BeanFactoryPostProcessorConfiguration导致其他(无缝无关)bean 的急切初始化。但随着春天的BeanFactoryPostProcessor到来,BeanPostProcessors他们还没有准备好。

Reading the javadoc for BeanFactoryPostProcessor(Thanks to @Pavel for pointing this out) explains the issue exactly:

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

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.

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

The solution:

解决方案:

The slightly modified applicationContext.xml:

稍作修改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)

新的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>

The new Contexts.java: (Notice that bootstrap is parent context to the regular applicationContext)

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

The BeanFactoryPostProcessorConfigurationwithout DependantBeanas a parameter:

BeanFactoryPostProcessorConfigurationDependantBean作为参数:

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 DependantBeanand DependencyBeaninto the bootstrappackage. The goal was achieved to read @Valueproperties from the database. While reusing the old definitions of the beans and without duplicating the beans.

使其工作的最后一件事是移动DependantBeanDependencyBean放入bootstrap包中。实现了@Value从数据库读取属性的目标。同时重用 bean 的旧定义而不复制 bean。

回答by Pavel Horal

Maybe more simpler and descriptive answer:

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

Yes, it is possible to use @Componentbean as BeanFactoryPostProcessordependency.

是的,可以使用@Componentbean 作为BeanFactoryPostProcessor依赖项。

However every dependency of a BeanFactoryPostProcessorwill be instantiated before any BeanPostProcessoris active. And these include:

但是, a 的每个依赖项BeanFactoryPostProcessor都将在 anyBeanPostProcessor处于活动状态之前被实例化。其中包括:

  • CommonAnnotationBeanPostProcessor- responsible for @PostConstruct, @Resourceand some other annotations
  • AutowiredAnnotationBeanPostProcessor- responsible for @Autowiredand @Valueannotations
  • ...and many more...
  • CommonAnnotationBeanPostProcessor- 负责@PostConstruct@Resource以及其他一些注释
  • AutowiredAnnotationBeanPostProcessor- 负责@Autowired@Value注释
  • ...还有很多...

So tu sum it up:

所以总结一下:

Yes, it is possible to use @Componentbean as BeanFactoryPostProcessordependency, but they can not use annotation based injection (@Autowired, @Resource, @WebServiceRef, ...) and other features provided by BeanPostProcessors .

是的,可以使用@Componentbean 作为BeanFactoryPostProcessor依赖项,但它们不能使用基于注解的注入(@Autowired, @Resource, @WebServiceRef, ...)和BeanPostProcessors提供的其他功能。



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

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

  • Each context initializes and applies its own post processorinfrastructure, where you still can reference dependencies from parent contexts.
  • 每个上下文都会初始化并应用自己的后处理器基础结构,您仍然可以在其中引用父上下文的依赖项。


Other approaches might be (which I would prefer):

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

  • Use BeanFactoryAwareinterface on your @Componentbean and pull your dependency yourself (as Spring will not inject it).
  • Define beans connected with BeanFactoryPostProcessors within context configuration XMLor @Configuration(i.e. don't use @Componentfor these beans).
  • BeanFactoryAware@Componentbean上使用interface 并自己拉取依赖项(因为 Spring 不会注入它)。
  • BeanFactoryPostProcessor在上下文配置中定义与s连接的beanXML@Configuration(即不@Component用于这些 bean)。

回答by NullPointerException

You need to give an id to your component like this

您需要像这样为您的组件提供一个 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...
  }

and then use the reference

然后使用参考

 <ref bean="myClass">

回答by FourOfAKind

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

尝试使用 Spring Util 命名空间并指定值类型。参考这个问题