242 votes

Spring @Autowire sur les propriétés et les constructeurs.

Donc, depuis que j'utilise Spring, si je devais écrire un service qui a des dépendances, je ferais ce qui suit :

@Component
public class SomeService {
     @Autowired private SomeOtherService someOtherService;
}

Je suis maintenant tombé sur un 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;
    }
}

Ces deux méthodes fonctionneront, je le comprends. Mais y a-t-il un avantage à utiliser l'option B ? Pour moi, cela crée plus de code dans la classe et le test unitaire. (Avoir à écrire le constructeur et ne pas pouvoir utiliser @InjectMocks)

Y a-t-il quelque chose qui m'échappe ? Le constructeur autowired fait-il autre chose que d'ajouter du code aux tests unitaires ? S'agit-il d'un moyen plus approprié de réaliser l'injection de dépendances ?

0 votes

1 votes

Lequel est Option A y Option B ?

319voto

JB Nizet Points 250258

Oui, l'option B (qui est appelée injection de constructeur) est en fait recommandée par rapport à l'injection de champ, et présente plusieurs avantages :

  • les dépendances sont clairement identifiées. Il n'y a aucun moyen d'en oublier une lors des tests ou de l'instanciation de l'objet dans toute autre circonstance (comme la création explicite de l'instance du haricot dans une classe de configuration).
  • les dépendances peuvent être finales, ce qui contribue à la robustesse et à la sécurité des threads.
  • vous n'avez pas besoin de la réflexion pour définir les dépendances. InjectMocks est toujours utilisable, mais pas nécessaire. Vous pouvez créer des mocks par vous-même et les injecter en appelant simplement le constructeur

Voir cet article de blog pour un article plus détaillé, rédigé par l'un des contributeurs du Printemps, Olivier Gierke .

5 votes

Alors plongeons plus profondément et disons que vous avez d'autres propriétés comme @Value("some.prop") private String property ; Est-ce que vous le mettriez aussi dans le constructeur ? Il semble juste que vous vous retrouveriez avec de très longs constructeurs entièrement paramétrés. Ce qui n'est pas une mauvaise perse, juste beaucoup plus de code. J'apprécie vraiment votre commentaire !

13 votes

Oui, vous devriez aussi le mettre dans le constructeur. Comme le dit l'article en lien, lorsque le constructeur commence à avoir trop de paramètres, c'est souvent le signe que vous devriez diviser la classe en plus petites classes avec moins de responsabilités et moins de dépendances.

1 votes

Je ne sais pas comment j'ai pu tout voir sauf la ligne de l'article de blog. Merci pour ça !

53voto

VA31 Points 612

Je vais vous expliquer en termes simples :

En Option(A), vous permettez à n'importe qui (dans une classe différente à l'extérieur/à l'intérieur du conteneur Spring) de créer une instance en utilisant un constructeur par défaut (comme new SomeService() ), ce qui n'est PAS bon car vous avez besoin de SomeOtherService (en tant que dépendance) pour votre SomeService .

Est-ce qu'il y a autre chose que le constructeur auto-connecté fait à part ajouter du code aux tests unitaires ? Est-ce que c'est une meilleure façon de faire une injection de dépendance ?

L'option (B) est l'approche privilégiée car il ne permet PAS de créer SomeService sans résoudre réellement l'objet SomeOtherService dépendance.

1 votes

Avec l'option B, en quoi ne s'agit-il pas d'une "fuite d'abstraction" ? Si je ne sais pas à l'avance quelle est l'implémentation réelle d'un service, et que Impl1 a des dépendances A/B/C et que Impl2 a A/B/F/G, l'approche constructeur m'obligerait à connaître les dépendances dès le départ. Mais, avec @autowire, lorsque je tente de consommer un service, si toutes les dépendances nécessaires sont enregistrées dans le conteneur, alors tout va bien et je n'ai pas eu à connaître les dépendances du service.

3 votes

@ChrisKnoll Le but est de savoir que les dépendances sont nécessaires pour que votre service fonctionne (surtout pour les tests !). Ce n'est pas une "abstraction fuyante" que de savoir que votre voiture a besoin d'une clé pour démarrer. Vous n'avez pas besoin de connaître l'implémentation exacte de la clé, mais vous devez savoir que vous en avez besoin. De même, tant que votre service en question (SomeService) est annoté say(Service), vous n'avez pas besoin de savoir quelles sont ses dépendances. Dans votre classe (qui consomme le service), ayez la même annotation autowire pour lui et vous pouvez appeler SomeService.perform() sans appeler le mot-clé 'new'.

26voto

stinger Points 1309

Veuillez noter que, depuis Printemps 4.3 vous n'avez même pas besoin d'un @Autowired sur votre constructeur, vous pouvez donc écrire votre code dans le style Java plutôt que de vous attacher aux annotations de Spring. Votre extrait de code ressemblerait à cela :

@Component
public class SomeService {
    private final SomeOtherService someOtherService;

    public SomeService(SomeOtherService someOtherService){
        this.someOtherService = someOtherService;
    }
}

12voto

Daniel Perník Points 1556

Bon à savoir

S'il n'y a qu'un seul appel au constructeur, il n'est pas nécessaire d'inclure une annotation @Autowired. Vous pouvez alors 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.

8voto

Dougie T Points 179

En fait, d'après mon expérience, la deuxième option est meilleure. Sans le besoin de @Autowired . En fait, il est plus sage de créer un code qui n'est pas trop étroitement couplé au framework (aussi bon que le printemps) . Vous voulez un code qui essaye autant que possible d'adopter un prise de décision différée approche. C'est autant pojo de manière à ce que le framework puisse être remplacé facilement. Je vous conseille donc de créer un fichier de configuration séparé et d'y définir votre haricot, comme ceci :

En SomeService.java fichier :

public class SomeService {
    private final SomeOtherService someOtherService;

    public SomeService(SomeOtherService someOtherService){
        this.someOtherService = someOtherService;
    }
}

En ServiceConfig.java fichier :

@Config
public class ServiceConfig {
    @Bean
    public SomeService someService(SomeOtherService someOtherService){
        return new SomeService(someOtherService);
    }
}

En fait, si vous voulez entrer dans les détails techniques, l'utilisation de l'option "thread safety" (entre autres) pose des problèmes de sécurité. Injection de champ ( @Autowired ), en fonction de la taille du projet évidemment. Regardez ça pour en savoir plus sur le avantages et inconvénients du câblage automatique . En fait, les gars du pivot recommandent que vous utilisiez Injection du constructeur au lieu de Injection de champ

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X