313 Stimmen

Warum wird ApplicationContext.getBean von Spring als schlecht angesehen?

Ich habe eine allgemeine Frage zum Thema Frühling gestellt: Automatisch gegossene Frühlingsbohnen und mehrere Leute haben geantwortet, dass der Aufruf von Spring ApplicationContext.getBean() sollten so weit wie möglich vermieden werden. Warum ist das so?

Wie sollte ich sonst Zugriff auf die Beans erhalten, die ich mit Spring erstellt habe?

Ich verwende Spring in einer Nicht-Web-Anwendung und hatte geplant, den Zugriff auf eine freigegebene ApplicationContext Objekt wie von LiorH beschrieben .

Abänderung

Ich akzeptiere die unten stehende Antwort, aber hier ist eine andere Meinung von Martin Fowler, der erörtert die Vorzüge von Dependency Injection gegenüber der Verwendung eines Service Locators (was im Wesentlichen dasselbe ist wie der Aufruf einer umhüllten ApplicationContext.getBean() ).

Fowler erklärt unter anderem: " Beim Service Locator fordert die Anwendungsklasse den Dienst explizit durch eine Nachricht an den Locator an. Bei der Injektion gibt es keine explizite Anfrage, der Dienst erscheint in der Anwendungsklasse - daher die Umkehrung der Kontrolle. Die Umkehrung der Kontrolle ist ein gängiges Merkmal von Frameworks, aber sie hat ihren Preis. Sie ist in der Regel schwer zu verstehen und führt zu Problemen bei der Fehlersuche. Im Großen und Ganzen ziehe ich es also vor, sie [die Umkehrung der Kontrolle] zu vermeiden, wenn ich sie nicht brauche. Das soll nicht heißen, dass es etwas Schlechtes ist, ich denke nur, dass es sich gegenüber der einfacheren Alternative rechtfertigen muss. "

14voto

easyplanner.cu.cc Punkte 141

使用方法 @Autowired o ApplicationContext.getBean() ist eigentlich dasselbe. Auf beiden Wegen erhalten Sie die Bean, die in Ihrem Kontext konfiguriert ist, und auf beiden Wegen hängt Ihr Code von Spring ab. Das einzige, was Sie vermeiden sollten, ist die Instanziierung Ihres ApplicationContext. Tun Sie dies nur einmal! Mit anderen Worten, eine Zeile wie

ApplicationContext context = new ClassPathXmlApplicationContext("AppContext.xml");

sollte nur einmal in Ihrer Anwendung verwendet werden.

6voto

sourcerebels Punkte 5062

Eine der Prämissen von Spring ist die Vermeidung Kupplung . Definieren und verwenden Sie Schnittstellen, DI, AOP und vermeiden Sie die Verwendung von ApplicationContext.getBean() :-)

5voto

yankee Punkte 35636

Einer der Gründe dafür ist die Testbarkeit. Nehmen wir an, Sie haben diese Klasse:

interface HttpLoader {
    String load(String url);
}
interface StringOutput {
    void print(String txt);
}
@Component
class MyBean {
    @Autowired
    MyBean(HttpLoader loader, StringOutput out) {
        out.print(loader.load("http://stackoverflow.com"));
    }
}

Wie können Sie diese Bohne testen? Z.B. so:

class MyBeanTest {
    public void creatingMyBean_writesStackoverflowPageToOutput() {
        // setup
        String stackOverflowHtml = "dummy";
        StringBuilder result = new StringBuilder();

        // execution
        new MyBean(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get, result::append);

        // evaluation
        assertEquals(result.toString(), stackOverflowHtml);
    }
}

Einfach, nicht wahr?

Während man immer noch von Spring abhängt (aufgrund der Annotationen), kann man die Abhängigkeit von Spring aufheben, ohne den Code zu ändern (nur die Annotationsdefinitionen), und der Testentwickler muss nichts über die Funktionsweise von Spring wissen (vielleicht sollte er das sowieso, aber es ermöglicht die Überprüfung und den Test des Codes unabhängig davon, was Spring tut).

