Одна из причин - тестируемость. Скажем, у вас есть этот класс:
interface HttpLoader {
String load(String url);
}
interface StringOutput {
void print(String txt);
}
@Component
class MyBean {
@Autowired
MyBean(HttpLoader loader, StringOutput out) {
out.print(loader.load("http://stackoverflow.com"));
}
}
Как вы можете проверить этот боб? Например, вот так:
class MyBeanTest {
public void creatingMyBean_writesStackoverflowPageToOutput() {
// setup
String stackOverflowHtml = "dummy";
StringBuilder result = new StringBuilder();
// execution
new MyBean(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get, result::append);
// evaluation
assertEquals(result.toString(), stackOverflowHtml);
}
}
Легко, правда?
Хотя вы по-прежнему зависите от Spring (из-за аннотаций), вы можете удалить свою зависимость от Spring без изменения какого-либо кода (только определения аннотаций), и разработчику теста не нужно ничего знать о том, как работает Spring (возможно, он должен это делать в любом случае, но это позволяет просматривать и тестировать код отдельно от того, что делает весна).
Это все еще возможно сделать то же самое при использовании ApplicationContext. Однако тогда вам нужно издеваться над ApplicationContext
огромным интерфейсом. Вам либо нужна фиктивная реализация, либо вы можете использовать фальшивый фреймворк, такой как Mockito:
@Component
class MyBean {
@Autowired
MyBean(ApplicationContext context) {
HttpLoader loader = context.getBean(HttpLoader.class);
StringOutput out = context.getBean(StringOutput.class);
out.print(loader.load("http://stackoverflow.com"));
}
}
class MyBeanTest {
public void creatingMyBean_writesStackoverflowPageToOutput() {
// setup
String stackOverflowHtml = "dummy";
StringBuilder result = new StringBuilder();
ApplicationContext context = Mockito.mock(ApplicationContext.class);
Mockito.when(context.getBean(HttpLoader.class))
.thenReturn(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get);
Mockito.when(context.getBean(StringOutput.class)).thenReturn(result::append);
// execution
new MyBean(context);
// evaluation
assertEquals(result.toString(), stackOverflowHtml);
}
}
Это вполне возможно, но я думаю, что большинство людей согласятся, что первый вариант более элегантный и упрощает тестирование.
Единственный вариант, который действительно является проблемой, это:
@Component
class MyBean {
@Autowired
MyBean(StringOutput out) {
out.print(new HttpLoader().load("http://stackoverflow.com"));
}
}
Тестирование этого требует огромных усилий, иначе ваш компонент будет пытаться подключиться к stackoverflow в каждом тесте. И как только у вас произойдет сбой в сети (или администраторы в stackoverflow блокируют вас из-за чрезмерной скорости доступа), у вас будут случайно провальные тесты.
Таким образом, в качестве заключения я бы не сказал, что использование ApplicationContext
напрямую автоматически неправильно и его следует избегать любой ценой. Однако, если есть лучшие варианты (и есть в большинстве случаев), используйте лучшие варианты.
new MyOtherClass()
объекта? Я знаю о @Autowired, но я когда-либо использовал его только на полях, и он разбивается наnew MyOtherClass()
...