Einer der Gründe dafür ist die Testbarkeit. Nehmen wir an, Sie haben diese Klasse:
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"));
}
}
Wie können Sie diese Bohne testen? Z.B. so:
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);
}
}
Einfach, nicht wahr?
Während man immer noch von Spring abhängt (aufgrund der Annotationen), kann man die Abhängigkeit von Spring aufheben, ohne den Code zu ändern (nur die Annotationsdefinitionen), und der Testentwickler muss nichts über die Funktionsweise von Spring wissen (vielleicht sollte er das sowieso, aber es ermöglicht die Überprüfung und den Test des Codes unabhängig davon, was Spring tut).
Es ist immer noch möglich, dasselbe zu tun, wenn man den ApplicationContext verwendet. Allerdings müssen Sie dann den Mock ApplicationContext
was eine riesige Schnittstelle ist. Sie benötigen entweder eine Dummy-Implementierung oder Sie können ein Mocking-Framework wie Mockito verwenden:
@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);
}
}
Das ist durchaus eine Möglichkeit, aber ich denke, die meisten Leute würden zustimmen, dass die erste Option eleganter ist und den Test einfacher macht.
Die einzige Option, die wirklich ein Problem darstellt, ist diese:
@Component
class MyBean {
@Autowired
MyBean(StringOutput out) {
out.print(new HttpLoader().load("http://stackoverflow.com"));
}
}
Dies zu testen, erfordert einen enormen Aufwand, da Ihre Bohne sonst bei jedem Test versuchen wird, eine Verbindung zu stackoverflow herzustellen. Und sobald ein Netzwerkfehler auftritt (oder die Admins von Stackoverflow die Verbindung wegen zu hoher Zugriffsraten sperren), werden die Tests zufällig fehlschlagen.
Als Schlussfolgerung würde ich also nicht sagen, dass die Verwendung des ApplicationContext
ist automatisch falsch und sollte unter allen Umständen vermieden werden. Wenn es jedoch bessere Möglichkeiten gibt (und die gibt es in den meisten Fällen), dann sollten Sie die besseren Möglichkeiten nutzen.