Можно ли ввести Боб, определенный с помощью @Component, в качестве аргумента в BeanFactoryPostProcessor?


И если да, то какая конфигурация необходима? Разве это не рекомендуется?

Аннотированный класс:

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

Боб зависимости, который терпит неудачу:

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

Пользовательский 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 - *-3.1.1.ОСВОБОЖДАТЬ.кувшин Кстати 2: Код для воспроизведения ошибки также можно найти здесь .

4 7

4 ответа:

Может быть, более простой и описательный ответ:

Да, можно использовать @Component bean как BeanFactoryPostProcessor зависимость.

Однако каждая зависимость BeanFactoryPostProcessor будет создана до того, как любая BeanPostProcessor будет активна. И к ним относятся:

  • CommonAnnotationBeanPostProcessor - ответственный за @PostConstruct, @Resource и некоторые другие Примечания
  • AutowiredAnnotationBeanPostProcessor - ответственный за @Autowired и @Value аннотации
  • ...и многое другое...

Итак, ты суммируешь это вверх:

Да, можно использовать @Component bean как BeanFactoryPostProcessor зависимость, но они не могут использовать инъекцию на основе аннотаций (@Autowired, @Resource, @WebServiceRef, ...) и другие функции, предоставляемые BeanPostProcessors .


Обходным путем для вашего примера может быть создание иерархии ApplicationContext, как вы предложили:

  • каждый контекст инициализирует и применяет свою собственную инфраструктуру постпроцессора, где вы все еще можете ссылаться на зависимости от родителя контексты.

Другие подходы могут быть (которые я предпочел бы):

  • используйте BeanFactoryAware интерфейс на вашем @Component Бобе и вытяните свою зависимость самостоятельно (так как Spring не будет вводить ее).
  • определите бобы, связанные с BeanFactoryPostProcessors в конфигурации контекста XML или @Configuration (т. е. не используйте @Component для этих бобов).

Благодаря серьезной отладке spring мы обнаружили, что параметр DependantBean to BeanFactoryPostProcessorConfiguration вызвал нетерпеливую инициализацию других (явно не связанных между собой) бобов. Но так как весна была в стадии BeanFactoryPostProcessor, то BeanPostProcessors не были готовы.

Чтение javadoc для BeanFactoryPostProcessor (спасибо @Pavel за указание на это) точно объясняет проблему:

BeanFactoryPostProcessor может взаимодействовать с определениями 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: (обратите внимание, что bootstrap является родительским контекстом для обычного 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
            {

            }
        };
    }
}
Последнее, что заставило его работать, - это переместить DependantBean и DependencyBean в пакет bootstrap. Цель была достигнута, чтобы прочитать свойства @Value из базы данных. При повторном использовании старых определений бобов и без дублирования бобов.

Вы должны дать идентификатор вашему компоненту вот так

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

Попробуйте использовать пространство имен Spring Util и укажите тип значения. Обратитесь к этому вопросу