Spring'in ApplicationContext.getBean'ı neden kötü sayılıyor?


270

Genel bir Bahar sorusu sordum: Spring Beans'i otomatik olarak döktüm ve Spring'in çağrılmasından ApplicationContext.getBean()mümkün olduğunca kaçınılması gerektiğini birden fazla kişi yanıtladı . Neden?

İlkbahar oluşturmak için yapılandırdığım fasulyeye başka nasıl erişebilirim?

Spring'i web dışı bir uygulamada kullanıyorum ve LiorH tarafından tarif edildiği gibi paylaşılan bir ApplicationContextnesneye erişmeyi planlamıştım .

düzeltme

Aşağıdaki cevabı kabul ediyorum, ancak burada bir Servis Bulucu (aslında bir sarılmış olarak adlandırmakla aynı ApplicationContext.getBean()) olan Bağımlılık Enjeksiyonunun avantajlarını tartışan Martin Fowler tarafından alternatif bir yaklaşım var .

Fowler kısmen, " Servis bulucu ile uygulama sınıfı, kendisinden [servis] bulucuya bir mesajla açık bir şekilde sorar. Enjeksiyonla açık bir istek yoktur, hizmet uygulama sınıfında görünür - dolayısıyla kontrolün ters çevrilmesi. Kontrolün ters çevrilmesi, çerçevelerin ortak bir özelliğidir, ancak bu bir fiyata gelen bir şeydir.Ancak zor olma eğilimindedir ve hata ayıklamaya çalışırken sorunlara yol açar.Bu yüzden genel olarak bundan kaçınmayı tercih ederim Bu ona kötü bir şey demek değil, sadece daha basit bir alternatif üzerinden kendini haklı çıkarması gerektiğini düşünüyorum. "

Yanıtlar:


202

Diğer soru üzerine bir yorumda bundan bahsettim, ancak kontrolün tersine çevrilmesi fikri, sınıflarınızın hiçbirinin bağımlı oldukları nesneleri nasıl aldıklarını bilmelerini veya bakımlarını yapmamasını sağlamaktır . Bu, herhangi bir zamanda kullandığınız belirli bir bağımlılığın uygulama türünü değiştirmeyi kolaylaştırır. Ayrıca, bağımlılıkların sahte uygulamalarını sağlayabileceğiniz için sınıfların test edilmesini kolaylaştırır. Son olarak, sınıfları temel sorumluluklarına daha basit ve daha odaklanmış hale getirir .

Arama ApplicationContext.getBean()Denetimi Tersine Çevirmez! Verilen fasulye adı için hangi uygulamanın yapılandırıldığını değiştirmek hala kolay olsa da, sınıf şimdi bu bağımlılığı sağlamak için doğrudan Bahar'a güveniyor ve başka bir şekilde alamıyor. Bir test sınıfında kendi sahte uygulamanızı yapıp bunu kendinize aktaramazsınız. Bu temelde Spring'in bağımlılık enjeksiyon kabı olarak amacını yener.

Söylemek istediğiniz her yerde:

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

bunun yerine, örneğin bir yöntem beyan etmelisiniz:

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

Ve sonra yapılandırmanızda:

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

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

Bahar daha sonra otomatik olarak enjekte myClassedilir myOtherClass.

Her şeyi bu şekilde ilan edin ve kökünün hepsinde şöyle bir şey var:

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

MyApplicationen merkezi sınıftır ve en azından dolaylı olarak programınızdaki diğer tüm hizmetlere bağlıdır. Önyükleme yaparken, mainyönteminizde arayabilir, applicationContext.getBean("myApplication")ancak getBean()başka bir yeri aramanız gerekmez !


3
Bununla ilgili olarak, bir nesne oluştururken yalnızca ek açıklamalarla çalışan bir şey var mı new MyOtherClass()? Ben @Autowired hakkında biliyorum, ama ben sadece tarlalarda kullandım ve kırılıyor new MyOtherClass()..
Tim

