web-dev-qa-db-fra.com

Spring @Autowire on Properties vs Constructor

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?

94
GSUgambit

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:

  • les dépendances sont clairement identifiées. Il n'y a aucun moyen d'en oublier un lors du test ou de l'instanciation de l'objet dans un autre cas (comme créer l'instance de bean explicitement 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 réflexion pour définir les dépendances. InjectMocks est toujours utilisable, mais pas nécessaire. Vous pouvez simplement créer des simulacres 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 de Spring, Olivier Gierke .

131
JB Nizet

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.

26
developer

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();
    }
}
1
Swapan Pramanick

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

0
Dougie T

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.

0
Daniel Perník