Donc, depuis que j'utilise Spring, si je devais écrire un service comportant des dépendances, je procéderais comme suit:
@Component
public class SomeService {
@Autowired private SomeOtherService someOtherService;
}
J'ai maintenant rencontré du code qui utilise une autre convention pour atteindre le même objectif
@Component
public class SomeService {
private final SomeOtherService someOtherService;
@Autowired
public SomeService(SomeOtherService someOtherService){
this.someOtherService = someOtherService;
}
}
Je comprends ces deux méthodes. Mais y a-t-il un avantage à utiliser l'option B? Pour moi, cela crée plus de code dans le test de classe et d'unité. (Avoir à écrire un constructeur et ne pas pouvoir utiliser @InjectMocks)
Y a-t-il quelque chose qui me manque? Y at-il autre chose que le constructeur auto-câblé fait en plus d'ajouter du code aux tests unitaires? Est-ce une manière plus privilégiée de faire une injection de dépendance?
Oui, l'option B (appelée injection de constructeur) est en fait recommandée par rapport à l'injection sur site et présente plusieurs avantages:
Voir cet article de blog pour un article plus détaillé rédigé par l'un des contributeurs de Spring, Olivier Gierke .
Je vais vous expliquer avec des mots simples:
Dans Option (A), vous autorisez toute personne (de classe différente en dehors/à l'intérieur du conteneur Spring) à créer une instance à l'aide du constructeur par défaut (tel que new SomeService()
), ce qui n'est PAS valable. besoin de SomeOtherService
objet (en tant que dépendance) pour votre SomeService
.
Y at-il autre chose que le constructeur auto-câblé fait en plus d'ajouter du code aux tests unitaires? Est-ce une manière plus privilégiée de faire une injection de dépendance?
L'option (B) est l'approche préférée car cela ne permet PAS de créer un objet SomeService
sans résoudre réellement la dépendance SomeOtherService
.
Les constructeurs Autowired
fournissent un crochet pour ajouter du code personnalisé avant de l'enregistrer dans le conteneur de ressort. Supposons que SomeService
class étend une autre classe nommée SuperSomeService
et qu'elle ait un constructeur qui prend un nom comme argument. Dans ce cas, le constructeur Autowired
fonctionne bien. De même, si vous avez d'autres membres à initialiser, vous pouvez le faire dans le constructeur avant de renvoyer l'instance dans le conteneur spring.
public class SuperSomeService {
private String name;
public SuperSomeService(String name) {
this.name = name;
}
}
@Component
public class SomeService extends SuperSomeService {
private final SomeOtherService someOtherService;
private Map<String, String> props = null;
@Autowired
public SomeService(SomeOtherService someOtherService){
SuperSomeService("SomeService")
this.someOtherService = someOtherService;
props = loadMap();
}
}
En fait, selon mon expérience, la deuxième option est préférable. Sans la nécessité de @Autowired
. En fait, il est plus sage de créer du code qui ne soit pas trop étroitement couplé au cadre (aussi bon que le printemps est) . Vous voulez un code qui tente autant que possible d'adopter une approche de prise de décision différée . C'est autant que possible pojo , à tel point que le cadre peut être échangé facilement. Je vous conseillerais donc de créer un fichier de configuration séparé et de définir votre bean ici, comme ceci:
Dans le fichier SomeService.Java :
public class SomeService {
private final SomeOtherService someOtherService;
public SomeService(SomeOtherService someOtherService){
this.someOtherService = someOtherService;
}
}
Dans le fichier ServiceConfig.Java :
@Config
public class ServiceConfig {
@Bean
public SomeService someService(SomeOtherService someOtherService){
return new SomeService(someOtherService);
}
}
En fait, si vous souhaitez approfondir le sujet, des questions de sécurité des threads (entre autres) se posent lors de l'utilisation de l'injection de champ (- @Autowired
), selon la taille du projet évidemment. Cochez cette case pour en savoir plus sur le avantages et inconvénients du câblage automatique . En fait, les personnes clés recommandent en fait d’utiliser l’injection de constructeur au lieu de l’injection de champ
Bon à savoir
S'il n'y a qu'un seul appel de constructeur, il n'est pas nécessaire d'inclure une annotation @Autowired. Ensuite, vous pouvez utiliser quelque chose comme ceci:
@RestController
public class NiceController {
private final DataRepository repository;
public NiceController(ChapterRepository repository) {
this.repository = repository;
}
}
... exemple d'injection de Spring Data Repository.