70
ApplicationContext.getBean () yönteminin IoC olmadığı doğru değildir. Niether, tüm sınıflarınızın Bahar tarafından başlatılması zorunludur . Bu uygunsuz dogma. ApplicationContext'in kendisi enjekte edilirse, o zaman bir fasulyeyi bu şekilde başlatmasını istemek mükemmeldir - ve oluşturduğu fasulye, başlangıçta enjekte edilen ApplicationContext'e göre farklı uygulamalar olabilir. Örneğin, derleme zamanında bilinmeyen, ancak spring.xml dosyamda tanımlanan uygulamalardan biriyle eşleşen bir fasulye adına dayalı olarak yeni fasulye örnekleri oluşturduğum bir senaryom var.
Alex Worden

3
Alex ile aynı fikirdeyim, bir fabrika sınıfının yalnızca kullanıcı etkileşimi ile çalışma zamanında hangi fasulye veya uygulamayı kullanacağını bileceği aynı konuya sahibim, bu ContextAware arayüzünün geldiğini düşünüyorum
Ben

3
@elbek: applicationContext.getBeanbağımlılık enjeksiyonu değil: çerçeveye doğrudan erişerek, servis bulucu olarak kullanıyor .
ColinD

6
@herman: Spring'i bilmiyorum çünkü uzun zamandır kullanmadım, ama JSR-330 / Guice / Dagger'da bunu bir Provider<Foo>yerine enjekte ederek Foove provider.get()her ihtiyacınız olduğunda arayarak yeni örnek. Kabın kendisine referans yok ve kolayca Providertest için bir oluşturabilirsiniz .
ColinD

64

Hizmet Konum Belirleyiciyi Denetimi Tersine Çevirme (IoC) tercih etme nedenleri:

  1. Servis Bulucu, kodunuzda diğer kişilerin izlemesi çok, çok daha kolaydır. IoC 'sihir' ama bakım programcıları, nesnelerinizi nasıl bağladığınızı anlamak için kıvrımlı Yay yapılandırmalarınızı ve sayısız konumunuzu anlamalıdır.

  2. IoC, yapılandırma sorunlarının hatalarını ayıklamak için korkunç. Belirli uygulama sınıflarında uygulama, yanlış yapılandırıldığında başlamaz ve bir hata ayıklayıcıda olup bitenlere adım atma şansınız olmayabilir.

  3. IoC öncelikle XML tabanlıdır (Ek açıklamalar bir şeyleri iyileştirir, ancak hala çok fazla XML vardır). Bu, Spring tarafından tanımlanan tüm sihirli etiketleri bilmedikçe geliştiricilerin programınız üzerinde çalışamayacağı anlamına gelir. Artık Java'yı bilmek yeterince iyi değil. Bu, daha az deneyim programcılarını engeller (yani, Servis Bulucu gibi daha basit bir çözüm aynı gereksinimleri karşıladığında daha karmaşık bir çözüm kullanmak aslında zayıf bir tasarımdır). Ayrıca, XML sorunlarının teşhisi için destek, Java sorunlarının desteğinden çok daha zayıftır.

  4. Bağımlılık enjeksiyonu daha büyük programlar için daha uygundur. Çoğu zaman ek karmaşıklık buna değmez.

  5. Genellikle "uygulamayı daha sonra değiştirmek isteyebilirsin" durumunda Bahar kullanılır. Bunu, Bahar IoC'nin karmaşıklığı olmadan başarmanın başka yolları da var.

  6. Web uygulamaları (Java EE WARs) için Spring içeriği derleme zamanında etkili bir şekilde bağlanır (operatörlerin patlatılmış savaşta bağlam etrafında toplanmasını istemiyorsanız). Spring use özellik dosyalarını yapabilirsiniz, ancak sunucu uygulamaları özellik dosyalarının önceden belirlenmiş bir konumda olması gerekir; bu, aynı kutuda aynı anda birden çok sunucu uygulamasını dağıtamayacağınız anlamına gelir. Sunucu uygulaması başlangıç ​​zamanında özellikleri değiştirmek için Spring'i JNDI ile kullanabilirsiniz, ancak yönetici tarafından değiştirilebilen parametreler için JNDI kullanıyorsanız, Spring'in gereksinimi azalır (JNDI etkin bir şekilde bir Servis Bulucu olduğundan).

  7. Spring ile Spring yöntemlerinizi gönderiyorsa program Kontrolünü kaybedebilirsiniz. Bu uygundur ve birçok uygulama türü için çalışır, ancak hepsi için geçerli değildir. Başlatma sırasında görevler (iş parçacıkları vb.) Oluşturmanız gerektiğinde veya Spring'in içeriğin WAR'nize ne zaman bağlandığı hakkında bilmediği değiştirilebilir kaynaklara ihtiyacınız olduğunda program akışını denetlemeniz gerekebilir.

