Я столкнулся с проблемой, которую можно объяснить только моим фундаментальным отсутствием понимания возможностей контейнера IoC Spring и настройки контекста, поэтому я бы попросил разъяснений по этому поводу.
Просто для справки, приложение, которое я поддерживаю, имеет следующий набор технологий:
- Java 1.6
- Пружина 2.5.6
- RichFaces 3.3.1-GA UI
- Spring Framework используется для управления компонентами с модулем Spring JDBC, используемым для поддержки DAO
- Maven используется в качестве менеджера сборки
- JUnit 4.4 теперь представлен как тестовый движок
Я задним числом (sic!) Пишу тесты JUnit для приложения, и меня удивило то, что я не смог внедрить bean-компонент в тестовый класс, используя инъекцию сеттера без обращения к нотации @Autowire.
Позвольте мне предоставить пример настройки и сопровождающие файлы конфигурации.
Тестовый класс TypeTest
действительно прост:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest {
@Autowired
private IType type;
@Test
public void testFindAllTypes() {
List<Type> result;
try {
result = type.findAlltTypes();
assertNotNull(result);
} catch (Exception e) {
e.printStackTrace();
fail("Exception caught with " + e.getMessage());
}
}
}
Его контекст определен в TestStackOverflowExample-context.xml
:
<context:property-placeholder location="classpath:testContext.properties" />
<context:annotation-config />
<tx:annotation-driven />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${db.connection.driver.class}" />
<property name="url" value="${db.connection.url}" />
<property name="username" value="${db.connection.username}" />
<property name="password" value="${db.connection.password}" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="beanDAO" class="com.example.BeanDAOImpl">
<property name="ds" ref="dataSource"></property>
<property name="beanDAOTwo" ref="beanDAOTwo"></property>
</bean>
<bean id="beanDAOTwo" class="com.example.BeanDAOTwoImpl">
<property name="ds" ref="dataSource"></property>
</bean>
<bean id="type" class="com.example.TypeImpl">
<property name="beanDAO" ref="beanDAO"></property>
</bean>
TestContext.properties
находится в classpath и содержит только специфичные для БД данные, необходимые для источника данных.
Это работает как талисман, но мой вопрос - почему это не работает, когда я пытаюсь вручную подключить бины и выполнить установку сеттера, как в:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest {
private IType type;
public IType getType () {
return type;
}
public void setType(IType type) {
this.type= type;
}
@Test
public void testFindAllTypes(){
//snip, snip...
}
}
Что мне здесь не хватает? Какая часть конфигурации здесь не так? Когда я пытаюсь вручную ввести bean-компоненты с помощью сеттеров, тест завершается неудачно, потому что эта часть
result = type.findAlltTypes();
разрешается как ноль во время выполнения. Я, конечно, обратился к справочному руководству Spring и попробовал различные комбинации конфигурации XML; все, что я могу сделать, - это то, что Spring не смог внедрить bean-компоненты, потому что он почему-то не может правильно разыменовать ссылку на контекст Spring Test Context, но с помощью @Autowired это происходит «автоматически», и я действительно не могу понять, почему это происходит, потому что JavaDoc обоих Autowired
аннотация и класс PostProcessor
этого не упоминают.
Также стоит добавить, что @Autowired
используется в приложении только здесь. В других местах выполняется только ручное подключение, поэтому возникает вопрос - почему он работает там , а не здесь , в моем тесте? Какую часть конфигурации DI я пропускаю? Как @Autowired
получить ссылку на Spring Context?
EDIT:
Я тоже пробовал это, но с такими же результатами:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest implements ApplicationContextAware{
private IType type;
private ApplicationContext ctx;
public TypeTest(){
super();
ctx = new FileSystemXmlApplicationContext("/TypeTest-context.xml");
ctx.getBean("type");
}
public IType getType () {
return type;
}
public void setType(IType type) {
this.type= type;
}
@Test
public void testFindAllTypes(){
//snip, snip...
}
}
Возможно, есть еще идеи?
EDIT2:
Я нашел способ, не прибегая к написанию собственных TestContextListener
или BeanPostProcessor
. Это удивительно просто, и оказывается, что я был на правильном пути с моим последним редактированием:
1) Разрешение контекста на основе конструктора:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest{
private IType type;
private ApplicationContext ctx;
public TypeTest(){
super();
ctx = new FileSystemXmlApplicationContext("/TypeTest-context.xml");
type = ctx.getBean("type");
}
public IType getType () {
return type;
}
public void setType(IType type) {
this.type= type;
}
@Test
public void testFindAllTypes(){
//snip, snip...
}
}
2) Реализация интерфейса ApplicationContextAware:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TypeTest implements ApplicationContextAware{
private IType type;
private ApplicationContext ctx;
public IType getType () {
return type;
}
public void setType(IType type) {
this.type= type;
}
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.ctx = ctx;
type = (Type) ctx.getBean("type");
}
@Test
public void testFindAllTypes(){
//snip, snip...
}
}
В обоих этих подходах правильно реализованы компоненты.