it-swarm.com.de

Welche Klassen sollten bis zum Frühjahr automatisch verdrahtet werden (wann die Abhängigkeitsinjektion verwendet werden soll)?

Ich verwende Dependency Injection im Frühjahr seit einiger Zeit und verstehe, wie es funktioniert und welche Vor- und Nachteile es hat. Wenn ich jedoch eine neue Klasse erstelle, frage ich mich oft: Sollte diese Klasse von Spring IOC Container) verwaltet werden?

Und ich möchte nicht über Unterschiede zwischen @ Autowired-Annotation, XML-Konfiguration, Setter-Injection, Konstruktor-Injection usw. sprechen. Meine Frage ist allgemein.

Nehmen wir an, wir haben einen Service mit einem Konverter:

@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
    }
}

Der Konverter hat eindeutig keine Abhängigkeiten, daher muss er nicht automatisch verdrahtet werden. Aber für mich scheint es besser als autowired. Code ist sauberer und einfach zu testen. Wenn ich diesen Code ohne DI schreibe, sieht der Dienst folgendermaßen aus:

@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);
    }
}

Jetzt ist es viel schwieriger, es zu testen. Darüber hinaus wird für jede Konvertierungsoperation ein neuer Konverter erstellt, obwohl er sich immer im selben Zustand befindet, was wie ein Overhead erscheint.

In Spring MVC gibt es einige bekannte Muster: Controller, die Dienste verwenden, und Dienste, die Repositorys verwenden. Wenn das Repository automatisch verdrahtet ist (was normalerweise der Fall ist), muss der Dienst ebenfalls automatisch verdrahtet werden. Und das ist ganz klar. Aber wann verwenden wir die Annotation @Component? Wenn Sie einige statische Util-Klassen haben (wie Konverter, Mapper) - verdrahten Sie sie automatisch?

Versuchen Sie, alle Klassen automatisch zu verdrahten? Dann sind alle Klassenabhängigkeiten leicht zu injizieren (wieder leicht zu verstehen und leicht zu testen). Oder versuchen Sie nur dann automatisch zu verdrahten, wenn dies unbedingt erforderlich ist?

Ich habe einige Zeit damit verbracht, nach allgemeinen Regeln für die Verwendung von Autowiring zu suchen, konnte jedoch keine konkreten Tipps finden. Normalerweise sprechen die Leute über "Verwenden Sie DI? (Ja/Nein)" oder "Welche Art der Abhängigkeitsinjektion bevorzugen Sie", was meine Frage nicht beantwortet.

Für Tipps zu diesem Thema wäre ich dankbar!

34
Kacper86

Stimmen Sie dem Kommentar von @ ericW zu und möchten Sie nur hinzufügen, dass Sie Initialisierer verwenden können, um Ihren Code kompakt zu halten:

@Autowired
private Converter converter;

oder

private Converter converter = new Converter();

oder wenn die Klasse wirklich überhaupt keinen Zustand hat

private static final Converter CONVERTER = new Converter();

Eines der Hauptkriterien dafür, ob Spring eine Bohne instanziieren und injizieren sollte, ist, ob diese Bohne so kompliziert ist, dass Sie sie beim Testen verspotten möchten. Wenn ja, dann injizieren Sie es. Wenn der Konverter beispielsweise einen Roundtrip zu einem externen System durchführt, machen Sie ihn stattdessen zu einer Komponente. Oder wenn der Rückgabewert einen großen Entscheidungsbaum mit Dutzenden möglicher Variationen basierend auf der Eingabe hat, verspotten Sie ihn. Und so weiter.

Sie haben diese Funktionalität bereits gut aufgerollt und gekapselt. Jetzt ist es nur noch eine Frage, ob sie komplex genug ist, um als separate "Einheit" zum Testen betrachtet zu werden.

8
Rob

Ich denke nicht, dass Sie alle Ihre Klassen @Autowired müssen, es sollte von der tatsächlichen Verwendung abhängen, für Ihre Szenarien sollte es besser sein, statische Methode anstelle von @Autowired zu verwenden. Ich habe keinen Vorteil darin gesehen, @Autowired für diese einfache Utils-Klasse zu verwenden, und das erhöht die Kosten für Spring-Container absolut, wenn es nicht richtig verwendet wird.

