Uno dei motivi è la testabilità. Dì che hai questa lezione:
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"));
}
}
Come puoi testare questo fagiolo? Ad esempio in questo modo:
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);
}
}
Facile vero?
Mentre continui a dipendere da Spring (a causa delle annotazioni) puoi rimuovere la tua dipendenza da spring senza cambiare alcun codice (solo le definizioni delle annotazioni) e lo sviluppatore del test non ha bisogno di sapere come funziona Spring (forse dovrebbe comunque, ma permette di rivedere e testare il codice separatamente da quello che fa Spring).
È ancora possibile fare lo stesso quando si utilizza ApplicationContext. Tuttavia, è necessario prendere in giro ApplicationContext
che è un'interfaccia enorme. O hai bisogno di un'implementazione fittizia o puoi usare un framework beffardo come 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);
}
}
Questa è una possibilità, ma penso che la maggior parte delle persone concorderebbe sul fatto che la prima opzione è più elegante e semplifica il test.
L'unica opzione che è davvero un problema è questa:
@Component
class MyBean {
@Autowired
MyBean(StringOutput out) {
out.print(new HttpLoader().load("http://stackoverflow.com"));
}
}
Il test richiede enormi sforzi o il bean tenterà di connettersi allo stackoverflow su ogni test. E non appena si verifica un errore di rete (o gli amministratori di StackOverflow ti bloccano a causa di un tasso di accesso eccessivo), i test falliranno casualmente.
Quindi, in conclusione, non direi che l'uso ApplicationContext
diretto è automaticamente sbagliato e dovrebbe essere evitato a tutti i costi. Tuttavia, se esistono opzioni migliori (e nella maggior parte dei casi), utilizzare le opzioni migliori.
new MyOtherClass()
oggetto? So di @Autowired, ma l'ho mai usato solo sui campi e si rompe sunew MyOtherClass()
..