J'utilise Dependency Injection au printemps depuis un certain temps maintenant, et je comprends comment cela fonctionne et quels sont les avantages et les inconvénients de son utilisation. Cependant, lorsque je crée une nouvelle classe, je me demande souvent - Cette classe doit-elle être gérée par Spring IOC Container?
Et je ne veux pas parler des différences entre l'annotation @Autowired, la configuration XML, l'injection de setter, l'injection de constructeur, etc. Ma question est générale.
Disons que nous avons un service avec un convertisseur:
@Service
public class Service {
@Autowired
private Repository repository;
@Autowired
private Converter converter;
public List<CarDto> getAllCars() {
List<Car> cars = repository.findAll();
return converter.mapToDto(cars);
}
}
@Component
public class Converter {
public CarDto mapToDto(List<Car> cars) {
return new ArrayList<CarDto>(); // do the mapping here
}
}
De toute évidence, le convertisseur n'a aucune dépendance, il n'est donc pas nécessaire qu'il soit câblé automatiquement. Mais pour moi, cela semble mieux que auto-câblé. Le code est plus propre et facile à tester. Si j'écris ce code sans DI, le service ressemblera à ça:
@Service
public class Service {
@Autowired
private Repository repository;
public List<CarDto> getAllCars() {
List<Car> cars = repository.findAll();
Converter converter = new Converter();
return converter.mapToDto(cars);
}
}
Maintenant, il est beaucoup plus difficile de le tester. De plus, un nouveau convertisseur sera créé pour chaque opération de conversion, même s'il est toujours dans le même état, ce qui semble être une surcharge.
Il existe des modèles bien connus dans Spring MVC: contrôleurs utilisant des services et services utilisant des référentiels. Ensuite, si le référentiel est câblé automatiquement (ce qui est généralement le cas), le service doit également être câblé automatiquement. Et c'est assez clair. Mais quand utilisons-nous l'annotation @Component? Si vous avez des classes utilitaires statiques (comme les convertisseurs, les mappeurs) - les attribuez-vous automatiquement?
Essayez-vous de rendre toutes les classes câblées automatiquement? Ensuite, toutes les dépendances de classe sont faciles à injecter (encore une fois, faciles à comprendre et faciles à tester). Ou essayez-vous de câbler automatiquement uniquement lorsque cela est absolument nécessaire?
J'ai passé un certain temps à chercher des règles générales sur le moment d'utiliser le câblage automatique, mais je n'ai trouvé aucun conseil spécifique. Habituellement, les gens parlent de "utilisez-vous DI? (Oui / non)" ou "quel type d'injection de dépendance préférez-vous", ce qui ne répond pas à ma question.
Je serais reconnaissant pour tout conseil concernant ce sujet!
la source
Réponses:
Soyez d'accord avec le commentaire de @ ericW, et je veux juste ajouter que vous pouvez utiliser des initialiseurs pour garder votre code compact:
ou
ou, si la classe n'a vraiment aucun état
L'un des critères clés pour savoir si Spring doit instancier et injecter un bean est, est-ce que ce bean est si compliqué que vous voulez vous en moquer lors des tests? Si oui, injectez-le. Par exemple, si le convertisseur effectue un aller-retour vers n'importe quel système externe, faites-en plutôt un composant. Ou si la valeur de retour a un grand arbre de décision avec des dizaines de variations possibles en fonction de l'entrée, alors moquez-la. Et ainsi de suite.
Vous avez déjà fait un bon travail pour enrouler cette fonctionnalité et l'encapsuler, donc maintenant il s'agit juste de savoir si elle est suffisamment complexe pour être considérée comme une «unité» distincte pour les tests.
la source
Je ne pense pas que vous devez @Autowired toutes vos classes, cela devrait dépendre de l'utilisation réelle, pour vos scénarios, il vaut mieux utiliser une méthode statique au lieu de @Autowired. Je ne vois aucun avantage à utiliser @Autowired pour ces classes d'utilitaires simples, et cela augmentera absolument le coût du conteneur Spring s'il n'est pas utilisé correctement.
la source
Ma règle d'or est basée sur quelque chose que vous avez déjà dit: la testabilité. Demandez-vous " Puis-je le tester facilement? ". Si la réponse est oui, en l'absence de toute autre raison, je serais d'accord avec ça. Donc, si vous développez le test unitaire en même temps que vous le développez, vous économiserez beaucoup de douleur.
Le seul problème potentiel est que si le convertisseur échoue, votre test de service échouera également. Certaines personnes diraient que vous devriez vous moquer des résultats du convertisseur dans les tests unitaires. De cette façon, vous seriez en mesure d'identifier les erreurs plus rapidement. Mais il a un prix: il faut se moquer de tous les résultats des convertisseurs alors que le vrai convertisseur aurait pu faire le travail.
Je suppose également qu'il n'y a aucune raison d'utiliser différents convertisseurs dto.
la source
TL; DR: Une approche hybride de câblage automatique pour DI et de transfert de constructeur pour DI peut simplifier le code que vous avez présenté.
J'ai regardé des questions similaires en raison de certains weblogic avec des erreurs / complications de démarrage du framework Spring impliquant des dépendances d'initialisation de @autowired bean. J'ai commencé à mélanger dans une autre approche DI: le transfert constructeur. Il requiert des conditions comme celles que vous présentez ("De toute évidence, le convertisseur n'a pas de dépendances, il n'est donc pas nécessaire qu'il soit câblé automatiquement."). Néanmoins, j'aime beaucoup pour la flexibilité et c'est toujours assez simple.
ou même comme un rif sur la réponse de Rob
Cela peut ne pas nécessiter de changement d'interface publique, mais je le ferais. Toujours le
pourrait être protégé ou privé pour être limité à des fins de test / extension uniquement.
Le point clé est que la DI est facultative. Une valeur par défaut est fournie, qui peut être remplacée de manière simple. Il a sa faiblesse car le nombre de champs augmente, mais pour 1, peut-être 2 champs, je préférerais cela dans les conditions suivantes:
Dernier point (un peu OT, mais lié à la façon dont nous décidons quoi / où @autowired): la classe du convertisseur, telle que présentée, est une méthode utilitaire (aucun champ, aucun constructeur, pourrait être statique). Peut-être que la solution serait d'avoir une sorte de méthode mapToDto () dans la classe Cars? Autrement dit, poussez l'injection de conversion vers la définition Cars, où elle est probablement déjà intimement liée:
la source
Je pense qu'un bon exemple de cela est quelque chose comme SecurityUtils ou UserUtils. Avec n'importe quelle application Spring qui gère les utilisateurs, vous aurez besoin d'une classe Util avec un tas de méthodes statiques telles que:
getCurrentUser ()
isAuthenticated ()
isCurrentUserInRole (autorité d'autorité)
etc.
et je ne les ai jamais câblés. JHipster (que j'utilise comme un assez bon juge des meilleures pratiques) ne le fait pas.
la source
Nous pouvons séparer les classes sur la base de la fonction de cette classe telle que contrôleurs, service, référentiel, entité. Nous pouvons utiliser d'autres classes pour notre logique uniquement à des fins de contrôleurs, de service, etc., à cette occasion, nous pourrions annoter ces classes avec @component. Cela enregistrera automatiquement cette classe dans le conteneur de printemps. S'il est enregistré, il sera géré par un conteneur à ressort. Le concept d'injection de dépendance et d'inversion de contrôle peut être fourni à cette classe annotée.
la source