Bahar işlem yönetimi için çok iyidir ve bazı avantajları vardır. Sadece IoC, birçok durumda aşırı mühendislik yapabilir ve koruyucular için haksız karmaşıklık getirebilir. IoC'yi önce kullanmamanın yollarını düşünmeden otomatik olarak kullanmayın.


7
Ayrıca - ServiceLocator'ınız her zaman Spring'den gelen IoC'yi kullanabilir, kodunuzun Spring'e bağımlı olmasını, Spring ek açıklamalarıyla dolu ve çözülemeyen magik'i soyutlayabilir. Kısa süre önce, Spring'in desteklenmediği GoogleAppEngine'a bir grup kod taşıdım. Keşke tüm IoC'leri bir ServiceFactory'nin arkasına gizlemiş olsaydım!
Alex Worden

IoC, hor gördüğüm bir anemik etki alanı modelini teşvik ediyor. Varlık fasulye, kendi davranışlarını uygulamak için hizmetlerini aramak için bir yol gerekir. Bu katmanda, bir servis bulucuya gerçekten ihtiyaç duyamazsınız.
Joel

4
Bizar. Bahar TÜMÜNÜ ek açıklamalarla birlikte kullanıyorum. Gerçekten de belirli bir öğrenme eğrisi dahil olmasına rağmen, şimdi, bakım, hata ayıklama, netlik, okunabilirlik konusunda hiçbir sorunum yok ... Sanırım şeyleri nasıl yapılandırdığınız hile.
Lawrence

25

Sınıfı application-context.xml dosyasına dahil etmenin getBean kullanma gereksinimini önlediği doğrudur. Ancak, bu bile gereksizdir. Tek başına bir uygulama yazıyorsanız ve sürücü sınıfınızı application-context.xml dosyasına dahil etmek istemiyorsanız, Spring'in sürücünün bağımlılıklarını otomatik olarak bağlaması için aşağıdaki kodu kullanabilirsiniz:

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

Uygulamamın bazı yönlerini (örneğin test etmek için) kullanması gereken bir tür bağımsız sınıfımız olduğunda bunu birkaç kez yapmam gerekiyordu, ancak uygulama bağlamına dahil etmek istemiyorum çünkü aslında uygulamanın bir parçası. Ayrıca, her zaman çirkin olduğunu düşündüğüm bir String adı kullanarak fasulyeye bakma ihtiyacını ortadan kaldırdığını unutmayın.


Bu yöntemi @Autowiredek açıklama ile başarılı bir şekilde kullanabildim .
Mart'ta blong

21

Bahar gibi bir şey kullanmanın en güzel avantajlarından biri, nesnelerinizi birbirine bağlamak zorunda kalmamanızdır. Zeus'un başı açılır ve sınıflarınız görünür, gerektiğinde tüm bağımlılıkları yaratılır ve kablolanır. Büyülü ve harika.

Ne kadar çok şey söylerseniz ClassINeed classINeed = (ClassINeed)ApplicationContext.getBean("classINeed");, o kadar az sihir alırsınız. Daha az kod neredeyse her zaman daha iyidir. Sınıfınız gerçekten bir ClassINeed fasulyesine ihtiyaç duyduysa, neden sadece kabloyu bağlamadınız?

