Dlaczego jest aplikacja Springkontekst.getBean uważany za złego?

Zadałem ogólne Pytanie wiosenne: Auto-cast Spring Beans i wielu ludzi odpowiedziało, że wywoływanie Spring ' S ApplicationContext.getBean() powinno być jak najbardziej unikane. Dlaczego?

Jak inaczej uzyskać dostęp do fasoli, które skonfigurowałem, aby stworzyć Spring?

Używam Springa w aplikacji nie-webowej i planowałem uzyskać dostęp do udostępnionego ApplicationContextobiektu zgodnie z opisem LiorH.

Poprawka

Akceptuję odpowiedź poniżej, ale oto alternatywne ujęcie Martina Fowlera, który omawia zalety iniekcji zależności vs. używanie lokalizatora usług (co jest zasadniczo takie samo jak wywołanie zawiniętego ApplicationContext.getBean()).

W części Fowler stwierdza, " z lokalizatorem usługi Klasa aplikacji prosi o nią [usługę] jawnie Komunikatem do lokalizatora. Przy wtrysku nie ma wyraźnego żądania, usługa pojawia się w klasie Aplikacji - stąd inwersja sterowania. Inwersja sterowania to wspólna cecha frameworków, ale jest to coś, co ma swoją cenę. Wydaje się, że jest to trudne do zrozumienia i prowadzi do problemów, gdy próbujesz debugować. Więc ogólnie wolę tego unikać [odwrócenie kontroli], chyba że tego potrzebuję. To nie znaczy, że jest to zła rzecz, ale myślę, że musi się usprawiedliwić przed prostszą alternatywą."

 289
Author: Community, 2009-05-01

14 answers

Wspomniałem o tym w komentarzu na inne pytanie, ale cała idea inwersji sterowania polega na tym, aby żadna z Twoich klas nie wiedziała ani nie dbała o to, jak zdobywają obiekty, od których zależą. Ułatwia to zmianę rodzaju implementacji danej zależności w dowolnym momencie. Ułatwia to również testowanie klas, ponieważ można zapewnić przykładowe implementacje zależności. W końcu sprawia, że klasy są prostsze i bardziej skupiają się na ich jądrze odpowiedzialność.

Wywołanie ApplicationContext.getBean() nie jest inwersją sterowania! Chociaż wciąż łatwo jest zmienić implementację skonfigurowaną dla danej nazwy bean, Klasa opiera się teraz bezpośrednio na Spring, aby zapewnić tę zależność i nie może uzyskać jej w inny sposób. Nie możesz po prostu zrobić własnej implementacji w klasie testowej i przekazać jej samemu. To w zasadzie zaprzecza celowi Springa jako kontenera iniekcji zależności.

Wszędzie, gdzie chcesz powiedzieć:

MyClass myClass = applicationContext.getBean("myClass");

Ty należy zamiast tego, na przykład, zadeklarować metodę:

public void setMyClass(MyClass myClass) {
   this.myClass = myClass;
}

A następnie w konfiguracji:

<bean id="myClass" class="MyClass">...</bean>

<bean id="myOtherClass" class="MyOtherClass">
   <property name="myClass" ref="myClass"/>
</bean>

Spring automatycznie wstrzyknie myClass do myOtherClass.

Zadeklaruj wszystko w ten sposób, a u podstaw tego wszystkiego masz coś w stylu:

<bean id="myApplication" class="MyApplication">
   <property name="myCentralClass" ref="myCentralClass"/>
   <property name="myOtherCentralClass" ref="myOtherCentralClass"/>
</bean>

MyApplication jest najbardziej centralną klasą i zależy przynajmniej pośrednio od każdej innej usługi w twoim programie. Podczas bootstrapowania w Twojej metodzie main możesz wywołać applicationContext.getBean("myApplication"), ale nie musisz wywoływać getBean() gdziekolwiek indziej!

 211
Author: ColinD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 12:54:52