Es ist immer noch möglich, dasselbe zu tun, wenn man den ApplicationContext verwendet. Allerdings müssen Sie dann den Mock ApplicationContext was eine riesige Schnittstelle ist. Sie benötigen entweder eine Dummy-Implementierung oder Sie können ein Mocking-Framework wie Mockito verwenden:

@Component
class MyBean {
    @Autowired
    MyBean(ApplicationContext context) {
        HttpLoader loader = context.getBean(HttpLoader.class);
        StringOutput out = context.getBean(StringOutput.class);

        out.print(loader.load("http://stackoverflow.com"));
    }
}
class MyBeanTest {
    public void creatingMyBean_writesStackoverflowPageToOutput() {
        // setup
        String stackOverflowHtml = "dummy";
        StringBuilder result = new StringBuilder();
        ApplicationContext context = Mockito.mock(ApplicationContext.class);
        Mockito.when(context.getBean(HttpLoader.class))
            .thenReturn(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get);
        Mockito.when(context.getBean(StringOutput.class)).thenReturn(result::append);

        // execution
        new MyBean(context);

        // evaluation
        assertEquals(result.toString(), stackOverflowHtml);
    }
}

Das ist durchaus eine Möglichkeit, aber ich denke, die meisten Leute würden zustimmen, dass die erste Option eleganter ist und den Test einfacher macht.

Die einzige Option, die wirklich ein Problem darstellt, ist diese:

@Component
class MyBean {
    @Autowired
    MyBean(StringOutput out) {
        out.print(new HttpLoader().load("http://stackoverflow.com"));
    }
}

Dies zu testen, erfordert einen enormen Aufwand, da Ihre Bohne sonst bei jedem Test versuchen wird, eine Verbindung zu stackoverflow herzustellen. Und sobald ein Netzwerkfehler auftritt (oder die Admins von Stackoverflow die Verbindung wegen zu hoher Zugriffsraten sperren), werden die Tests zufällig fehlschlagen.

Als Schlussfolgerung würde ich also nicht sagen, dass die Verwendung des ApplicationContext ist automatisch falsch und sollte unter allen Umständen vermieden werden. Wenn es jedoch bessere Möglichkeiten gibt (und die gibt es in den meisten Fällen), dann sollten Sie die besseren Möglichkeiten nutzen.

4voto

Brian Agnew Punkte 260470

Die Idee ist, dass Sie sich auf die Injektion von Abhängigkeiten ( Umkehrung der Steuerschuldnerschaft oder IoC). Das heißt, Ihre Komponenten werden mit den Komponenten konfiguriert, die sie benötigen. Diese Abhängigkeiten sind eingespritzt (über den Konstruktor oder die Setter) - Sie bekommen sie nicht selbst.

ApplicationContext.getBean() erfordert, dass Sie eine Bean innerhalb Ihrer Komponente explizit benennen. Durch die Verwendung von IoC kann Ihre Konfiguration stattdessen bestimmen, welche Komponente verwendet werden soll.

So können Sie Ihre Anwendung mit verschiedenen Komponentenimplementierungen leicht neu verkabeln oder Objekte für das Testen auf einfache Weise konfigurieren, indem Sie Mocked-Varianten bereitstellen (z. B. eine Mocked-DAO, damit Sie beim Testen nicht auf eine Datenbank stoßen).

4voto

StaxMan Punkte 107669

Andere haben auf das allgemeine Problem hingewiesen (und es sind gültige Antworten), aber ich möchte nur eine zusätzliche Bemerkung machen: Es geht nicht darum, dass man es NIE tun sollte, sondern eher darum, es so wenig wie möglich zu tun.

Normalerweise bedeutet dies, dass es genau einmal gemacht wird: beim Bootstrapping. Und dann geht es nur um den Zugriff auf die "Root"-Bean, über die andere Abhängigkeiten aufgelöst werden können. Dies kann wiederverwendbarer Code sein, wie z. B. ein Basisservlet (bei der Entwicklung von Webanwendungen).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X