Welche Klassen sollten von Spring automatisch verkabelt werden (wenn Abhängigkeitsinjektion verwendet werden soll)?


32

Ich verwende Dependency Injection seit einiger Zeit im Frühjahr und verstehe, wie es funktioniert und welche Vor- und Nachteile es mit sich bringt. Wenn ich jedoch eine neue Klasse erstelle, frage ich mich oft: Soll 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 eine allgemeine.

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

Es ist klar, dass der Konverter keine Abhängigkeiten aufweist und daher nicht automatisch verdrahtet werden muss. 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. Außerdem wird für jeden Konvertierungsvorgang ein neuer Konvertierer 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 verkabelt wird (was normalerweise der Fall ist), muss auch der Service automatisch verkabelt werden. Und das ist ganz klar. Aber wann verwenden wir die @Component-Annotation? Wenn Sie statische Util-Klassen haben (wie Konverter, Mapper) - führen Sie eine automatische Verdrahtung durch?

Versuchen Sie, alle Klassen automatisch zu verkabeln? Dann sind alle Klassenabhängigkeiten leicht zu injizieren (wiederum 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 spezifischen Tipps finden. Normalerweise wird über "Verwenden Sie DI? (Ja / Nein)" oder "Welche Art der Abhängigkeitsinjektion bevorzugen Sie" gesprochen, was meine Frage nicht beantwortet.

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


3
+1 für "Wann geht es zu weit?"
Andy Hunt

Schauen Sie sich diese Frage und diesen Artikel an
Laiv

Antworten:


8

Stimmen Sie dem Kommentar von @ ericW zu und möchten Sie ihn nur hinzufügen. Denken Sie daran, 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();

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

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


5

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 statische Methode anstelle von @Autowired verwenden. Ich habe keine Vorteile bei der Verwendung von @Autowired für diese einfache Utils-Klasse gesehen, und das wird die Kosten für den Spring-Container absolut erhöhen, wenn er nicht ordnungsgemäß verwendet wird.


2

Meine Faustregel basiert auf etwas, das Sie bereits gesagt haben: Testbarkeit. Fragen Sie sich " Kann ich das Gerät einfach testen? ". Wenn die Antwort ja ist, wäre ich in Abwesenheit eines anderen Grundes damit einverstanden. Wenn Sie also den Komponententest zur gleichen Zeit wie Sie entwickeln, sparen Sie eine Menge Schmerzen.

Das einzige mögliche Problem besteht darin, dass bei einem Ausfall des Konverters auch der 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 erkennen. 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, andere dto-Konverter zu verwenden.


0

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 ähnliche Fragen aufgrund einer Weblogik mit Spring Framework-Startfehlern / -komplikationen untersucht, die @autowired-Bean-Initialisierungsabhängigkeiten betreffen. Ich habe begonnen, in einem anderen DI-Ansatz zu mischen: Konstruktorweiterleitung. Es sind Bedingungen wie die von Ihnen angegebenen erforderlich ("Natürlich hat der Konverter keine Abhängigkeiten, daher muss er nicht automatisch verkabelt werden."). Trotzdem mag ich die Flexibilität sehr und sie 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 ein Schuss 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 ist möglicherweise keine Änderung der öffentlichen Schnittstelle erforderlich, aber ich würde es tun. Immernoch

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

könnte geschützt oder privat gemacht werden, um nur für Testzwecke / Erweiterungen in den Geltungsbereich zu gelangen.

Der entscheidende Punkt ist, dass der DI optional ist. Es wird ein Standardwert bereitgestellt, der 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 vorziehen:

  • Sehr kleine Anzahl von Feldern
  • Die Standardeinstellung wird fast immer verwendet (DI ist eine Testnaht) oder die Standardeinstellung wird fast nie verwendet (Lücke schließen), da ich die Konsistenz mag. Dies ist also Teil meines Entscheidungsbaums und keine echte Designbeschränkung

Letzter Punkt (ein bisschen OT, aber abhängig davon, wie wir entscheiden, was / wo @autowired ist): Die dargestellte Konverterklasse ist eine Dienstprogrammmethode (keine Felder, kein Konstruktor, könnte statisch sein). Vielleicht hätte die Lösung eine Art mapToDto () -Methode in der Cars-Klasse? Das heißt, verschieben Sie die Konvertierungsinjektion in die Cars-Definition, wo dies wahrscheinlich bereits eng verknüpft ist:

@Service
public class Service {

   @Autowired
   private Repository repository;

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

-1

Ich denke, ein gutes Beispiel dafür ist so etwas wie SecurityUtils oder UserUtils. Für jede Spring-App, die Benutzer verwaltet, benötigen Sie eine Util-Klasse mit einer ganzen Reihe statischer Methoden wie:

getCurrentUser ()

isAuthenticated ()

isCurrentUserInRole (Autoritätsautorität)

etc.

und ich habe diese noch nie automatisch verkabelt. JHipster (den ich als einen ziemlich guten Richter der besten Praxis benutze) tut es nicht.


-2

Wir können die Klassen auf der Grundlage der Funktion dieser Klasse so trennen, dass Controller, Service, Repository, Entität. Wir dürfen andere Klassen nur für unsere Logik verwenden, nicht für den Zweck von Controllern, Diensten usw. In diesem Fall können wir diese Klassen mit @component kommentieren. Dadurch wird diese Klasse automatisch im Federbehälter registriert. Wenn es registriert ist, wird es von Federbehälter verwaltet. Das Konzept der Abhängigkeitsinjektion und der Inversion der Steuerung kann an diese mit Anmerkungen versehene Klasse übergeben werden.


3
Dieser Beitrag ist ziemlich schwer zu lesen (Textwand). Hätten Sie etwas dagegen bearbeiten sie in eine bessere Form ing?
gnat
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.