Можно ли ввести Боб, определенный с помощью @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 ответа:
Может быть, более простой и описательный ответ:
Да, можно использовать
@Component
bean какBeanFactoryPostProcessor
зависимость.Однако каждая зависимость
BeanFactoryPostProcessor
будет создана до того, как любаяBeanPostProcessor
будет активна. И к ним относятся:
CommonAnnotationBeanPostProcessor
- ответственный за@PostConstruct
,@Resource
и некоторые другие ПримечанияAutowiredAnnotationBeanPostProcessor
- ответственный за@Autowired
и@Value
аннотации- ...и многое другое...
Итак, ты суммируешь это вверх:
Да, можно использовать
@Component
bean какBeanFactoryPostProcessor
зависимость, но они не могут использовать инъекцию на основе аннотаций (@Autowired
,@Resource
,@WebServiceRef
, ...) и другие функции, предоставляемыеBeanPostProcessor
s .
Обходным путем для вашего примера может быть создание иерархии
ApplicationContext
, как вы предложили:
- каждый контекст инициализирует и применяет свою собственную инфраструктуру постпроцессора, где вы все еще можете ссылаться на зависимости от родителя контексты.
Другие подходы могут быть (которые я предпочел бы):
- используйте
BeanFactoryAware
интерфейс на вашем@Component
Бобе и вытяните свою зависимость самостоятельно (так как Spring не будет вводить ее).- определите бобы, связанные с
BeanFactoryPostProcessor
s в конфигурации контекстаXML
или@Configuration
(т. е. не используйте@Component
для этих бобов).
Благодаря серьезной отладке spring мы обнаружили, что параметр
DependantBean
toBeanFactoryPostProcessorConfiguration
вызвал нетерпеливую инициализацию других (явно не связанных между собой) бобов. Но так как весна была в стадии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 и укажите тип значения. Обратитесь к этому вопросу