L'une des raisons est la testabilité. Disons que vous avez cette classe:
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"));
}
}
Comment pouvez-vous tester ce bean? Par exemple, comme ceci:
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, non?
Bien que vous dépendiez toujours de Spring (en raison des annotations), vous pouvez supprimer votre dépendance à Spring sans modifier aucun code (uniquement les définitions d'annotations) et le développeur de test n'a besoin de rien savoir sur le fonctionnement de Spring (peut-être qu'il devrait de toute façon, mais il permet de revoir et de tester le code séparément de ce que fait le ressort).
Il est toujours possible de faire de même lors de l'utilisation d'ApplicationContext. Cependant, vous devez vous moquer de ApplicationContext
ce qui est une énorme interface. Vous avez besoin d'une implémentation factice ou vous pouvez utiliser un cadre de simulation tel que 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);
}
}
C'est tout à fait une possibilité, mais je pense que la plupart des gens conviendraient que la première option est plus élégante et rend le test plus simple.
La seule option qui pose vraiment problème est celle-ci:
@Component
class MyBean {
@Autowired
MyBean(StringOutput out) {
out.print(new HttpLoader().load("http://stackoverflow.com"));
}
}
Tester cela nécessite des efforts énormes ou votre bean va tenter de se connecter à stackoverflow à chaque test. Et dès que vous avez une panne de réseau (ou que les administrateurs de stackoverflow vous bloquent en raison d'un taux d'accès excessif), vous aurez des tests qui échouent au hasard.
Donc, en guise de conclusion, je ne dirais pas que l'utilisation ApplicationContext
directe est automatiquement erronée et doit être évitée à tout prix. Cependant, s'il existe de meilleures options (et il y en a dans la plupart des cas), utilisez les meilleures options.
new MyOtherClass()
objet? Je connais @Autowired, mais je ne l'ai utilisé que sur des champs et il se décomposenew MyOtherClass()
..