Powody, dla których warto preferować Service Locator zamiast inwersji sterowania (IoC) to:

  1. Service Locator jest znacznie, znacznie łatwiejsze dla innych osób do naśladowania w kodzie. IoC jest "magiczne", ale programiści konserwacji muszą zrozumieć konfiguracje sprężyn i wszystkie niezliczone lokalizacje, aby dowiedzieć się, jak podłączyć swoje obiekty.

  2. IoC jest fatalny w debugowaniu problemów z konfiguracją. W niektórych klasach aplikacji aplikacja nie uruchomi się, gdy źle skonfigurowany i możesz nie mieć szansy przejść przez to, co dzieje się z debuggerem.

  3. IoC opiera się przede wszystkim na XML (adnotacje poprawiają rzeczy, ale nadal jest wiele XML tam). Oznacza to, że programiści nie mogą pracować nad Twoim programem, dopóki nie znają wszystkich magicznych tagów zdefiniowanych przez Spring. Nie wystarczy już znać Javę. Utrudnia to mniej doświadczonym programistom (np. w rzeczywistości słaba konstrukcja jest stosowanie bardziej skomplikowanego rozwiązania, gdy prostsze rozwiązanie, takie jako lokalizator usług, spełni te same wymagania). Dodatkowo wsparcie dla diagnozowania problemów XML jest znacznie słabsze niż wsparcie dla problemów Java.

  4. Dependency injection jest bardziej odpowiednie dla większych programów. W większości przypadków dodatkowa złożoność nie jest tego warta.

  5. Często Spring jest używany w przypadku, gdy "możesz chcieć zmienić implementację później". Istnieją inne sposoby osiągnięcia tego celu bez złożoności Spring IoC.

  6. Dla WWW aplikacje (Java EE WARs) kontekst Spring jest efektywnie związany w czasie kompilacji (chyba, że chcesz, aby operatory grały wokół kontekstu w wybuchu wojny). Możesz sprawić, że Spring użyje plików właściwości, ale w przypadku servletów pliki właściwości będą musiały znajdować się w wstępnie ustalonej lokalizacji, co oznacza, że nie możesz wdrożyć wielu servletów w tym samym czasie na tym samym polu. Możesz użyć Springa z JNDI do zmiany właściwości podczas uruchamiania servletu, ale jeśli używasz JNDI dla administratora-modyfikowalne parametry potrzeba samej sprężyny zmniejsza się (ponieważ JNDI jest skutecznie lokalizatorem usług).

  7. Dzięki Spring możesz stracić kontrolę nad programem, jeśli Spring jest wysyłany do Twoich metod. Jest to wygodne i działa w wielu rodzajach aplikacji, ale nie we wszystkich. Może być konieczne kontrolowanie przepływu programu, gdy musisz utworzyć zadania (wątki itp.) podczas inicjalizacji lub potrzebujesz modyfikowalnych zasobów, o których Spring nie wiedział, kiedy zawartość była powiązana z Twoim Wojna.

Spring jest bardzo dobry do zarządzania transakcjami i ma pewne zalety. Po prostu IoC może być nadmiernie inżynierski w wielu sytuacjach i wprowadzić nieuzasadnioną złożoność dla opiekunów. Nie używaj IoC automatycznie, nie myśląc o tym, jak go nie używać.

 66
Author: Moa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-03 09:53:00

To prawda, że włączenie klasy do kontekstu aplikacji.xml unika konieczności używania getBean. Jednak nawet to jest w rzeczywistości niepotrzebne. Jeśli piszesz samodzielną aplikację i nie chcesz umieszczać swojej klasy kierowcy w kontekście aplikacji.xml, możesz użyć następującego kodu, aby mieć zależności sterownika Spring autowire:

public class AutowireThisDriver {

    private MySpringBean mySpringBean;    

    public static void main(String[] args) {
       AutowireThisDriver atd = new AutowireThisDriver(); //get instance

       ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                  "/WEB-INF/applicationContext.xml"); //get Spring context 

       //the magic: auto-wire the instance with all its dependencies:
       ctx.getAutowireCapableBeanFactory().autowireBeanProperties(atd,
                  AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);        

       // code that uses mySpringBean ...
       mySpringBean.doStuff() // no need to instantiate - thanks to Spring
    }

    public void setMySpringBean(MySpringBean bean) {
       this.mySpringBean = bean;    
    }
}