Bununla birlikte, ilk nesnenin yaratılması için bir şeyin açık olması gerekir. Ana yönteminizde getBean () ile bir veya iki fasulye elde etmekle ilgili yanlış bir şey yoktur, ancak bundan kaçınmalısınız çünkü bunu her kullandığınızda, Spring'in büyüsünü gerçekten kullanmıyorsunuzdur.


1
Ancak OP "ClassINeed" demiyor, "BeanNameINeed" diyor - IoC konteynerinin herhangi bir şekilde yapılandırılmış herhangi bir sınıfta bir örnek oluşturmasına izin veriyor. Belki de IoC'den daha çok "servis bulucu" modeline benzer, ancak yine de gevşek bağlantı ile sonuçlanır.
HDave

16

Motivasyon açıkça Bahar'a bağlı olmayan bir kod yazmaktır. Bu şekilde, kapları değiştirmeyi seçerseniz, herhangi bir kodu yeniden yazmak zorunda kalmazsınız.

Kapsayıcıyı, kodunuz için görünmez bir şey olarak düşünün, sorulmadan sihirli bir şekilde ihtiyaçlarını karşılayın.

Bağımlılık enjeksiyonu, "servis bulucu" modelinin bir karşılığıdır. Bağımlılıkları ada göre arayacaksanız, DI konteynerinden de kurtulabilir ve JNDI gibi bir şey kullanabilirsiniz.


11

@AutowiredVeya kullanmak ApplicationContext.getBean()gerçekten aynı şeydir. Her iki şekilde de bağlamınızda yapılandırılmış olan fasulyeyi alırsınız ve her iki şekilde de kodunuz bahara bağlıdır. Kaçınmanız gereken tek şey ApplicationContext'inizin örneğidir. Bunu sadece bir kez yapın! Başka bir deyişle,

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

başvurunuzda yalnızca bir kez kullanılmalıdır.


Hayır! Bazen @Autowired veya ApplicationContext.getBean () tamamen farklı fasulye üretebilir. Bunun nasıl olduğundan emin değilim, ama şu anda bu sorunla mücadele ediyorum.
Oleksandr_DJ

4

Fikir, bağımlılık enjeksiyonuna ( kontrolün ters çevrilmesi veya IoC) güvenmenizdir . Yani, bileşenleriniz ihtiyaç duydukları bileşenlerle yapılandırılır. Bu bağımlılıklar enjekte edilir (kurucu veya ayarlayıcılar aracılığıyla) - o zaman kendiniz alamazsınız.

ApplicationContext.getBean()bileşeninize açıkça bir fasulye isimlendirmenizi gerektirir. Bunun yerine, IoC kullanarak yapılandırmanız hangi bileşenin kullanılacağını belirleyebilir.

Bu, uygulamanızı farklı bileşen uygulamaları ile kolayca yeniden sarmanıza veya alaylı varyantlar sağlayarak nesneleri test etmek için basit bir şekilde yapılandırmanıza olanak tanır (örn. Alaycı bir DAO, böylece test sırasında bir veritabanına çarpmazsınız)


4

Diğerleri genel soruna işaret etti (ve geçerli cevaplar), ancak sadece bir ek yorum sunacağım: ASLA bunu yapmamalısınız değil, mümkün olduğunca az yapın.

Genellikle bu tam olarak bir kez yapıldığı anlamına gelir: önyükleme sırasında. Ve sonra sadece diğer bağımlılıkların çözülebileceği "kök" fasulyeye erişmek. Bu, temel sunucu uygulaması gibi yeniden kullanılabilir kod olabilir (web uygulamaları geliştiriyorsanız).


4

Bahar tesislerinden biri bağlantıdan kaçınmaktır . Interfaces, DI, AOP tanımlayın ve kullanın ve ApplicationContext.getBean () :-) kullanmaktan kaçının


4

Sebeplerden biri test edilebilirliktir. Diyelim ki bu sınıfa sahipsiniz:

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

