it-swarm.com.de

Spring @Autowire auf Properties vs Constructor

Da ich Spring verwende, würde ich, wenn ich einen Dienst mit Abhängigkeiten schreiben würde, Folgendes tun:

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

Ich bin jetzt auf Code gestoßen, der eine andere Konvention verwendet, um dasselbe Ziel zu erreichen

@Component
public class SomeService {
    private final SomeOtherService someOtherService;

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

Beide Methoden werden funktionieren, das verstehe ich. Aber hat die Verwendung von Option B einige Vorteile? Für mich schafft es mehr Code im Klassen- und Unit-Test. (Konstruktor schreiben müssen und @InjectMocks nicht verwenden können)

Fehlt mir etwas? Gibt es noch etwas, das der Autowired-Konstruktor außer dem Hinzufügen von Code zu den Komponententests tut? Ist dies ein bevorzugterer Weg, eine Abhängigkeitsinjektion durchzuführen?

94
GSUgambit

Ja, Option B (die als Konstruktorinjektion bezeichnet wird) wird gegenüber der Feldinjektion empfohlen und bietet mehrere Vorteile:

  • die Abhängigkeiten sind klar erkennbar. Es gibt keine Möglichkeit, eine zu vergessen, wenn Sie das Objekt testen oder unter anderen Umständen instanziieren (z. B. wenn Sie die Bean-Instanz explizit in einer Konfigurationsklasse erstellen).
  • die Abhängigkeiten können endgültig sein, was zu Robustheit und Thread-Sicherheit beiträgt
  • sie brauchen keine Reflexion, um die Abhängigkeiten festzulegen. InjectMocks ist weiterhin verwendbar, aber nicht erforderlich. Sie können Mocks einfach selbst erstellen und durch Aufrufen des Konstruktors einfügen

Siehe dieser Blog-Beitrag für einen ausführlicheren Artikel von einem der Spring-Mitarbeiter, Olivier Gierke .

131
JB Nizet

Ich werde Sie in einfachen Worten erklären:

In Option (A), erlauben Sie jedem (in einer anderen Klasse außerhalb/innerhalb des Spring-Containers), eine Instanz mit dem Standardkonstruktor (wie new SomeService()) zu erstellen, was NICHT gut ist da Sie SomeOtherService Objekt (als Abhängigkeit) für Ihr SomeService benötigen.

Gibt es noch etwas, das der Autowired-Konstruktor außer dem Hinzufügen von Code zu den Komponententests tut? Ist dies ein bevorzugterer Weg, eine Abhängigkeitsinjektion durchzuführen?

Option (B) ist der bevorzugte Ansatz da es NICHT möglich ist, SomeService -Objekte zu erstellen, ohne die Abhängigkeit von SomeOtherService tatsächlich aufzulösen.

26
developer

Autowired -Konstruktoren stellen einen Hook bereit, mit dem Sie benutzerdefinierten Code hinzufügen können, bevor Sie ihn im Spring-Container registrieren. Angenommen, die Klasse SomeService erweitert eine andere Klasse mit dem Namen SuperSomeService und hat einen Konstruktor, der einen Namen als Argument verwendet. In diesem Fall funktioniert der Konstruktor Autowired einwandfrei. Wenn andere Elemente initialisiert werden müssen, können Sie dies auch im Konstruktor tun, bevor Sie die Instanz in den Spring-Container zurücksetzen.

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

Eigentlich ist nach meiner Erfahrung die zweite Option besser. Ohne die Notwendigkeit für @Autowired. In der Tat ist es klüger, Code zu erstellen, der nicht zu eng mit dem Framework verbunden ist (so gut wie Spring ist) . Sie möchten, dass Code, der so viel wie möglich versucht, einen verzögerten Entscheidungsfindungs -Ansatz anwendet. Das ist so viel pojo wie möglich, so viel, dass das Framework leicht ausgetauscht werden kann. Daher würde ich Ihnen raten, eine separate Konfigurationsdatei zu erstellen und Ihre Bean dort wie folgt zu definieren:

In SomeService.Java Datei:

public class SomeService {
    private final SomeOtherService someOtherService;

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

In ServiceConfig.Java Datei:

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

Wenn Sie sich eingehend mit technischen Fragen befassen möchten, stellen sich (unter anderem) Fragen zur Threadsicherheit, die bei der Verwendung von Field Injection (@Autowired), natürlich abhängig von der Größe des Projekts. Schauen Sie sich dies an , um mehr über die Vor- und Nachteile von Autowiring zu erfahren. Tatsächlich empfehlen die Pivot-Leute, dass Sie Konstruktor-Injektion anstelle von Feld-Injektion verwenden

0
Dougie T

Gut zu wissen

Wenn es nur einen Konstruktoraufruf gibt, muss keine @Autowired-Annotation eingefügt werden. Dann können Sie so etwas verwenden:

@RestController
public class NiceController {

    private final DataRepository repository;

    public NiceController(ChapterRepository repository) {
        this.repository = repository;
    }
}

... Beispiel für die Einspeisung von Spring Data Repository.

0
Daniel Perník