Musiałem to zrobić kilka razy, gdy mam jakąś samodzielną klasę, która musi używać jakiegoś aspektu mojej aplikacji (np. testowanie), ale nie chcę go włączać do kontekstu aplikacji, ponieważ nie jest on w rzeczywistości częścią aplikacji. Zauważ również, że pozwala to uniknąć konieczności przeglądania fasoli za pomocą nazwy ciągów znaków, co zawsze uważałem za brzydkie.

 25
Author: iftheshoefritz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-07 11:35:09

Jedną z najfajniejszych zalet używania czegoś takiego jak wiosna jest to, że nie musisz łączyć ze sobą przedmiotów. Głowa Zeusa rozdziela się i pojawiają się Twoje klasy, w pełni uformowane ze wszystkimi ich zależnościami utworzonymi i podłączonymi, w razie potrzeby. To magiczne i fantastyczne.

Im więcej mówisz, tym mniej magii dostajesz. Mniej kodu jest prawie zawsze lepsze. Jeśli twoja klasa naprawdę potrzebowała fasolki ClassINeed, dlaczego jej nie podłączyłeś?

That said, something oczywiście musi utworzyć pierwszy obiekt. Nie ma nic złego w Twojej głównej metodzie zdobywania fasoli lub dwóch poprzez getBean (), ale powinieneś jej unikać, ponieważ za każdym razem, gdy jej używasz, nie używasz całej magii Wiosny.

 22
Author: Brandon Yarbrough,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-01 17:46:37

Motywacją jest pisanie kodu, który nie zależy wprost od Springa. W ten sposób, jeśli zdecydujesz się zmienić kontenery, nie musisz przepisywać żadnego kodu.

Pomyśl o kontenerze jako o czymś, co jest niewidoczne dla Twojego kodu, magicznie zaspokajając jego potrzeby, bez pytania.

Iniekcja zależności jest kontrapunktem wzorca "service locator". Jeśli zamierzasz wyszukać zależności po nazwie, równie dobrze możesz pozbyć się kontenera DI i użyć czegoś takiego JNDI.

 16
Author: erickson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-01 17:48:12

Używanie @Autowired lub ApplicationContext.getBean() to naprawdę to samo. Na oba sposoby otrzymujesz fasolę, która jest skonfigurowana w Twoim kontekście, a na oba sposoby Twój kod zależy od wiosny. Jedyną rzeczą, której powinieneś unikać, jest tworzenie instancji ApplicationContext. Zrób to tylko raz! Innymi słowy, linia jak

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

Należy stosować tylko raz w aplikacji.

 13
Author: easyplanner.cu.cc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-29 07:57:20

Jednym z pomieszczeń sprężynowych jest unikanie sprzężenia . Define and use Interfaces, DI, AOP and avoid using ApplicationContext.getBean() :-)

 5
Author: sourcerebels,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-01 23:09:14

Jednym z powodów jest testowalność. Powiedz, że masz tę klasę:

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

Jak możesz przetestować tę fasolę? Np. Tak:

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);
    }
}
Spokojnie, prawda?

Podczas gdy nadal jesteś zależny od Springa (ze względu na adnotacje), możesz usunąć swoją zależność od springa bez zmiany kodu (tylko definicje adnotacji), a programista testu nie musi wiedzieć nic o tym, jak działa spring (może i powinien, ale pozwala to na przeglądanie i testowanie kodu oddzielnie od tego, co wiosna robi).

Nadal można zrobić to samo podczas korzystania z ApplicationContext. Jednak wtedy musisz wyśmiewać ApplicationContext, który jest ogromnym interfejsem. Albo potrzebujesz atrapy implementacji, albo możesz użyć szyderczego frameworka, takiego jak Mockito:

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

Jest to całkiem możliwe, ale myślę, że większość ludzi zgodzi się, że pierwsza opcja jest bardziej elegancka i ułatwia test.

Jedyną opcją, która jest naprawdę problemem, jest ta:

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