Bu fasulyeyi nasıl test edebilirsiniz? Örneğin şöyle:

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

Kolay değil mi?

Hala Spring'e (ek açıklamalardan dolayı) bağlıyken, herhangi bir kodu değiştirmeden (sadece ek açıklama tanımları) bahar bağımlılığını kaldırabilirsiniz ve test geliştiricisinin baharın nasıl çalıştığı hakkında bir şey bilmesine gerek yoktur (belki de yine de yapmalıdır, ancak kodu, yayın ne yaptığından ayrı olarak incelemeye ve test etmeye izin verir).

ApplicationContext'i kullanırken de aynısını yapmak hala mümkündür. Ancak o zaman ApplicationContextbüyük bir arayüz olan sahte gerekir . Kukla bir uygulamaya ihtiyacınız var veya Mockito gibi bir alaycı çerçeve kullanabilirsiniz:

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

Bu oldukça bir olasılık, ama çoğu insanın ilk seçeneğin daha zarif olduğunu ve testi basitleştirdiğini kabul edeceğini düşünüyorum.

Gerçekten sorun olan tek seçenek budur:

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

Bunu test etmek büyük çaba gerektirir veya fasulyeniz her testte yığın akışına bağlanmaya çalışır. Ve bir ağ arızanız olur olmaz (veya yığın akışındaki yöneticiler aşırı erişim oranı nedeniyle sizi engeller) rastgele başarısız testlere sahip olursunuz.

Sonuç olarak, ApplicationContextdoğrudan kullanmanın otomatik olarak yanlış olduğunu ve ne pahasına olursa olsun kaçınılması gerektiğini söylemem. Ancak, daha iyi seçenekler varsa (ve çoğu durumda), daha iyi seçenekleri kullanın.


3

Sadece getBean () gerekli iki durum buldum:

Diğerleri bağımsız bir program için "ana" çekirdeği getirmek için main () içinde getBean () kullanarak bahsetti.

GetBean () 'den yaptığım başka bir kullanım, etkileşimli bir kullanıcı yapılandırmasının belirli bir durum için fasulye yapısını belirlediği durumlardır. Böylece, örneğin, önyükleme sisteminin bir kısmı, scope = 'prototype' fasulye tanımına sahip getBean () kullanarak ve sonra ek özellikler ayarlayarak bir veritabanı tablosunda dolaşır. Muhtemelen, veritabanı bağlamını ayarlayan ve uygulama bağlamı XML'ini (yeniden) yazmaya çalışmaktan daha kolay olacak bir kullanıcı arayüzü vardır.


3

GetBean kullanmanın bir anlamı daha var. Zaten var olan bir sistemi yeniden yapılandırıyorsanız, bağımlılıklar bahar bağlam dosyalarında açıkça çağrılmaz. GetBean için çağrılar vererek işleme başlayabilirsiniz, böylece hepsini aynı anda kablolamak zorunda kalmazsınız. Bu şekilde yay konfigürasyonunuzu yavaşça oluşturabilir, her parçayı zamanla yerine koyabilir ve uçları düzgün bir şekilde sıralayabilirsiniz. GetBean çağrıları nihayetinde değiştirilecek, ancak kodun yapısını anladığınızda veya orada eksiklik olarak, gittikçe daha fazla fasulye kablolama ve getBean için daha az ve daha az çağrı kullanma sürecine başlayabilirsiniz.


2

ancak, servis bulucu modeline ihtiyaç duyduğunuz durumlar da vardır. Örneğin, bir denetleyici fasulye var, bu denetleyici bağımlılığı yapılandırma tarafından enjekte edilebilir bazı varsayılan hizmet fasulye olabilir. bu denetleyici şimdi veya daha sonra çağırabilecek birçok ek veya yeni hizmet olsa da, daha sonra servis çekirdeklerini almak için servis bulucuya ihtiyaç duyar.


0

Kullanmanız gerekir: ApplicationContext yerine ConfigurableApplicationContext

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.