5
ericW

Meine Faustregel basiert auf etwas, das Sie bereits gesagt haben: Testbarkeit. Fragen Sie sich "Kann ich es einfach testen?". Wenn die Antwort ja ist, wäre ich ohne einen anderen Grund damit einverstanden. Wenn Sie also den Komponententest zur gleichen Zeit entwickeln, in der Sie sich entwickeln, sparen Sie viel Schmerz.

Das einzige mögliche Problem besteht darin, dass bei einem Ausfall des Konverters auch Ihr Servicetest fehlschlägt. Einige Leute würden sagen, dass Sie die Ergebnisse des Konverters in Unit-Tests verspotten sollten. Auf diese Weise können Sie Fehler schneller identifizieren. Aber es hat einen Preis: Sie müssen alle Konverterergebnisse verspotten, wenn der echte Konverter die Arbeit hätte erledigen können.

Ich gehe auch davon aus, dass es überhaupt keinen Grund gibt, verschiedene dto-Konverter zu verwenden.

2
Borjab

TL; DR: Ein hybrider Ansatz aus Autowiring für DI und Konstruktorweiterleitung für DI kann den von Ihnen präsentierten Code vereinfachen.

Ich habe mir ähnliche Fragen aufgrund eines Weblogics mit Startfehlern/Komplikationen beim Start des Spring Frameworks angesehen, die Abhängigkeiten bei der Initialisierung von @autowired Bean betreffen. Ich habe begonnen, einen anderen DI-Ansatz einzumischen: Konstruktorweiterleitung. Es erfordert Bedingungen, wie Sie sie vorlegen ("Der Konverter hat eindeutig keine Abhängigkeiten, daher muss er nicht automatisch verdrahtet werden."). Trotzdem mag ich die Flexibilität sehr und es ist immer noch ziemlich einfach.

@Service
public class Service {

    @Autowired
    private Repository repository;

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        Converter converter = new Converter();
        return getAllCars(converter);
    }
}

oder sogar als Rif von Robs Antwort

@Service
public class Service {

    @Autowired
    private Repository repository;

    private final Converter converter = new Converter(); // static if safe for that

    public List<CarDto> getAllCars(Converter converter) {
        List<Car> cars = repository.findAll();
        return converter.mapToDto(cars);
    }
    public List<CarDto> getAllCars() {
        return getAllCars(converter);
    }
}

Es erfordert möglicherweise keine Änderung der öffentlichen Schnittstelle, aber ich würde. Immernoch

public List<CarDto> getAllCars(Converter converter) { ... }

könnte geschützt oder privat gemacht werden, um nur zu Testzwecken/zur Erweiterung herabgesetzt zu werden.

Der entscheidende Punkt ist, dass der DI optional ist. Es wird eine Standardeinstellung bereitgestellt, die auf einfache Weise überschrieben werden kann. Es hat seine Schwäche, wenn die Anzahl der Felder zunimmt, aber für 1, vielleicht 2 Felder würde ich dies unter den folgenden Bedingungen bevorzugen:

  • Sehr kleine Anzahl von Feldern
  • Standard wird fast immer verwendet (DI ist eine Testnaht) oder Standard wird fast nie verwendet (Stop Gap), weil ich Konsistenz mag, daher ist dies Teil meines Entscheidungsbaums, keine echte Designbeschränkung

Letzter Punkt (ein bisschen OT, aber im Zusammenhang damit, wie wir entscheiden, was/wo @autowired ist): Die Konverterklasse ist, wie dargestellt, eine Dienstprogrammmethode (keine Felder, kein Konstruktor, könnte statisch sein). Vielleicht wäre die Lösung eine Art mapToDto () -Methode in der Cars-Klasse? Das heißt, schieben Sie die Umwandlungsinjektion auf die Cars-Definition, wo dies wahrscheinlich bereits eng miteinander verbunden ist:

@Service
public class Service {

   @Autowired
   private Repository repository;

   public List<CarDto> getAllCars() {
    return repository.findAll().stream.map(c -> c.mapToDto()).collect(Collectors.toList()));
   }
}
0
Kristian H