Testowanie tego wymaga ogromnego wysiłku lub Bean będzie próbował połączyć się ze stackoverflow na każdym teście. A gdy tylko wystąpi awaria sieci (lub Administratorzy w stackoverflow zablokują cię z powodu nadmiernej szybkości dostępu), będziesz miał losowo nieudane testy.

Więc jako wniosek nie powiedziałbym, że korzystanie z ApplicationContext bezpośrednio jest automatycznie błędne i powinno być unikane za wszelką cenę. Jeśli jednak istnieją lepsze opcje (a są w większości przypadków), użyj lepszych opcji.

 5
Author: yankee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-25 13:59:50

Chodzi o to, że polegasz na iniekcji zależności ( inwersji sterowania , lub IoC). Oznacza to, że Twoje komponenty są skonfigurowane z komponentami, których potrzebują. Te zależności są wstrzykiwane (przez konstruktor lub settery) - nie dostajesz wtedy sam.

ApplicationContext.getBean() wymaga jawnego nazwania fasoli w komponencie. Zamiast tego, używając IoC, twoja konfiguracja może określić, który komponent będzie używany.

Pozwala to na przepięcie aplikacji z można też w prosty sposób skonfigurować obiekty do testowania, dostarczając wyśmiewane warianty (np. wyśmiewane DAO, aby nie trafić w bazę danych podczas testowania)

 4
Author: Brian Agnew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-01 17:52:03

Inni wskazali na ogólny problem (i są poprawnymi odpowiedziami), ale dam tylko jeden dodatkowy komentarz: nie chodzi o to, że nigdy nie powinieneś tego robić, ale raczej, że rób to jak najmniej.

Zazwyczaj oznacza to, że odbywa się to dokładnie raz: podczas bootstrapowania. A potem wystarczy uzyskać dostęp do" korzeni " fasoli, dzięki której można rozwiązać inne zależności. Może to być Kod wielokrotnego użytku, jak podstawowy servlet (jeśli tworzymy aplikacje internetowe).

 4
Author: StaxMan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-01 18:03:41

Znalazłem tylko dwie sytuacje, w których wymagane było getBean ():

Inni wspominali o używaniu getBean () w main () do pobierania fasoli "main" dla samodzielnego programu.

Inne zastosowania getBean() są w sytuacjach, w których interaktywna Konfiguracja użytkownika określa makijaż fasoli dla konkretnej sytuacji. Tak, że na przykład część systemu rozruchowego zapętla się przez tabelę bazy danych za pomocą getBean() z definicją scope= 'prototype', a następnie ustawia dodatkowe właściwości. Prawdopodobnie istnieje interfejs użytkownika, który dostosowuje tabelę bazy danych, która byłaby bardziej przyjazna niż próba (ponownego)napisania kontekstu aplikacji XML.

 3
Author: nsayer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-01 18:24:38

Jest inny czas, kiedy używanie getBean ma sens. Jeśli rekonfigurujesz system, który już istnieje, gdzie zależności nie są jawnie wywoływane w plikach kontekstowych spring. Możesz rozpocząć proces, wprowadzając połączenia do getBean, dzięki czemu nie musisz podłączać wszystkiego na raz. W ten sposób możesz powoli budować swoją konfigurację sprężynową, umieszczając każdy element na swoim miejscu w czasie i odpowiednio ustawiając bity. Wezwania do getBean zostaną ostatecznie zastąpione, ale jako rozumiesz strukturę kodu, lub brak tam, można rozpocząć proces okablowania coraz więcej beans i za pomocą coraz mniej połączeń do getBean.

 3
Author: Tony Giaccone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-27 20:18:40

Jednak nadal istnieją przypadki, w których potrzebny jest wzorzec lokalizatora usług. na przykład, mam kontroler bean, ten kontroler może mieć domyślne service beans, które mogą być wstrzykiwane przez konfigurację. chociaż może istnieć wiele dodatkowych lub nowych usług, kontroler ten może wywołać teraz lub później, które następnie potrzebują lokalizatora usług, aby pobrać fasolę usługi.

 2
Author: lwpro2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-27 09:45:45

Powinieneś użyć: ConfigurableApplicationContext zamiast Dla ApplicationContext

 0
Author: Hai Nguyen Le,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-05-19 07:01:04